mirror of
https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
synced 2025-09-30 22:25:02 +02:00
Revert "permet l'ajout des frameworks et des routes"
This reverts commit 361112699c
This commit is contained in:
51
app/node_modules/cejs/application/net/work_crawler/README.wiki
generated
vendored
51
app/node_modules/cejs/application/net/work_crawler/README.wiki
generated
vendored
@@ -1,51 +0,0 @@
|
||||
= CeJS 網路作品爬蟲程式庫 =
|
||||
批量下載網路作品(小說、漫畫)的函式庫。 WWW work crawler library.
|
||||
|
||||
More examples: See [https://github.com/kanasimi/work_crawler 各網站工具檔.js]
|
||||
|
||||
== 下載作業流程 ==
|
||||
|
||||
[[../work_crawler.js]]: [[arguments.js]]
|
||||
→ [[task.js]]
|
||||
→ [[search.js]]
|
||||
→ [[work.js]]
|
||||
→ [[chapter.js]]
|
||||
→ [[image.js]] or [[ebook.js]]
|
||||
|
||||
# 獲取伺服器列表。 start_downloading()
|
||||
# 解析設定檔,判別所要下載的作品列表。 parse_work_id(), get_work_list(), .base_URL, .extract_work_id()
|
||||
# 特別處理特定id。 .convert_id()
|
||||
# 解析 作品名稱 → 作品id get_work(), .search_URL, .parse_search_result()
|
||||
# 獲取作品資訊與各章節資料。 get_work_data(), pre_process_chapter_list_data(), process_chapter_list_data()
|
||||
# 對於章節列表與作品資訊分列不同頁面(URL)的情況,應該另外指定 .chapter_list_URL。 get_work_data(), .work_URL, .parse_work_data(), chapter_list_URL, .get_chapter_list(), .after_get_work_data()
|
||||
# 獲取每一個章節的內容與各個影像資料。 pre_get_chapter_data(), .chapter_URL, get_chapter_data(), .pre_parse_chapter_data(), .parse_chapter_data()
|
||||
# 獲取各個章節的每一個影像內容。 get_image(), .image_preprocessor(), .image_post_processor(), .after_get_image()
|
||||
# finish_up(), .after_download_chapter(), .after_download_work()
|
||||
|
||||
== History ==
|
||||
{| class="wikitable"
|
||||
|+ History 沿革
|
||||
! Date !! Modify
|
||||
|-
|
||||
| 2016/10/30 21:40:6 || 完成主要架構設計與構思,開始撰寫程式。
|
||||
|-
|
||||
| 2016/11/1 23:15:16 || 正式運用:批量下載腾讯漫画 qq。
|
||||
|-
|
||||
| 2016/11/5 22:44:17 || 正式運用:批量下載漫画台 manhuatai。
|
||||
|-
|
||||
| 2016/11/27 19:7:2 || 模組化。 ([[sites]]/*)
|
||||
|-
|
||||
| 2019/10/13 13:23:25 || 分拆至 work_crawler/*.js
|
||||
|}
|
||||
|
||||
== See also ==
|
||||
* https://github.com/abc9070410/JComicDownloader
|
||||
* http://pxer.pea3nut.org/md/use https://github.com/eight04/ComicCrawler
|
||||
* https://github.com/riderkick/FMD https://github.com/yuru-yuri/manga-dl
|
||||
* https://github.com/Xonshiz/comic-dl
|
||||
* https://github.com/wellwind/8ComicDownloaderElectron
|
||||
* https://github.com/inorichi/tachiyomi
|
||||
* https://github.com/Arachnid-27/Cimoc
|
||||
* https://github.com/qq573011406/KindleHelper
|
||||
* https://github.com/InzGIBA/manga
|
||||
* [https://scrapy.org/ Scrapy 爬蟲框架]
|
540
app/node_modules/cejs/application/net/work_crawler/arguments.js
generated
vendored
540
app/node_modules/cejs/application/net/work_crawler/arguments.js
generated
vendored
@@ -1,540 +0,0 @@
|
||||
/**
|
||||
* @name WWW work crawler sub-functions
|
||||
*
|
||||
* @fileoverview WWW work crawler functions: part of command-line arguments
|
||||
*
|
||||
* @since 2019/10/20 拆分自 CeL.application.net.work_crawler.task
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
if (typeof CeL === 'function') {
|
||||
// 忽略沒有 Windows Component Object Model 的錯誤。
|
||||
CeL.env.ignore_COM_error = true;
|
||||
|
||||
CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.arguments',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : 'this,*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
}
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
var Work_crawler = library_namespace.net.work_crawler;
|
||||
|
||||
var gettext = library_namespace.locale.gettext;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 正規化定義參數的規範,例如數量包含可選範圍,可用 RegExp。如'number:0~|string:/v\\d/i',
|
||||
* 'number:1~400|string:item1;item2;item3'。亦可僅使用'number|string'。
|
||||
*
|
||||
* @see CeL.data.fit_filter()
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text#pattern
|
||||
*/
|
||||
function generate_argument_condition(condition) {
|
||||
if (library_namespace.is_Object(condition))
|
||||
return condition;
|
||||
|
||||
var condition_data = Object.create(null), matched, PATTERN = /([a-z]+)(?::(\/(\\[\s\S]|[^\/])+\/([i]*)|[^|]+))?(?:\||$)/g;
|
||||
while (matched = PATTERN.exec(condition)) {
|
||||
var type = matched[1], _condition = undefined;
|
||||
if (!matched[2]) {
|
||||
;
|
||||
|
||||
} else if (matched[3]) {
|
||||
_condition = new RegExp(matched[3], matched[4]);
|
||||
|
||||
} else if (type === 'number' && (_condition = matched[2].match(
|
||||
// @see CeL.date.parse_period.PATTERN
|
||||
/([+\-]?\d+(?:\.\d+)?)?\s*[–~-—─~〜﹣至]\s*([+\-]?\d+(?:\.\d+)?)?/))) {
|
||||
_condition = {
|
||||
min : _condition[1] && +_condition[1],
|
||||
max : _condition[2] && +_condition[2]
|
||||
};
|
||||
|
||||
} else if (type === 'number'
|
||||
&& (matched[2] === 'natural' || matched[2] === 'ℕ')) {
|
||||
_condition = function is_natural(value) {
|
||||
return value >= 1 && value === Math.floor(value);
|
||||
};
|
||||
|
||||
} else if (type === 'number'
|
||||
&& (matched[2] === 'natural+0' || matched[2] === 'ℕ+0')) {
|
||||
// Naturals with zero: non-negative integers 非負整數。
|
||||
_condition = function is_non_negative(value) {
|
||||
return value >= 0 && value === Math.floor(value);
|
||||
};
|
||||
|
||||
} else if (type === 'number' && matched[2] === 'integer') {
|
||||
_condition = function is_integer(value) {
|
||||
return value === Math.floor(value);
|
||||
};
|
||||
|
||||
} else {
|
||||
_condition = matched[2].split(';');
|
||||
}
|
||||
|
||||
condition_data[type] = _condition;
|
||||
}
|
||||
|
||||
return condition_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始設定好命令列選項之型態資料集。
|
||||
*
|
||||
* @param {Object}[arg_hash]
|
||||
* 參數型態資料集。
|
||||
* @param {Boolean}[append]
|
||||
* 添加至當前的參數型態資料集。否則會重新設定參數型態資料集。
|
||||
*
|
||||
* @returns {Object}命令列選項之型態資料集。
|
||||
*/
|
||||
function setup_argument_conditions(arg_hash, append) {
|
||||
if (append) {
|
||||
arg_hash = Object.assign(Work_crawler.prototype.import_arg_hash,
|
||||
arg_hash);
|
||||
} else if (arg_hash) {
|
||||
// default: rest import_arg_hash
|
||||
Work_crawler.prototype.import_arg_hash = arg_hash;
|
||||
} else {
|
||||
arg_hash = Work_crawler.prototype.import_arg_hash;
|
||||
}
|
||||
|
||||
Object.keys(arg_hash).forEach(function(key) {
|
||||
arg_hash[key] = generate_argument_condition(arg_hash[key]);
|
||||
});
|
||||
// console.log(arg_hash);
|
||||
return arg_hash;
|
||||
}
|
||||
|
||||
Work_crawler.setup_argument_conditions = setup_argument_conditions;
|
||||
|
||||
/**
|
||||
* 檢核 crawler 的設定參數。
|
||||
*
|
||||
* @param {String}key
|
||||
* 參數名稱
|
||||
* @param value
|
||||
* 欲設定的值
|
||||
*
|
||||
* @returns {Boolean} true: Error occudded
|
||||
*
|
||||
* @see CeL.data.fit_filter()
|
||||
*/
|
||||
function verify_arg(key, value) {
|
||||
if (!(key in this.import_arg_hash)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var type = typeof value, arg_type_data = this.import_arg_hash[key];
|
||||
// console.log(arg_type_data);
|
||||
|
||||
if (!(type in arg_type_data)) {
|
||||
library_namespace.warn([ 'verify_arg: ', {
|
||||
// gettext_config:{"id":"the-allowed-data-type-for-$1-is-$4-but-it-was-set-to-{$2}-$3"}
|
||||
T : [ '"%1" 這個值所允許的數值類型為 %4,但現在被設定成 {%2} %3',
|
||||
//
|
||||
key, typeof value, value,
|
||||
//
|
||||
library_namespace.is_Object(arg_type_data)
|
||||
//
|
||||
? Object.keys(arg_type_data).map(function(type) {
|
||||
return gettext(type);
|
||||
}).join('|') : arg_type_data ]
|
||||
} ]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
arg_type_data = arg_type_data[type];
|
||||
if (Array.isArray(arg_type_data)) {
|
||||
if (arg_type_data.length === 1
|
||||
&& typeof arg_type_data[0] === 'string') {
|
||||
var fso_type = arg_type_data[0]
|
||||
.match(/^fso_(file|files|directory|directories)$/);
|
||||
if (fso_type) {
|
||||
fso_type = fso_type[1];
|
||||
if (typeof value === 'string')
|
||||
value = value.split('|');
|
||||
// assert: Array.isArray(value)
|
||||
var error_fso = undefined, checker = fso_type
|
||||
.startsWith('file') ? library_namespace.storage.file_exists
|
||||
: library_namespace.storage.directory_exists;
|
||||
if (value.some(function(fso_path) {
|
||||
if (!checker(fso_path)) {
|
||||
error_fso = fso_path;
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
library_namespace.warn([ 'verify_arg: ', {
|
||||
// gettext_config:{"id":"some-$2-path(s)-specified-by-$1-do-not-exist-$3"}
|
||||
T : [ '至少一個由「%1」所指定的%2路徑不存在:%3', key,
|
||||
// gettext_config:{"id":"file","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"files","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"directory","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"directories","mark_type":"combination_message_id"}
|
||||
gettext(fso_type), error_fso ]
|
||||
} ]);
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// e.g., "string:value1,value2"
|
||||
if (arg_type_data.includes(value)) {
|
||||
// verified
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (arg_type_data && ('min' in arg_type_data)) {
|
||||
// assert: type === 'number'
|
||||
if ((!arg_type_data.min || arg_type_data.min <= value)
|
||||
&& (!arg_type_data.max || value <= arg_type_data.max)) {
|
||||
// verified
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (typeof arg_type_data === 'function') {
|
||||
if (arg_type_data(value))
|
||||
return;
|
||||
|
||||
} else {
|
||||
if (arg_type_data !== undefined) {
|
||||
library_namespace.warn([ 'verify_arg: ', {
|
||||
// gettext_config:{"id":"unable-to-process-$1-condition-with-value-type-$2"}
|
||||
T : [ '無法處理 "%1" 在數值類型為 %2 時之條件!', key, arg_type_data ]
|
||||
} ]);
|
||||
}
|
||||
// 應該修改審查條件式,而非數值本身的問題。
|
||||
return;
|
||||
}
|
||||
|
||||
library_namespace.warn([ 'verify_arg: ', {
|
||||
// gettext_config:{"id":"$1-is-set-to-the-problematic-value-{$2}-$3"}
|
||||
T : [ '"%1" 被設定成了有問題的值:{%2} %3', key, typeof value, value ]
|
||||
} ]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 設定 crawler 的參數。 normalize and setup value
|
||||
*
|
||||
* @example<code>
|
||||
|
||||
crawler.setup_value(key, value);
|
||||
|
||||
// 應該用:
|
||||
this.setup_value(key, value);
|
||||
// 不應用:
|
||||
this[key] = value;
|
||||
delete this[key];
|
||||
|
||||
</code>
|
||||
*
|
||||
* @param {any}
|
||||
* key
|
||||
* @param {any}
|
||||
* value
|
||||
*
|
||||
* @return {String}has error
|
||||
*/
|
||||
function setup_value(key, value) {
|
||||
if (!key)
|
||||
// gettext_config:{"id":"key-value-not-given"}
|
||||
return '未提供鍵值';
|
||||
|
||||
if (library_namespace.is_Object(key)) {
|
||||
// assert: value === undefined
|
||||
value = key;
|
||||
for (key in value) {
|
||||
this.setup_value(key, value[key]);
|
||||
}
|
||||
// TODO: return error
|
||||
return;
|
||||
}
|
||||
|
||||
// assert: typeof key === 'string'
|
||||
|
||||
switch (key) {
|
||||
case 'proxy':
|
||||
// 使用代理伺服器 proxy_server
|
||||
// TODO: check .proxy
|
||||
library_namespace.info({
|
||||
// gettext_config:{"id":"using-proxy-server-$1"}
|
||||
T : [ 'Using proxy server: %1', value ]
|
||||
});
|
||||
this.get_URL_options.proxy = this[key] = value;
|
||||
return;
|
||||
|
||||
case 'cookie':
|
||||
// set-cookie, document.cookie
|
||||
if (this.get_URL_options.agent) {
|
||||
library_namespace.merge_cookie(this.get_URL_options.agent,
|
||||
value);
|
||||
} else if (this.get_URL_options.cookie) {
|
||||
if (!/;\s*$/.test(this.get_URL_options.cookie))
|
||||
this.get_URL_options.cookie += ';';
|
||||
this.get_URL_options.cookie += value;
|
||||
} else {
|
||||
this.get_URL_options.cookie = value;
|
||||
}
|
||||
// console.trace(this.get_URL_options);
|
||||
return;
|
||||
|
||||
case 'timeout':
|
||||
value = library_namespace.to_millisecond(value);
|
||||
if (!(value >= 0)) {
|
||||
// gettext_config:{"id":"failed-to-parse-time"}
|
||||
return '無法解析的時間';
|
||||
}
|
||||
this.get_URL_options.timeout = this[key] = value;
|
||||
break;
|
||||
|
||||
// case 'agent':
|
||||
// @see function setup_agent(URL)
|
||||
|
||||
case 'user_agent':
|
||||
if (!value) {
|
||||
// gettext_config:{"id":"user-agent-is-not-set"}
|
||||
return '未設定 User-Agent。';
|
||||
}
|
||||
this.get_URL_options.headers['User-Agent'] = this[key] = value;
|
||||
break;
|
||||
|
||||
case 'Referer':
|
||||
if (!value
|
||||
// value === '': Unset Referer
|
||||
&& value !== '') {
|
||||
// gettext_config:{"id":"referer-cannot-be-undefined"}
|
||||
return 'Referer 不可為 undefined。';
|
||||
}
|
||||
library_namespace.debug({
|
||||
// gettext_config:{"id":"configure-referer-$1"}
|
||||
T : [ '設定 Referer:%1', JSON.stringify(value) ]
|
||||
}, 2);
|
||||
this.get_URL_options.headers.Referer = value;
|
||||
// console.log(this.get_URL_options);
|
||||
return;
|
||||
|
||||
case 'allow_EOI_error':
|
||||
if (this.using_default_MIN_LENGTH) {
|
||||
this[key] = value;
|
||||
// 因為 .allow_EOI_error 會影響到 .MIN_LENGTH
|
||||
this.setup_value('MIN_LENGTH', 'default');
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'MIN_LENGTH':
|
||||
// 設定預設可容許的最小圖像大小。
|
||||
if (!(value >= 0)) {
|
||||
if (value === 'default') {
|
||||
this.using_default_MIN_LENGTH = true;
|
||||
value = this.allow_EOI_error ? 4e3 : 1e3;
|
||||
} else
|
||||
// gettext_config:{"id":"min-image-size-should-be-greater-than-0"}
|
||||
return '最小圖片大小應大於等於零';
|
||||
} else {
|
||||
delete this.using_default_MIN_LENGTH;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'main_directory':
|
||||
if (!value || typeof value !== 'string')
|
||||
return;
|
||||
value = value.replace(/[\\\/]/g,
|
||||
// 正規化成當前作業系統使用的目錄分隔符號。
|
||||
library_namespace.env.path_separator);
|
||||
// main_directory 必須以 path separator 作結。
|
||||
value = library_namespace.append_path_separator(value);
|
||||
break;
|
||||
}
|
||||
|
||||
if (key in this.import_arg_hash) {
|
||||
this.verify_arg(key, value);
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
// delete this[key];
|
||||
}
|
||||
this[key] = value;
|
||||
}
|
||||
|
||||
// import command line arguments 以命令行參數為準
|
||||
// 從命令列引數來的設定,優先等級比起作品預設設定更高。
|
||||
function import_args() {
|
||||
// console.log(library_namespace.env.arg_hash);
|
||||
if (!library_namespace.env.arg_hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ( var key in library_namespace.env.arg_hash) {
|
||||
if (!(key in this.import_arg_hash) && !(key in this)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = library_namespace.env.arg_hash[key];
|
||||
|
||||
if (this.import_arg_hash[key] === 'number') {
|
||||
try {
|
||||
// value = +value;
|
||||
// 這樣可以處理如"1e3"
|
||||
value = JSON.parse(value);
|
||||
} catch (e) {
|
||||
library_namespace.error('import_args: '
|
||||
// gettext_config:{"id":"cannot-parse-$1"}
|
||||
+ gettext('無法解析 %1', key + '=' + value));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var old_value = this[key], error = this.setup_value(key, value);
|
||||
if (error) {
|
||||
library_namespace.error('import_args: '
|
||||
// gettext_config:{"id":"unable-to-set-$1-$2"}
|
||||
+ gettext('無法設定 %1:%2', key + '=' + old_value, error));
|
||||
} else {
|
||||
library_namespace.log(library_namespace.display_align([
|
||||
[ key + ': ', old_value ],
|
||||
// + ' ': 增加間隙。
|
||||
// gettext_config:{"id":"from-command-line"}
|
||||
[ gettext('由命令列') + ' → ', value ] ]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// export 導出.
|
||||
|
||||
// @instance
|
||||
Object.assign(Work_crawler.prototype, {
|
||||
verify_arg : verify_arg,
|
||||
setup_value : setup_value,
|
||||
import_args : import_args,
|
||||
// 數值規範。命令列可以設定的選項之型態資料集。通常僅做測試微調用。
|
||||
// GUI 選項於 work_crawler/gui_electron/gui_electron_functions.js 設定。
|
||||
// 以純量為主,例如邏輯真假、數字、字串。無法處理函數!
|
||||
// 現在 import_arg_hash 之說明已與 I18n 統合在一起。
|
||||
// work_crawler/work_crawler_loader.js與gui_electron_functions.js各參考了import_arg_hash的可選參數。
|
||||
// @see work_crawler/gui_electron/gui_electron_functions.js
|
||||
// @see work_crawler/resource/locale of work_crawler - locale.csv
|
||||
|
||||
// gettext_config:{"id":"number","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"function","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"boolean","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"string","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"fso_file","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"fso_files","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"fso_directory","mark_type":"combination_message_id"}
|
||||
// gettext_config:{"id":"fso_directories","mark_type":"combination_message_id"}
|
||||
import_arg_hash : {
|
||||
// 預設值設定於 Work_crawler_prototype @ CeL.application.net.work_crawler
|
||||
|
||||
// set download directory, fso:directory
|
||||
main_directory : 'string:fso_directory',
|
||||
|
||||
// crawler.show_work_data(work_data);
|
||||
show_information_only : 'boolean',
|
||||
|
||||
one_by_one : 'boolean',
|
||||
// 篩選想要下載的章節標題關鍵字。例如"單行本"。
|
||||
chapter_filter : 'string',
|
||||
// 開始/接續下載的章節。將依類型轉成 .start_chapter_title 或
|
||||
// .start_chapter_NO。對已下載過的章節,必須配合 .recheck。
|
||||
start_chapter : 'number:natural|string',
|
||||
// 開始/接續下載的章節編號。
|
||||
start_chapter_NO : 'number:natural',
|
||||
// 下載此章節編號範圍。例如 "20-30,50-60"。
|
||||
chapter_NO_range : 'string',
|
||||
// 開始/接續下載的章節標題。
|
||||
start_chapter_title : 'string',
|
||||
// 指定了要開始下載的列表序號。將會跳過這個訊號之前的作品。
|
||||
// 一般僅使用於命令列設定。default:1
|
||||
start_list_serial : 'number:natural|string',
|
||||
// 重新整理列表檔案 rearrange list file
|
||||
rearrange_list_file : 'boolean',
|
||||
// string: 如 "3s"
|
||||
chapter_time_interval : 'number:natural+0|string|function',
|
||||
MIN_LENGTH : 'number:natural+0',
|
||||
timeout : 'number:natural+0|string',
|
||||
// 容許錯誤用的相關操作設定。
|
||||
MAX_ERROR_RETRY : 'number:natural+0',
|
||||
allow_EOI_error : 'boolean',
|
||||
skip_error : 'boolean',
|
||||
skip_chapter_data_error : 'boolean',
|
||||
|
||||
directory_name_pattern : 'string',
|
||||
|
||||
preserve_work_page : 'boolean',
|
||||
preserve_chapter_page : 'boolean',
|
||||
remove_ebook_directory : 'boolean',
|
||||
// 當新獲取的檔案比較大時,覆寫舊的檔案。
|
||||
overwrite_old_file : 'boolean',
|
||||
vertical_writing : 'boolean|string',
|
||||
// RTL_writing : 'boolean',
|
||||
convert_to_language : 'string:TW;CN',
|
||||
// 不解開原電子書的選項: 就算存在舊電子書檔案,也不解壓縮、利用舊資料。
|
||||
discard_old_ebook_file : 'boolean',
|
||||
|
||||
user_agent : 'string',
|
||||
// 代理伺服器 proxy_server: "username:password@hostname:port"
|
||||
proxy : 'string',
|
||||
// 設定下載時要添加的 cookie。 document.cookie: "key=value"
|
||||
cookie : 'string',
|
||||
|
||||
// 可接受的圖片類別(延伸檔名)。以 "|" 字元作分隔,如 "webp|jpg|png"。未設定將不作檢查。
|
||||
// 輸入 "images" 表示接受所有圖片。
|
||||
acceptable_types : 'string',
|
||||
// 漫畫下載完畢後壓縮圖片檔案。
|
||||
archive_images : 'boolean',
|
||||
// 完全沒有出現錯誤才壓縮圖片檔案。
|
||||
archive_all_good_images_only : 'boolean',
|
||||
// 壓縮圖片檔案之後,刪掉原先的圖片檔案。
|
||||
remove_images_after_archive : 'boolean',
|
||||
images_archive_extension : 'string',
|
||||
|
||||
// 重新擷取用的相關操作設定。
|
||||
regenerate : 'boolean',
|
||||
reget_chapter : 'boolean',
|
||||
recheck : 'boolean|string:changed;multi_parts_changed',
|
||||
search_again : 'boolean',
|
||||
cache_title_to_id : 'boolean',
|
||||
|
||||
write_chapter_metadata : 'boolean',
|
||||
write_image_metadata : 'boolean',
|
||||
|
||||
// 封存舊作品。
|
||||
archive_old_works : 'boolean|string',
|
||||
// 以作品完結時間為分界來封存舊作品。預設為最後一次下載時間。
|
||||
use_finished_date_to_archive_old_works : 'boolean',
|
||||
// 同時自作品列表中刪除將封存之作品。
|
||||
modify_work_list_when_archive_old_works : 'boolean',
|
||||
|
||||
// 儲存偏好選項 save_options。
|
||||
save_preference : 'boolean'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
setup_argument_conditions();
|
||||
|
||||
// 不設定(hook)本 module 之 namespace,僅執行 module code。
|
||||
return library_namespace.env.not_to_extend_keyword;
|
||||
}
|
1998
app/node_modules/cejs/application/net/work_crawler/chapter.js
generated
vendored
1998
app/node_modules/cejs/application/net/work_crawler/chapter.js
generated
vendored
File diff suppressed because it is too large
Load Diff
1028
app/node_modules/cejs/application/net/work_crawler/ebook.js
generated
vendored
1028
app/node_modules/cejs/application/net/work_crawler/ebook.js
generated
vendored
File diff suppressed because it is too large
Load Diff
665
app/node_modules/cejs/application/net/work_crawler/image.js
generated
vendored
665
app/node_modules/cejs/application/net/work_crawler/image.js
generated
vendored
@@ -1,665 +0,0 @@
|
||||
/**
|
||||
* @name WWW work crawler sub-functions
|
||||
*
|
||||
* @fileoverview WWW work crawler functions: part of image
|
||||
*
|
||||
* @since 2019/10/13 拆分自 CeL.application.net.work_crawler
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
if (typeof CeL === 'function') {
|
||||
// 忽略沒有 Windows Component Object Model 的錯誤。
|
||||
CeL.env.ignore_COM_error = true;
|
||||
|
||||
CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.image',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : 'this,*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
}
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
var Work_crawler = library_namespace.net.work_crawler, crawler_namespace = Work_crawler.crawler_namespace;
|
||||
|
||||
var gettext = library_namespace.locale.gettext,
|
||||
/** node.js file system module */
|
||||
node_fs = library_namespace.platform.nodejs && require('fs');
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function image_path_to_url(path, server) {
|
||||
if (path.includes('://')) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (!server.includes('://')) {
|
||||
// this.get_URL_options.headers.Host = server;
|
||||
server = 'http://' + server;
|
||||
}
|
||||
return server + path;
|
||||
}
|
||||
|
||||
function EOI_error_path(path, XMLHttp) {
|
||||
return path.replace(/(\.[^.]*)$/, this.EOI_error_postfix
|
||||
// + (XMLHttp && XMLHttp.status ? ' ' + XMLHttp.status : '')
|
||||
+ '$1');
|
||||
}
|
||||
|
||||
// 下載單一個圖片。
|
||||
// callback(image_data, status)
|
||||
function get_image(image_data, callback, images_archive) {
|
||||
// console.log(image_data);
|
||||
if (!image_data || !image_data.file || !image_data.url) {
|
||||
if (image_data) {
|
||||
image_data.has_error = true;
|
||||
image_data.done = true;
|
||||
}
|
||||
// 注意: 此時 image_data 可能是 undefined
|
||||
if (this.skip_error) {
|
||||
// gettext_config:{"id":"unspecified-image-data"}
|
||||
this.onwarning(gettext('未指定圖片資料'), image_data);
|
||||
} else {
|
||||
// gettext_config:{"id":"unspecified-image-data"}
|
||||
this.onerror(gettext('未指定圖片資料'), image_data);
|
||||
}
|
||||
if (typeof callback === 'function')
|
||||
callback(image_data, 'invalid_data');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 每張圖片都要檢查實際存在的圖片檔案。當之前已存在完整的圖片時,就不再嘗試下載圖片。<br />
|
||||
* 工作機制:<br />
|
||||
* 檢核`image_data.file`是否存在。`image_data.file`由圖片的網址URL來判別可能的延伸檔名。猜不出的會採用預設的圖片延伸檔名/副檔名.default_image_extension。
|
||||
*
|
||||
* @see function process_images() @ CeL.application.net.work_crawler.chapter
|
||||
*
|
||||
* 若`image_data.file`不存在,將會檢核所有可接受的圖片類別副檔名(.acceptable_types)。
|
||||
* 每張圖片都要檢核所有可接受的圖片類別,會加大硬碟讀取負擔。 會用到 .overwrite_old_file 這個選項的,應該都是需要提報
|
||||
* issue 的,因此這個選項不會列出來。麻煩請在個別網站遇到此情況時提報 issue,列出作品名稱以及圖片類別,以供這邊確認圖片類別。
|
||||
* 只要存在完整無損害的預設圖片類別或是可接受的圖片類別,就直接跳出,不再嘗試下載這張圖片。否則會重新下載圖片。
|
||||
* 當下載的圖片以之前的圖片更大時,就會覆蓋原先的圖片。
|
||||
* 若下載的圖片類別並非預設的圖片類別(.default_image_extension),例如預設 JPG 但得到 PNG
|
||||
* 檔案時,會將副檔名改為實際得到的圖像格式。因此下一次下載時,需要設定 .acceptable_types 才能找得到圖片。
|
||||
*/
|
||||
var image_downloaded = node_fs.existsSync(image_data.file)
|
||||
|| this.skip_existed_bad_file
|
||||
// 檢查是否已有上次下載失敗,例如 server 上本身就已經出錯的檔案。
|
||||
&& node_fs.existsSync(this.EOI_error_path(image_data.file)), acceptable_types;
|
||||
|
||||
if (!image_downloaded) {
|
||||
// 正規化 acceptable_types
|
||||
acceptable_types = image_data.acceptable_types
|
||||
|| this.acceptable_types;
|
||||
if (!acceptable_types) {
|
||||
// 未設定將不作檢查。
|
||||
} else if (typeof acceptable_types === 'string') {
|
||||
acceptable_types = acceptable_types.trim();
|
||||
if (acceptable_types === 'images') {
|
||||
// 將會測試是否已經下載過一切可接受的檔案類別。
|
||||
acceptable_types = Object.keys(this.image_types);
|
||||
} else {
|
||||
acceptable_types = acceptable_types.split('|');
|
||||
}
|
||||
} else if (!Array.isArray(acceptable_types)) {
|
||||
library_namespace.warn({
|
||||
// gettext_config:{"id":"invalid-acceptable_types-$1"}
|
||||
T : [ 'Invalid acceptable_types: %1', acceptable_types ]
|
||||
});
|
||||
acceptable_types = null;
|
||||
}
|
||||
|
||||
if (acceptable_types) {
|
||||
// 檢核所有可接受的圖片類別(.acceptable_types)。
|
||||
image_downloaded = acceptable_types.some(function(extension) {
|
||||
var alternative_filename = image_data.file.replace(
|
||||
/\.[a-z\d]+$/, '.' + extension);
|
||||
if (node_fs.existsSync(alternative_filename)) {
|
||||
image_data.file = alternative_filename;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 檢查壓縮檔裡面的圖片檔案。
|
||||
var image_archived, bad_image_archived;
|
||||
if (images_archive && images_archive.fso_path_hash
|
||||
// 檢查壓縮檔,看是否已經存在圖像檔案。
|
||||
&& image_data.file.startsWith(images_archive.work_directory)) {
|
||||
image_archived = image_data.file
|
||||
.slice(images_archive.work_directory.length);
|
||||
bad_image_archived = images_archive.fso_path_hash[this
|
||||
.EOI_error_path(image_archived)];
|
||||
if (image_archived && bad_image_archived) {
|
||||
images_archive.to_remove.push(bad_image_archived);
|
||||
}
|
||||
|
||||
if (false) {
|
||||
console.log([ images_archive.fso_path_hash, acceptable_types,
|
||||
image_archived,
|
||||
images_archive.fso_path_hash[image_archived] ]);
|
||||
}
|
||||
image_downloaded = image_downloaded
|
||||
|| images_archive.fso_path_hash[image_archived]
|
||||
|| this.skip_existed_bad_file
|
||||
// 檢查是否已有上次下載失敗,例如 server 上本身就已經出錯的檔案。
|
||||
&& bad_image_archived;
|
||||
|
||||
if (!image_downloaded
|
||||
// 可以接受的圖片類別/圖片延伸檔名/副檔名/檔案類別 acceptable file extensions
|
||||
&& acceptable_types) {
|
||||
image_downloaded = acceptable_types.some(function(extension) {
|
||||
var alternative_filename = image_archived.replace(
|
||||
/\.[a-z\d]+$/, '.' + extension);
|
||||
return images_archive.fso_path_hash[alternative_filename];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (image_downloaded) {
|
||||
// console.log('get_image: Skip ' + image_data.file);
|
||||
image_data.done = true;
|
||||
if (typeof callback === 'function')
|
||||
callback(image_data, 'image_downloaded');
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
var _this = this,
|
||||
// 漫畫圖片的 URL。
|
||||
image_url = image_data.url, server = this.server_list;
|
||||
if (server) {
|
||||
server = server[server.length * Math.random() | 0];
|
||||
image_url = this.image_path_to_url(image_url, server, image_data);
|
||||
} else {
|
||||
image_url = this.full_URL(image_url);
|
||||
}
|
||||
image_data.parsed_url = image_url;
|
||||
if (!crawler_namespace.PATTERN_non_CJK.test(image_url)) {
|
||||
// 工具檔應先編碼URL。
|
||||
library_namespace.warn({
|
||||
// gettext_config:{"id":"invalid-url-must-encode-first-$1"}
|
||||
T : [ '必須先將URL編碼:%1', image_url ]
|
||||
});
|
||||
// console.trace(image_url);
|
||||
if (!/%[\dA-F]{2}/i.test(image_url))
|
||||
image_url = encodeURI(image_url);
|
||||
}
|
||||
|
||||
if (!image_data.file_length) {
|
||||
image_data.file_length = [];
|
||||
}
|
||||
// console.log('get_image: image_url: ' + image_url);
|
||||
// library_namespace.set_debug(3);
|
||||
|
||||
this.get_URL(image_url, function(XMLHttp) {
|
||||
// console.trace(XMLHttp.status);
|
||||
// console.log(image_data);
|
||||
if (image_data.url !== XMLHttp.responseURL) {
|
||||
// 紀錄最後實際下載的圖片網址。
|
||||
image_data.responseURL = XMLHttp.responseURL;
|
||||
}
|
||||
|
||||
/** {Buffer}圖片數據的內容。 */
|
||||
var contents = XMLHttp.buffer;
|
||||
|
||||
// 修正圖片結尾 tail 非正規格式之情況。
|
||||
// TODO: 應該檢測刪掉後是正確的圖片檔,才刪掉 trailing new line。
|
||||
if (_this.trim_trailing_newline && contents && contents.length > 4
|
||||
// 去掉最後的換行符號:有些圖片在檔案最後會添加上換行符號 "\r\n",因此被判別為非正規圖片檔。
|
||||
&& contents.at(-2) === 0x0D && contents.at(-1) === 0x0A) {
|
||||
contents = contents.slice(0, -2);
|
||||
}
|
||||
|
||||
if (_this.image_preprocessor) {
|
||||
// 圖片前處理程序 預處理器 image pre-processing
|
||||
// 例如修正圖片結尾非正規格式之情況。
|
||||
// 必須自行確保不會 throw,需檢查 contents 是否非 {Buffer}。
|
||||
try {
|
||||
contents = _this.image_preprocessor(contents, image_data);
|
||||
} catch (e) {
|
||||
has_error = has_error || e;
|
||||
}
|
||||
// if _this.image_preprocessor() returns `false`,
|
||||
// will treat as bad file.
|
||||
if (contents === undefined)
|
||||
contents = XMLHttp.buffer;
|
||||
}
|
||||
|
||||
var has_error = !contents || !(contents.length >= _this.MIN_LENGTH)
|
||||
|| (XMLHttp.status / 100 | 0) !== 2, verified_image;
|
||||
|
||||
// console.trace([ image_url, XMLHttp.responseURL ]);
|
||||
if (!image_data.is_bad
|
||||
// image_data.is_bad may be set by _this.image_preprocessor()
|
||||
&& typeof _this.is_limited_image_url === 'function') {
|
||||
// 處理特殊圖片: 檢查是否下載到 padding 用的 404 檔案。
|
||||
image_data.is_bad = _this.is_limited_image_url(
|
||||
XMLHttp.responseURL, image_data);
|
||||
if (!image_data.is_bad)
|
||||
delete image_data.is_bad;
|
||||
else if (image_data.is_bad === true)
|
||||
image_data.is_bad = 'is limited image';
|
||||
}
|
||||
|
||||
if (!has_error) {
|
||||
image_data.file_length.push(contents.length);
|
||||
library_namespace.debug({
|
||||
// gettext_config:{"id":"completed-image-testing-$1"}
|
||||
T : [ '測試圖片是否完整:%1', image_data.file ]
|
||||
}, 2, 'get_image');
|
||||
var file_type = library_namespace.file_type(contents);
|
||||
verified_image = file_type && !file_type.damaged;
|
||||
if (verified_image) {
|
||||
// console.log(_this.image_types);
|
||||
if (!file_type.extensions
|
||||
//
|
||||
|| !file_type.extensions.some(function(extension) {
|
||||
return extension in _this.image_types;
|
||||
})) {
|
||||
verified_image = false;
|
||||
library_namespace
|
||||
.warn({
|
||||
T : [
|
||||
// gettext_config:{"id":"unable-to-process-image-file-of-type-$2-$1"}
|
||||
file_type.type ? '無法處理類型為 %2 之圖片檔:%1'
|
||||
// gettext_config:{"id":"unable-to-determine-the-type-of-image-file-$1"}
|
||||
: '無法判別圖片檔之類型:%1', image_data.file,
|
||||
file_type.type ]
|
||||
});
|
||||
}
|
||||
|
||||
var original_extension
|
||||
//
|
||||
= image_data.file.match(/[^.]*$/)[0].toLowerCase();
|
||||
if (file_type.extensions ?
|
||||
//
|
||||
!image_data.file.endsWith('.' + file_type.extension)
|
||||
// accept '.jpeg' as alias of '.jpg'
|
||||
&& !file_type.extensions.includes(original_extension)
|
||||
// 無法判別圖片檔類型時,若原副檔名為圖片檔案類別則採用之。
|
||||
: !(original_extension in _this.image_types)) {
|
||||
// 依照所驗證的檔案格式改變副檔名。
|
||||
image_data.file = image_data.file.replace(/[^.]+$/,
|
||||
// e.g. .png
|
||||
file_type.extension
|
||||
// 若是沒有辦法判別延伸檔名,那麼就採用預設的圖片延伸檔名。
|
||||
|| _this.default_image_extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
// verified_image===true 則必然(!!has_error===false)
|
||||
// has_error表示下載過程發生錯誤,光是檔案損毀,不會被當作has_error!
|
||||
// has_error則必然(!!verified_image===false)
|
||||
|
||||
if (false) {
|
||||
console.log([ _this.skip_error, _this.MAX_ERROR_RETRY,
|
||||
//
|
||||
_this.MIN_LENGTH, has_error, _this.skip_error
|
||||
//
|
||||
&& image_data.error_count === _this.MAX_ERROR_RETRY ]);
|
||||
// 出錯次數
|
||||
library_namespace.log({
|
||||
// gettext_config:{"id":"number-of-errors-$1"}
|
||||
T : [ '{{PLURAL:%1|%1}} 次錯誤', image_data.error_count ]
|
||||
});
|
||||
}
|
||||
if (verified_image || image_data.is_bad || _this.skip_error
|
||||
// 有出問題的話,最起碼都需retry足夠次數。
|
||||
&& image_data.error_count === _this.MAX_ERROR_RETRY
|
||||
//
|
||||
|| _this.allow_EOI_error
|
||||
//
|
||||
&& image_data.file_length.length > _this.MAX_EOI_ERROR) {
|
||||
// console.log(image_data.file_length);
|
||||
if (verified_image || image_data.is_bad || _this.skip_error
|
||||
// skip error 的話,不管有沒有下載/獲取過檔案(包括404圖像不存在),依然 pass。
|
||||
// && image_data.file_length.length === 0
|
||||
//
|
||||
|| image_data.file_length.cardinal_1()
|
||||
// ↑ 若是每次都得到相同的檔案長度,那就當作來源檔案本來就有問題。
|
||||
&& (_this.skip_error || _this.allow_EOI_error
|
||||
//
|
||||
&& image_data.file_length.length > _this.MAX_EOI_ERROR)) {
|
||||
// 圖片下載過程結束,不再嘗試下載圖片:要不是過關,要不就是錯誤太多次了。
|
||||
var bad_file_path = _this.EOI_error_path(image_data.file,
|
||||
XMLHttp);
|
||||
if (has_error || image_data.is_bad
|
||||
|| verified_image === false) {
|
||||
image_data.file = bad_file_path;
|
||||
image_data.has_error = true;
|
||||
if (_this.preserve_bad_image) {
|
||||
library_namespace.warn([ {
|
||||
T : has_error ? contents
|
||||
// gettext_config:{"id":"force-non-image-files-to-be-saved-as-images"}
|
||||
? '強制將非圖片檔儲存為圖片。'
|
||||
// gettext_config:{"id":"force-empty-content-to-be-saved-as-an-image"}
|
||||
: '強制將空內容儲存為圖片。'
|
||||
// assert: (!!verified_image===false)
|
||||
// 圖檔損壞: e.g., Do not has EOI
|
||||
// gettext_config:{"id":"force-storage-of-damaged-image"}
|
||||
: '強制儲存損壞的圖片。'
|
||||
}, XMLHttp.status
|
||||
// 狀態碼正常就不顯示。
|
||||
&& (XMLHttp.status / 100 | 0) !== 2 ? {
|
||||
// gettext_config:{"id":"http-status-code-$1"}
|
||||
T : [ 'HTTP status code %1.', XMLHttp.status ]
|
||||
} : '',
|
||||
// 顯示 crawler 程式指定的錯誤。
|
||||
image_data.is_bad ? {
|
||||
// gettext_config:{"id":"error-$1"}
|
||||
T : [ 'Error: %1.', image_data.is_bad ]
|
||||
} : '', contents ? {
|
||||
// gettext_config:{"id":"file-size-$1"}
|
||||
T : [ 'File size: %1.',
|
||||
//
|
||||
CeL.to_KiB(contents.length) ]
|
||||
} : '',
|
||||
//
|
||||
': ' + image_data.file + '\n← ' + image_url ]);
|
||||
}
|
||||
if (!contents
|
||||
// 404之類,就算有內容,也不過是錯誤訊息頁面。
|
||||
|| (XMLHttp.status / 100 | 0) === 4) {
|
||||
contents = '';
|
||||
}
|
||||
} else {
|
||||
// pass, 過關了。
|
||||
if (node_fs.existsSync(bad_file_path)) {
|
||||
library_namespace.info({
|
||||
// gettext_config:{"id":"delete-corrupted-old-image-file-$1"}
|
||||
T : [ '刪除損壞的舊圖片檔:%1', bad_file_path ]
|
||||
});
|
||||
library_namespace.fs_remove(bad_file_path);
|
||||
}
|
||||
if (bad_image_archived) {
|
||||
// 登記壓縮檔內可以刪除的損壞圖檔。
|
||||
images_archive.to_remove.push(bad_image_archived);
|
||||
}
|
||||
}
|
||||
|
||||
var old_file_status, old_archived_file =
|
||||
// image_data.has_error?bad_image_archived:image_archived
|
||||
image_archived || bad_image_archived;
|
||||
try {
|
||||
old_file_status = node_fs.statSync(image_data.file);
|
||||
} catch (e) {
|
||||
// old/bad file not exist
|
||||
}
|
||||
|
||||
if (old_archived_file && (!old_file_status
|
||||
//
|
||||
|| old_archived_file.size > old_file_status.size)) {
|
||||
// 壓縮檔內的圖像質量更好的情況,那就採用壓縮檔的。
|
||||
if (old_file_status
|
||||
&& old_archived_file.size < contents.length) {
|
||||
library_namespace.warn({
|
||||
T : [ _this.archive_images
|
||||
// gettext_config:{"id":"the-quality-of-the-image-in-the-archive-is-better-than-in-the-directory-but-will-be-overwritten-after-downloading-$1"}
|
||||
? '壓縮檔內的圖片品質比目錄中的更好,但在下載完後將可能在壓縮時被覆蓋:%1'
|
||||
// gettext_config:{"id":"the-quality-of-the-image-in-the-archive-is-better-than-in-the-directory-$1"}
|
||||
: '壓縮檔內的圖片品質比目錄中的更好:%1',
|
||||
//
|
||||
old_archived_file.path ]
|
||||
});
|
||||
}
|
||||
|
||||
old_file_status = old_archived_file;
|
||||
}
|
||||
|
||||
if (!old_file_status || _this.overwrite_old_file
|
||||
// 得到更大的檔案,寫入更大的檔案。
|
||||
&& !(old_file_status.size >= contents.length)) {
|
||||
if (_this.image_post_processor) {
|
||||
// 圖片後處理程序 image post-processing
|
||||
contents = _this.image_post_processor(contents,
|
||||
image_data
|
||||
// , images_archive
|
||||
)
|
||||
|| contents;
|
||||
}
|
||||
|
||||
if (!image_data.has_error || _this.preserve_bad_image) {
|
||||
library_namespace.debug({
|
||||
// gettext_config:{"id":"save-image-data-to-your-hard-drive-$1"}
|
||||
T : [ '保存圖片數據到硬碟上:%1', image_data.file ]
|
||||
}, 1, 'get_image');
|
||||
// TODO: 檢查舊的檔案是不是文字檔。例如有沒有包含 HTML 標籤。
|
||||
try {
|
||||
node_fs
|
||||
.writeFileSync(image_data.file,
|
||||
contents);
|
||||
} catch (e) {
|
||||
library_namespace.error(e);
|
||||
// gettext_config:{"id":"unable-to-write-to-image-file-$1"}
|
||||
var message = [ gettext('無法寫入圖片檔案 [%1]。',
|
||||
image_data.file) ];
|
||||
if (e.code === 'ENOENT') {
|
||||
message.push(gettext(
|
||||
// TODO: show chapter_directory 當前作品章節目錄:
|
||||
// gettext_config:{"id":"it-may-be-because-the-download-directory-of-the-work-has-changed-and-the-cache-data-points-to-the-old-location-that-does-not-exist"}
|
||||
'可能因為作品下載目錄改變了,而快取資料指向不存在的舊位置。'));
|
||||
} else {
|
||||
message.push(gettext(
|
||||
// gettext_config:{"id":"it-may-be-because-the-work-information-cache-is-different-from-the-structure-of-the-work-chapter-on-the-current-website"}
|
||||
'可能因為作品資訊快取與當前網站上之作品章節結構不同。'));
|
||||
}
|
||||
message.push(gettext(
|
||||
// https://github.com/kanasimi/work_crawler/issues/278
|
||||
// gettext_config:{"id":"if-you-have-downloaded-this-work-before-please-save-the-original-work-catalog-or-rename-the-work-cache-file-(the-work-id.json-under-the-work-directory)-and-try-the-new-download"}
|
||||
'若您之前曾經下載過本作品的話,請封存原有作品目錄,或將作品資訊快取檔(作品目錄下的 作品id.json)改名之後嘗試全新下載。'
|
||||
//
|
||||
));
|
||||
_this.onerror(message.join('\n'), image_data);
|
||||
if (typeof callback === 'function') {
|
||||
callback(image_data,
|
||||
'image_file_write_error');
|
||||
}
|
||||
return Work_crawler.THROWED;
|
||||
}
|
||||
}
|
||||
} else if (old_file_status
|
||||
&& old_file_status.size > contents.length) {
|
||||
library_namespace.log({
|
||||
T : [
|
||||
// gettext_config:{"id":"there-is-a-large-old-file-($2)-that-will-not-be-overwritten-$1"}
|
||||
'存在較大的舊檔 (%2),將不覆蓋:%1',
|
||||
image_data.file,
|
||||
old_file_status.size + '>'
|
||||
+ contents.length ]
|
||||
});
|
||||
}
|
||||
image_data.done = true;
|
||||
if (typeof callback === 'function')
|
||||
callback(image_data/* , 'OK' */);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 有錯誤。下載圖像錯誤時報錯。
|
||||
var message;
|
||||
if (verified_image === false) {
|
||||
// 圖檔損壞: e.g., Do not has EOI
|
||||
message = [ {
|
||||
// gettext_config:{"id":"image-damaged"}
|
||||
T : '圖檔損壞:'
|
||||
} ];
|
||||
} else {
|
||||
// 圖檔沒資格驗證。
|
||||
message = [ {
|
||||
// gettext_config:{"id":"failed-to-get-image"}
|
||||
T : '無法取得圖片。'
|
||||
}, XMLHttp.status ? {
|
||||
// gettext_config:{"id":"http-status-code-$1"}
|
||||
T : [ 'HTTP status code %1.', XMLHttp.status ]
|
||||
} : '', {
|
||||
// gettext_config:{"id":"image-without-content"}
|
||||
T : !contents ? '圖片無內容:' : [
|
||||
//
|
||||
contents.length < _this.MIN_LENGTH
|
||||
// gettext_config:{"id":"$1-bytes-too-small"}
|
||||
? '檔案過小,僅 %1 {{PLURAL:%1|位元組}}:'
|
||||
// gettext_config:{"id":"$1-bytes"}
|
||||
: '檔案僅 %1 {{PLURAL:%1|位元組}}:', contents.length ]
|
||||
} ];
|
||||
}
|
||||
message.push(image_url + '\n→ ' + image_data.file);
|
||||
library_namespace.warn(message);
|
||||
// Release memory. 釋放被占用的記憶體。
|
||||
message = null;
|
||||
|
||||
if (image_data.error_count === _this.MAX_ERROR_RETRY) {
|
||||
image_data.has_error = true;
|
||||
// throw new Error(_this.id + ': ' +
|
||||
// gettext('MESSAGE_NEED_RE_DOWNLOAD'));
|
||||
library_namespace.log(_this.id + ': '
|
||||
// gettext_config:{"id":"message_need_re_download"}
|
||||
+ gettext('MESSAGE_NEED_RE_DOWNLOAD'));
|
||||
// console.log('error count: ' + image_data.error_count);
|
||||
if (contents && contents.length > 10
|
||||
//
|
||||
&& contents.length < _this.MIN_LENGTH
|
||||
// 檔案有驗證過,只是太小時,應該不是 false。
|
||||
&& verified_image !== false
|
||||
// 就算圖像是完整的,只是比較小,HTTP status code 也應該是 2xx。
|
||||
&& (XMLHttp.status / 100 | 0) === 2) {
|
||||
library_namespace.warn([ {
|
||||
// gettext_config:{"id":"perhaps-the-image-is-complete-just-too-small-and-not-up-to-standard-such-as-an-almost-blank-image"}
|
||||
T : '或許圖片是完整的,只是過小而未達標,例如幾乎為空白之圖片。'
|
||||
}, {
|
||||
// gettext_config:{"id":"work_crawler-skip-image-error-prompt"}
|
||||
T : [ 'work_crawler-skip-image-error-prompt',
|
||||
//
|
||||
contents.length,
|
||||
//
|
||||
JSON.stringify(_this.EOI_error_postfix) ]
|
||||
} ]);
|
||||
|
||||
} else if (image_data.file_length.length > 1
|
||||
&& !image_data.file_length.cardinal_1()) {
|
||||
library_namespace.warn([ {
|
||||
// gettext_config:{"id":"the-downloaded-image-is-different-in-size-$1"}
|
||||
T : [ '下載所得的圖片大小不同:%1。', image_data.file_length ]
|
||||
}, {
|
||||
// gettext_config:{"id":"if-it-is-not-because-the-website-cuts-off-the-connection-early-then-you-may-need-to-increase-the-time-limit-to-provide-enough-time-to-download-the-image"}
|
||||
T : '若非因網站提早截斷連線,那麼您或許需要增長時限來提供足夠的時間下載圖片?'
|
||||
} ]);
|
||||
// TODO: 提供續傳功能。
|
||||
// e.g., for 9mdm.js→dagu.js 魔剑王 第59话 4392-59-011.jpg
|
||||
|
||||
} else if (!_this.skip_error) {
|
||||
library_namespace.info([ {
|
||||
// gettext_config:{"id":"if-the-error-persists-you-can-set-skip_error=true-to-ignore-the-image-error"}
|
||||
T : '若錯誤持續發生,您可以設定 skip_error=true 來忽略圖片錯誤。'
|
||||
}, {
|
||||
// gettext_config:{"id":"you-must-set-the-skip_error-or-allow_eoi_error-option-to-store-corrupted-files"}
|
||||
T : '您必須設定 skip_error 或 allow_EOI_error 選項,才會儲存損壞的檔案。'
|
||||
}, {
|
||||
// gettext_config:{"id":"if-you-need-to-re-download-the-section-that-failed-to-download-before-turn-on-the-recheck-option"}
|
||||
T : '若您需要重新下載之前下載失敗的章節,請開啟 recheck 選項。'
|
||||
} ]);
|
||||
}
|
||||
|
||||
// gettext_config:{"id":"failed-to-download-image"}
|
||||
_this.onerror(gettext('圖片下載錯誤'), image_data);
|
||||
// image_data.done = false;
|
||||
if (typeof callback === 'function')
|
||||
callback(image_data, 'image_download_error');
|
||||
return Work_crawler.THROWED;
|
||||
// 網頁介面不可使用process.exit(),會造成白屏
|
||||
// process.exit(1);
|
||||
}
|
||||
|
||||
image_data.error_count = (image_data.error_count | 0) + 1;
|
||||
library_namespace.log([ 'get_image: ', {
|
||||
// gettext_config:{"id":"retry-$1-$2"}
|
||||
T : [ 'Retry %1/%2',
|
||||
//
|
||||
image_data.error_count, _this.MAX_ERROR_RETRY ]
|
||||
}, '...' ]);
|
||||
var get_image_again = function() {
|
||||
_this.get_image(image_data, callback, images_archive);
|
||||
}
|
||||
if (image_data.time_interval > 0) {
|
||||
library_namespace.log_temporary([ 'get_image: ', {
|
||||
// gettext_config:{"id":"waiting-for-$2-and-retake-the-image-$1"}
|
||||
T : [ '等待 %2 之後再重新取得圖片:%1', image_data.url,
|
||||
//
|
||||
library_namespace.age_of(0, image_data.time_interval, {
|
||||
digits : 1
|
||||
}) ]
|
||||
} ]);
|
||||
setTimeout(get_image_again, image_data.time_interval);
|
||||
} else
|
||||
get_image_again();
|
||||
|
||||
}, null, Object.assign({
|
||||
/**
|
||||
* 最多平行下載/獲取檔案(圖片)的數量。
|
||||
*
|
||||
* <code>
|
||||
incase "MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 connect listeners added. Use emitter.setMaxListeners() to increase limit"
|
||||
</code>
|
||||
*/
|
||||
max_listeners : 300,
|
||||
fetch_type : 'image'
|
||||
}, this.get_URL_options, image_data.get_URL_options), 'buffer');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// export 導出.
|
||||
|
||||
// @instance
|
||||
Object.assign(Work_crawler.prototype, {
|
||||
// 可接受的圖片類別(延伸檔名)。以 "|" 字元作分隔,如 "webp|jpg|png"。未設定將不作檢查。輸入 "images"
|
||||
// 表示接受所有圖片。
|
||||
// 若下載的圖片不包含在指定類型中,則會視為錯誤。本工具只能下載特定幾種圖片類型。本選項僅供檢查圖片,非用來挑選想下載的圖片類型。
|
||||
// {Array|String}可以接受的圖片類別/圖片延伸檔名/副檔名/檔案類別 acceptable file extensions。
|
||||
// acceptable_types : 'images',
|
||||
// acceptable_types : 'png',
|
||||
// acceptable_types : 'webp|png',
|
||||
// acceptable_types : ['webp', 'png'],
|
||||
|
||||
// 當圖片不存在 EOI (end of image) 標記,或是被偵測出非圖片時,依舊強制儲存檔案。
|
||||
// allow image without EOI (end of image) mark. default:false
|
||||
// allow_EOI_error : true,
|
||||
// 圖片檔案下載失敗處理方式:忽略/跳過圖片錯誤。當404圖片不存在、檔案過小,或是被偵測出非圖片(如不具有EOI)時,依舊強制儲存檔案。default:false
|
||||
// skip_error : true,
|
||||
//
|
||||
// 若已經存在壞掉的圖片,就不再嘗試下載圖片。default:false
|
||||
// skip_existed_bad_file : true,
|
||||
//
|
||||
// 循序逐個、一個個下載圖片。僅對漫畫有用,對小說無用。小說章節皆為逐個下載。 Download images one by one.
|
||||
// default: 同時下載本章節中所有圖片。 Download ALL images at the same time.
|
||||
// 若設成{Natural}大於零的數字(ms)或{String}時間長度,那會當成下載每張圖片之時間間隔 time_interval。
|
||||
// cf. .chapter_time_interval
|
||||
// one_by_one : true,
|
||||
//
|
||||
// e.g., '2-1.jpg' → '2-1 bad.jpg'
|
||||
EOI_error_postfix : ' bad',
|
||||
// 加上有錯誤檔案之註記。
|
||||
EOI_error_path : EOI_error_path,
|
||||
|
||||
image_path_to_url : image_path_to_url,
|
||||
|
||||
get_image : get_image
|
||||
});
|
||||
|
||||
// 不設定(hook)本 module 之 namespace,僅執行 module code。
|
||||
return library_namespace.env.not_to_extend_keyword;
|
||||
}
|
206
app/node_modules/cejs/application/net/work_crawler/search.js
generated
vendored
206
app/node_modules/cejs/application/net/work_crawler/search.js
generated
vendored
@@ -1,206 +0,0 @@
|
||||
/**
|
||||
* @name WWW work crawler sub-functions
|
||||
*
|
||||
* @fileoverview WWW work crawler functions: part of search
|
||||
*
|
||||
* @since 2019/10/13 拆分自 CeL.application.net.work_crawler
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
if (typeof CeL === 'function') {
|
||||
// 忽略沒有 Windows Component Object Model 的錯誤。
|
||||
CeL.env.ignore_COM_error = true;
|
||||
|
||||
CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.search',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : 'this,*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
}
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
var Work_crawler = library_namespace.net.work_crawler, crawler_namespace = Work_crawler.crawler_namespace;
|
||||
|
||||
var gettext = library_namespace.locale.gettext;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// @see luoxia.js, dmzj.js
|
||||
function parse_search_result_token(id_list, id_data, token_parser, token) {
|
||||
var matched = token.match(/<a\s([^<>]+)>([\s\S]+?)<\/a>/i);
|
||||
if (library_namespace.is_RegExp(token_parser)) {
|
||||
matched = token.match(token_parser);
|
||||
} else {
|
||||
// matched: [ link, attributes, inner HTML ]
|
||||
matched = token.match(/<a\s([^<>]+)>([\s\S]+?)<\/a>/i);
|
||||
}
|
||||
if (!matched)
|
||||
return;
|
||||
|
||||
var id = matched[1]
|
||||
// dmzj.js: title=""href="" 中間沒有空格。
|
||||
.match(/href=["'][^"'<>]+?\/([a-z\d\-_]+)(?:\/|\.html)?["']/i);
|
||||
|
||||
if (!id)
|
||||
return;
|
||||
|
||||
id = id[1];
|
||||
if (false && !isNaN(id)) {
|
||||
id = +id;
|
||||
}
|
||||
id_list.push(id);
|
||||
|
||||
var title = matched[1].match(/title=["']([^"'<>]+)["']/);
|
||||
id_data.push(crawler_namespace.get_label(title && title[1]
|
||||
|| matched[2]));
|
||||
}
|
||||
|
||||
// only for .parse_search_result() !!
|
||||
function extract_work_id_from_search_result_link(PATTERN_item_token, html,
|
||||
token_parser) {
|
||||
// console.log(html);
|
||||
var matched,
|
||||
// {Array}id_list = [ id, id, ... ]
|
||||
id_list = [],
|
||||
// {Array}id_data = [ title, title, ... ]
|
||||
id_data = [],
|
||||
//
|
||||
search_result_parser = typeof token_parser === 'function'
|
||||
//
|
||||
&& function(token) {
|
||||
// function parser(token, id_list, id_data){console.log(token);}
|
||||
var result = token_parser.call(this, token, id_list, id_data);
|
||||
if (Array.isArray(result) && result.length === 2) {
|
||||
id_list.push(result[0]);
|
||||
id_data.push(result[1]);
|
||||
}
|
||||
};
|
||||
|
||||
// PATTERN_item_token 會分離出每個作品的欄位。
|
||||
if (library_namespace.is_RegExp(PATTERN_item_token)) {
|
||||
// assert: PATTERN_item_token.global === true
|
||||
// matched: [ , HTML token to check ]
|
||||
while (matched = PATTERN_item_token.exec(html)) {
|
||||
if (search_result_parser) {
|
||||
search_result_parser(matched[1]);
|
||||
} else {
|
||||
parse_search_result_token(id_list, id_data, token_parser,
|
||||
matched[1]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (Array.isArray(PATTERN_item_token)) {
|
||||
html.each_between(PATTERN_item_token[0], PATTERN_item_token[1],
|
||||
search_result_parser
|
||||
|| parse_search_result_token.bind(null, id_list,
|
||||
id_data, token_parser));
|
||||
|
||||
} else {
|
||||
throw new TypeError('extract_work_id_from_search_result_link: '
|
||||
// gettext_config:{"id":"invalid-token-pattern-{$1}-$2"}
|
||||
+ gettext('Invalid token pattern: {%1} %2',
|
||||
typeof PATTERN_item_token, JSON
|
||||
.stringify(PATTERN_item_token)));
|
||||
}
|
||||
|
||||
// console.log([ id_list, id_data ]);
|
||||
// throw 'extract_work_id_from_search_result_link';
|
||||
return [ id_list, id_data ];
|
||||
}
|
||||
|
||||
// CeL.work_crawler.extract_work_id_from_search_result_link()
|
||||
Work_crawler.extract_work_id_from_search_result_link = extract_work_id_from_search_result_link;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var PATTERN_url_for_baidu = /([\d_]+)(?:\.html|\/(?:index\.html)?)?$/;
|
||||
if (library_namespace.is_debug()) {
|
||||
[ 'http://www.host/0/123/', 'http://www.host/123/index.html',
|
||||
'http://www.host/123.html' ].forEach(function(url) {
|
||||
console.assert('123' === 'http://www.host/123/'
|
||||
.match(PATTERN_url_for_baidu)[1]);
|
||||
});
|
||||
}
|
||||
|
||||
crawler_namespace.parse_search_result_set = {
|
||||
// baidu cse
|
||||
baidu : function(html, get_label) {
|
||||
// console.log(html);
|
||||
var id_data = [],
|
||||
// {Array}id_list = [id,id,...]
|
||||
id_list = [], get_next_between = html.find_between(
|
||||
' cpos="title" href="', '</a>'), text;
|
||||
|
||||
while ((text = get_next_between()) !== undefined) {
|
||||
// console.log(text);
|
||||
// 從URL網址中解析出作品id。
|
||||
var matched = text.between(null, '"').match(
|
||||
PATTERN_url_for_baidu);
|
||||
// console.log(matched);
|
||||
if (!matched)
|
||||
continue;
|
||||
id_list.push(matched[1]);
|
||||
// 從URL網址中解析出作品title。
|
||||
matched = text.match(/ title="([^"]+)"/);
|
||||
if (matched) {
|
||||
matched = matched[1];
|
||||
} else {
|
||||
// e.g., omanhua.js: <em>择</em><em>天</em><em>记</em>
|
||||
matched = text.between('<em>', {
|
||||
tail : '</em>'
|
||||
});
|
||||
}
|
||||
// console.log(matched);
|
||||
if (matched && (matched = get_label(matched))
|
||||
// 只取第一個符合的。
|
||||
// 避免如 http://host/123/, http://host/123/456.htm
|
||||
&& !id_data.includes(matched)) {
|
||||
id_data.push(matched);
|
||||
} else {
|
||||
id_list.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// console.log([ id_list, id_data ]);
|
||||
return [ id_list, id_data ];
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// export 導出.
|
||||
|
||||
// @instance
|
||||
Object.assign(Work_crawler.prototype, {
|
||||
search_result_file_name : 'search.json',
|
||||
cache_title_to_id : true,
|
||||
get_search_result_file : function() {
|
||||
var search_result_file = this.main_directory
|
||||
+ this.search_result_file_name;
|
||||
return search_result_file;
|
||||
},
|
||||
get_search_result : function() {
|
||||
var search_result_file = this.get_search_result_file(),
|
||||
// search cache
|
||||
// 檢查看看之前是否有獲取過。
|
||||
search_result = library_namespace.get_JSON(search_result_file);
|
||||
return search_result;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 不設定(hook)本 module 之 namespace,僅執行 module code。
|
||||
return library_namespace.env.not_to_extend_keyword;
|
||||
}
|
233
app/node_modules/cejs/application/net/work_crawler/sites/AlphaPolis.js
generated
vendored
233
app/node_modules/cejs/application/net/work_crawler/sites/AlphaPolis.js
generated
vendored
@@ -1,233 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading AlphaPolis user novels / comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載 アルファポリス - 電網浮遊都市 - 小説 / 漫画 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.AlphaPolis({
|
||||
// configuration
|
||||
site : '' || CeL.get_script_name()
|
||||
}).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @since 2020/7/18 5:31:55 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/novel.ja-JP/AlphaPolis.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.ja-JP/AlphaPolis_user_manga.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.ja-JP/AlphaPolis_official_manga.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.AlphaPolis',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 當網站不允許太過頻繁的訪問/access時,可以設定下載之前的等待時間(ms)。
|
||||
// 模仿實際人工請求。
|
||||
// chapter_time_interval : '5s',
|
||||
|
||||
base_URL : 'https://www.alphapolis.co.jp/',
|
||||
|
||||
// @deprecated: novel
|
||||
// till 20170619, use POST. 20170620 AlphaPolis 改版, use UTF-8.
|
||||
search_URL_2016_to_20170619 : function(work_title) {
|
||||
return [ 'top/search/', {
|
||||
// 2: 小説
|
||||
'data[tab]' : 2,
|
||||
'data[refer]' : work_title
|
||||
} ];
|
||||
},
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : function(work_title) {
|
||||
return 'search?category='
|
||||
+ this.work_type.split('/').reverse().join('_') + '&query='
|
||||
+ encodeURIComponent(work_title.replace(/\s+\d+$/, ''));
|
||||
},
|
||||
// for AlphaPolis_user_manga.js , AlphaPolis_official_manga.js
|
||||
parse_search_result : function(html, get_label) {
|
||||
// console.log(html);
|
||||
var _this = this, id_data = [],
|
||||
// {Array}id_list = [id,id,...]
|
||||
id_list = [];
|
||||
html.each_between(' class="title">', '</a>', function(text) {
|
||||
// console.log(text);
|
||||
var id = text.between(' href="/' + _this.work_type + '/', '"');
|
||||
if (id) {
|
||||
id_list.push(id.replace(/\//, '-'));
|
||||
id_data.push(get_label(text.between('>')));
|
||||
}
|
||||
});
|
||||
// console.log([ id_list, id_data ]);
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return this.work_type + '/' + work_id.replace('-', '/');
|
||||
},
|
||||
// for AlphaPolis.js , AlphaPolis_user_manga.js
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
// 2019/8/16 19:0 改版。
|
||||
title : get_label(html.between('<h1 class="title">', '</h1>')
|
||||
|| html.between('<h2 class="title">', '</h1>')),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
// e.g., 连载中, 連載中
|
||||
status : [],
|
||||
// get the first <div class="author">...</div>
|
||||
author : get_label(html.between('<div class="author">', '</a>')),
|
||||
last_update : get_label(html.between('<th>更新日時</th>', '</td>')),
|
||||
description : get_label(
|
||||
html.between('<div class="abstract">', '</div>'))
|
||||
.replace(/\n{2}/g, '\n'),
|
||||
ranking : get_label(html.between('<div class="ranking">',
|
||||
// also category
|
||||
'</div>')).replace(/\t/g, '').replace(/\n{3}/g, '\0').replace(
|
||||
/\n/g, ' ').split('\0'),
|
||||
// site_name : 'アルファポリス'
|
||||
language : html.between('data-lang="', '"') || 'ja-JP'
|
||||
|
||||
}, PATTERN, matched;
|
||||
|
||||
if (work_data.title === 'Not Found' && !work_data.author) {
|
||||
// 對於已經失效的作品,直接中斷下載。
|
||||
throw work_data.title;
|
||||
}
|
||||
|
||||
if (work_data.site_name) {
|
||||
// "アルファポリス - 電網浮遊都市 - " → "アルファポリス"
|
||||
work_data.site_name = work_data.site_name.replace(/ +- .+/, '');
|
||||
}
|
||||
|
||||
// 2019/1 才發現改 pattern 了。
|
||||
PATTERN = /<span class="tag">([\s\S]+?)<\/span>/g;
|
||||
while (matched = PATTERN.exec(html.between(
|
||||
'<div class="content-tags">', '</div>'))) {
|
||||
work_data.status.push(get_label(matched[1]));
|
||||
}
|
||||
|
||||
html.between('<div class="content-statuses">', '</div>')
|
||||
// additional tags
|
||||
.each_between(' class="content-status', '<',
|
||||
//
|
||||
function(text) {
|
||||
work_data.status.push(get_label(text.between('>')));
|
||||
});
|
||||
work_data.status = work_data.status.unique();
|
||||
|
||||
// <h2>作品の情報</h2>
|
||||
extract_work_data(work_data, html,
|
||||
/<th>([\s\S]+?)<\/th>[\s\n]*<td[^<>]*>([\s\S]+?)<\/td>/g);
|
||||
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
if (work_data.image
|
||||
// ignore site default image
|
||||
&& work_data.image.endsWith('\/ogp.png')) {
|
||||
delete work_data.image;
|
||||
}
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
// for AlphaPolis.js , AlphaPolis_user_manga.js
|
||||
get_chapter_list : function(work_data, html) {
|
||||
work_data.chapter_list = [];
|
||||
var _this = this;
|
||||
html = html.between('<div class="nav">', '<div class="freespace">')
|
||||
//
|
||||
.each_between('<a href="/'
|
||||
//
|
||||
+ _this.work_type + '/', '</a>', function(text) {
|
||||
work_data.chapter_list.push({
|
||||
url : '/' + _this.work_type + '/'
|
||||
//
|
||||
+ text.between(null, '"'),
|
||||
date : text.between('<span class="open-date">', '</span>')
|
||||
//
|
||||
.to_Date({
|
||||
zone : work_data.time_zone
|
||||
}),
|
||||
title : text.between(' class="title">',
|
||||
// '<span class="title"><span
|
||||
// class="bookmark-dummy"></span>',
|
||||
// '</span>'
|
||||
'<span class="open-date">')
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// for AlphaPolis_user_manga.js , AlphaPolis_official_manga.js
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// console.log(html);
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
Object.assign(chapter_data, {
|
||||
// 設定必要的屬性。
|
||||
title : get_label(html.between('<h2>', '</h2>')),
|
||||
image_list : []
|
||||
});
|
||||
|
||||
html.each_between('_pages.push("', '"', function(url) {
|
||||
if (url.includes('://'))
|
||||
chapter_data.image_list.push(url);
|
||||
});
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_AlphaPolis_crawler(configuration, callback, initializer) {
|
||||
// library_namespace.set_debug(9);
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
if (configuration.need_create_ebook) {
|
||||
library_namespace.run([ 'application.storage.EPUB'
|
||||
// CeL.detect_HTML_language()
|
||||
, 'application.locale' ]);
|
||||
}
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
// for 年齢確認 eternityConfirm()
|
||||
crawler.setup_value('cookie', [ 'confirm='
|
||||
+ Math.floor(Date.now() / 1000)
|
||||
// location.hostname
|
||||
+ ';domain=' + crawler.base_URL.match(/\/\/([^\/]+)/)[1]
|
||||
+ ';path=/;' ]);
|
||||
|
||||
return crawler;
|
||||
}
|
||||
|
||||
return new_AlphaPolis_crawler;
|
||||
}
|
375
app/node_modules/cejs/application/net/work_crawler/sites/PTCMS.js
generated
vendored
375
app/node_modules/cejs/application/net/work_crawler/sites/PTCMS.js
generated
vendored
@@ -1,375 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading PTCMS novels.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸常見小說管理系統: PT小说聚合程序 (PTCMS系统) 各個版本的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.PTCMS(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* TODO: 去掉前後網站廣告。
|
||||
*
|
||||
* @see https://www.ptcms.com/
|
||||
* @see http://down.chinaz.com/test/201210/2252_1.htm 杰奇小说连载系统 杰奇原创文学系统,
|
||||
* https://zhidao.baidu.com/question/518711125119801445.html 奇文网络小说管理系统
|
||||
* 终点小说网站管理系统 露天中文小说网站管理系统 https://zhidao.baidu.com/question/474414436.html
|
||||
* https://www.ptcms.com/ 关关采集器
|
||||
*
|
||||
* @see https://github.com/LZ0211/Wedge/tree/master/lib/Sites/plugins
|
||||
* https://github.com/lufengfan/NovelDownloader
|
||||
* https://github.com/unclezs/NovelHarvester
|
||||
* @see http://www.sodu.cc/default.html
|
||||
* https://kknews.cc/zh-tw/culture/oqyx5.html https://tw.hjwzw.com/
|
||||
* @see http://www.76wx.com/ http://www.xssk.net/
|
||||
*
|
||||
* @since 2017/6/19 21:15:40 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see https://github.com/kanasimi/work_crawler/blob/master/81xsw.js
|
||||
// @see https://github.com/kanasimi/work_crawler/blob/master/23us.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.PTCMS',
|
||||
|
||||
require : 'application.net.work_crawler.'
|
||||
//
|
||||
+ '|application.storage.EPUB.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function is_server_error(result) {
|
||||
// TODO: 81xsw 有時會 403,需要重新再擷取一次。
|
||||
|
||||
return result in {
|
||||
// 88dus 有時會 502,需要重新再擷取一次。
|
||||
'502 Bad Gateway' : true,
|
||||
// 630book 有時會 503,需要重新再擷取一次。
|
||||
'503 Service Unavailable' : true,
|
||||
// 630book 有時會 "500 - 内部服务器错误。"
|
||||
'服务器错误' : true
|
||||
};
|
||||
}
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// auto_create_ebook, automatic create ebook
|
||||
// MUST includes CeL.application.locale!
|
||||
need_create_ebook : true,
|
||||
// recheck:從頭檢測所有作品之所有章節。
|
||||
// 'changed': 若是已變更,例如有新的章節,則重新下載/檢查所有章節內容。
|
||||
recheck : 'changed',
|
||||
|
||||
// base_URL : 'http://www.*.com/',
|
||||
// charset : 'gbk',
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
// search_URL : '',
|
||||
|
||||
// for 笔趣阁
|
||||
parse_search_result_biquge : function(html, get_label) {
|
||||
// console.log(html);
|
||||
var matched = html
|
||||
.match(/og:url" content="[^<>"]+?\/(?:\d+_)?(\d+)\/"/);
|
||||
if (matched) {
|
||||
return [ [ +matched[1] ],
|
||||
[ get_label(html.between('og:title" content="', '"')) ] ];
|
||||
}
|
||||
|
||||
matched = html.match(/blockcontent">([\s\S]+)<\/div>/);
|
||||
if (matched) {
|
||||
/**
|
||||
* <code>
|
||||
|
||||
// xbiquge.cc.js:
|
||||
<div class="blocktitle">出现错误!</div><div class="blockcontent"><div style="padding: 10px"><br /> 错误原因:对不起,两次搜索的间隔时间不得少于 30 秒<br /><br /> 请 <a href="javascript:history.back(1)">返 回</a> 并修正<br /><br /></div><div style="width: 100%; text-align: right; line-height: 200%; padding-right: 10px;">[<a href="javascript:window.close()">关闭本窗口</a>]</div></div>
|
||||
|
||||
</code>
|
||||
*/
|
||||
matched = get_label(matched[1]).replace(/\n[\s\S]*/, '');
|
||||
library_namespace.error(matched);
|
||||
}
|
||||
|
||||
// console.trace(html);
|
||||
var id_list = [], id_data = [];
|
||||
html.each_between('<li>', '</li>', function(text) {
|
||||
matched = text.match(
|
||||
/**
|
||||
* <code>
|
||||
|
||||
// biquge.js:
|
||||
<span class="s2"><a href="https://www.xs.la/211_211278/" target="_blank">
|
||||
万古剑神</a>
|
||||
</span>
|
||||
|
||||
// xbiquge.js:
|
||||
<span class="s2"><a href="http://www.xbiquge.cc/book/24276/">元尊</a></span>
|
||||
|
||||
|
||||
// xbiquke.js
|
||||
<span class="s1">1</span>
|
||||
<span class="s2">
|
||||
<a href="/29_29775/" target="_blank">
|
||||
我真不是邪神走狗
|
||||
</a>
|
||||
</span>
|
||||
<span class="s4">
|
||||
<a href="/author/29775/">
|
||||
万劫火
|
||||
</a>
|
||||
</span>
|
||||
<span class="s3">
|
||||
<a style="color: Red;" href="/29_29775/22709468.html" target="_blank" title="番外·童年(一)">
|
||||
番外·童年(一)</a>
|
||||
</span>
|
||||
|
||||
<span class="s6">2021-11-26 02:30:39</span>
|
||||
|
||||
|
||||
</code>
|
||||
*/
|
||||
/<a href="[^<>"]*?\/(?:\d+_)?(\d+)\/"[^<>]*>([\s\S]+?)<\/a>/);
|
||||
// console.log([ text, matched ]);
|
||||
if (matched) {
|
||||
id_list.push(+matched[1]);
|
||||
id_data.push(get_label(matched[2]));
|
||||
}
|
||||
});
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
// work_URL : function(work_id) {
|
||||
// /** @see this.work_URL in CeL.application.net.work_crawler */ },
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
// console.log(html);
|
||||
// 由 meta data 取得作品資訊。
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : html.between('og:novel:title" content="', '"')
|
||||
// e.g., 88dushu.js
|
||||
|| html.between('og:title" content="', '"')
|
||||
// 通常與og:title相同
|
||||
|| html.between('og:novel:book_name" content="', '"'),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
author : html.between('og:novel:author" content="', '"'),
|
||||
// e.g., 连载[中], [已]完成
|
||||
status : html.between('og:novel:status" content="', '"'),
|
||||
category : html.between('og:novel:category" content="', '"'),
|
||||
// https://www.booktxt.net/: '<div id="fmimg">'
|
||||
image : html.between('<div id="fmimg">', '</div>').between(
|
||||
'<img src="', '"')
|
||||
// general
|
||||
|| html.between('og:image" content="', '"'),
|
||||
last_update :
|
||||
//
|
||||
html.between('og:novel:update_time" content="', '"')
|
||||
// e.g., 630book
|
||||
|| html.between('og:novel:update_time" content=\'', "'"),
|
||||
latest_chapter : html.between(
|
||||
'og:novel:latest_chapter_name" content="', '"'),
|
||||
description : get_label(
|
||||
//
|
||||
html.between('og:description" content="', '"')
|
||||
// e.g., 630book
|
||||
|| html.between('<div id="intro">', '</div>'))
|
||||
// 偶爾會有沒填上描述的書籍。
|
||||
|| '',
|
||||
language : 'cmn-Hans-CN',
|
||||
site_name : get_label(
|
||||
//
|
||||
html.between('<div class="logo">', '</div>')
|
||||
//
|
||||
|| html.between('<div class="header_logo">', '</div>')
|
||||
// e.g., 630book
|
||||
|| html.between('<strong class="logo">', '</strong>'))
|
||||
};
|
||||
// 由 meta data 取得作品資訊。
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
if (is_server_error(work_data.title)) {
|
||||
return this.REGET_PAGE;
|
||||
}
|
||||
|
||||
if (this.extract_work_data) {
|
||||
// e.g., xbiquke.js
|
||||
this.extract_work_data(work_data, html, get_label,
|
||||
extract_work_data);
|
||||
}
|
||||
|
||||
if (/^\d{1,2}-\d{1,2}$/.test(work_data.last_update)) {
|
||||
// e.g., 07-01 → 2017-07-01
|
||||
work_data.last_update = (new Date).getFullYear() + '-'
|
||||
+ work_data.last_update;
|
||||
}
|
||||
|
||||
if (work_data.site_name.includes('?$')) {
|
||||
// e.g., 88dushu.js
|
||||
work_data.site_name = html.between("AddFavorite('", "'");
|
||||
}
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
// 取得包含章節列表的文字範圍。
|
||||
// get_chapter_list_contents : function(html) {return html.between();},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
// determine base directory of work
|
||||
work_data.base_url = work_data.url.endsWith('/') ? work_data.url
|
||||
: work_data.url.replace(/\.[^.]+$/, '/');
|
||||
if (work_data.base_url.startsWith(this.base_URL)) {
|
||||
work_data.base_url = work_data.base_url
|
||||
.slice(this.base_URL.length - 1);
|
||||
}
|
||||
|
||||
if (this.get_chapter_list_contents) {
|
||||
html = this.get_chapter_list_contents(html);
|
||||
}
|
||||
// console.log(html);
|
||||
|
||||
work_data.chapter_list = [];
|
||||
var part_title, matched,
|
||||
// 章節以及篇章連結的模式。
|
||||
// [ all, tag name, attributes, 連結內容 HTML ]
|
||||
PATTERN_chapter = /<(li|dd|dt)([^<>]*)>([\s\S]*?)<\/\1>/g;
|
||||
while (matched = PATTERN_chapter.exec(html)) {
|
||||
if (false) {
|
||||
delete matched.input;
|
||||
console.log(matched);
|
||||
}
|
||||
|
||||
if (matched[1] === 'dt' ||
|
||||
// e.g., 88dushu.js
|
||||
matched[1] === 'li' && matched[2].includes('class="fj"')) {
|
||||
part_title = get_label(matched[3]);
|
||||
if (part_title.includes('最新章节') && part_title.length > 20) {
|
||||
// e.g., 《...》最新章节(提示:已启用缓存技术,最新章节可能会延时显示,登录书架即可实时查看。)
|
||||
// e.g., ...最新章节列表 (本页已经缓存,请加入书架查看...最新章节)
|
||||
part_title = 'pass';
|
||||
} else if (part_title.includes('正文')) {
|
||||
// e.g., 《...》正文卷, 《...》正文
|
||||
part_title = '';
|
||||
}
|
||||
// console.log(part_title);
|
||||
|
||||
} else if (part_title !== 'pass'
|
||||
// 取得連結內容。
|
||||
&& (matched = matched[3].between('<a ', '</a>'))) {
|
||||
var chapter_data = {
|
||||
// 從href取得章節的網址。
|
||||
url : matched.between('href="', '"')
|
||||
// xbiquge.js: 交錯使用 "", ''
|
||||
|| matched.between("href='", "'")
|
||||
// booktxt.js: 交錯使用 "", ''
|
||||
|| matched.between('href ="', '"'),
|
||||
part_title : part_title,
|
||||
// 從title/顯示的文字取得章節的標題。
|
||||
title : matched.between('title="', '"')
|
||||
|| get_label(matched.between('>'))
|
||||
};
|
||||
work_data.chapter_list.push(chapter_data);
|
||||
}
|
||||
}
|
||||
// console.log(work_data.chapter_list);
|
||||
},
|
||||
|
||||
// 取得每一個章節的內容與各個影像資料。 get_chapter_data()
|
||||
chapter_URL : function(work_data, chapter_NO) {
|
||||
var url = work_data.chapter_list[chapter_NO - 1].url;
|
||||
// console.trace(url);
|
||||
url = url.startsWith('/') || url.includes('://') ? url
|
||||
: work_data.base_url + url;
|
||||
// console.trace(url);
|
||||
return url;
|
||||
},
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
if (!html && this.skip_error === true) {
|
||||
// Skip empty chapter
|
||||
return;
|
||||
}
|
||||
|
||||
// 在取得小說章節內容的時候,若發現有章節被目錄漏掉,則將之補上。
|
||||
this.check_next_chapter(work_data, chapter_NO, html,
|
||||
this.PATTERN_next_chapter);
|
||||
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1],
|
||||
//
|
||||
sub_title = get_label(html.between('<h1>', '</h1>'))
|
||||
// || get_label(html.between('<H1>', '</H1>'))
|
||||
// || chapter_data.title
|
||||
, text = (html
|
||||
// general: <div id="content">
|
||||
// xbiquge.js: <div id="content" name="content">
|
||||
.between('<div id="content"', '</div>').between('>')
|
||||
// 去除掉廣告。
|
||||
// e.g., 88dushu.js
|
||||
|| html.between('<div class="yd_text2">', '</div>')).replace(
|
||||
/<script[^<>]*>[^<>]*<\/script>/g, ''),
|
||||
//
|
||||
KEY_interval_cache = 'original_chapter_time_interval';
|
||||
|
||||
if (is_server_error(sub_title) && text.length < 2000) {
|
||||
this[KEY_interval_cache] = this.chapter_time_interval;
|
||||
// 當網站不允許太過頻繁的訪問/access時,可以設定下載之前的等待時間(ms)。
|
||||
this.chapter_time_interval = 10 * 1000;
|
||||
return this.REGET_PAGE;
|
||||
}
|
||||
if (KEY_interval_cache in this) {
|
||||
// recover time interval
|
||||
if (this[KEY_interval_cache] > 0) {
|
||||
this.chapter_time_interval = this[KEY_interval_cache];
|
||||
} else {
|
||||
delete this.chapter_time_interval;
|
||||
}
|
||||
delete this[KEY_interval_cache];
|
||||
}
|
||||
|
||||
if (this.remove_ads) {
|
||||
text = this.remove_ads(text);
|
||||
}
|
||||
// console.log(text);
|
||||
|
||||
this.add_ebook_chapter(work_data, chapter_NO, {
|
||||
title : chapter_data.part_title,
|
||||
sub_title : sub_title,
|
||||
text : text
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_PTCMS_novels_crawler(configuration) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
if (configuration.parse_search_result === 'biquge') {
|
||||
configuration.parse_search_result = configuration.parse_search_result_biquge;
|
||||
}
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
return new library_namespace.work_crawler(configuration);
|
||||
}
|
||||
|
||||
return new_PTCMS_novels_crawler;
|
||||
}
|
634
app/node_modules/cejs/application/net/work_crawler/sites/SinMH.js
generated
vendored
634
app/node_modules/cejs/application/net/work_crawler/sites/SinMH.js
generated
vendored
@@ -1,634 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading SinMH CMS comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸常見漫畫管理系統: 圣樱漫画管理系统 (圣樱CMS) MHD模板 PC端 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.SinMH(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* TODO: ONE漫画 https://www.onemanhua.com/ 可能是比 930mh.js 更舊的版本?
|
||||
*
|
||||
* @see https://cms.shenl.com/sinmh/
|
||||
* @see https://www.manhuadui.com/js/common.js "Created by Shen.L on 2016/1/28."
|
||||
*
|
||||
* @since 2018/7/26 11:9:53 模組化 MHD模板。<br />
|
||||
* 2019/2/4 add 930mh.js 使用 CryptoJS,採用 DMZJ模板。<br />
|
||||
* 2019/7/2 50mh.js 使用 CryptoJS,採用 DMZJ模板。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/36mh.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/gufengmh.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/930mh.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.SinMH',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 嘗試取得被屏蔽的作品。
|
||||
// 對於被屏蔽的作品,將會每次都從頭檢查。
|
||||
try_to_get_blocked_work : true,
|
||||
|
||||
// 當有多個分部的時候才重新檢查。
|
||||
recheck : 'multi_parts_changed',
|
||||
|
||||
// allow .jpg without EOI mark.
|
||||
// allow_EOI_error : true,
|
||||
// 當圖像檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。
|
||||
// skip_error : true,
|
||||
|
||||
// 提取出引數(如 URL)中的作品ID 以回傳。
|
||||
extract_work_id : function(work_information) {
|
||||
// e.g,
|
||||
// https://www.36mh.com/manhua/IDOLiSH7ouxiangxingyuanxiangliuxingxuyuan/
|
||||
return /^[a-z\d]+$/i.test(work_information) && work_information;
|
||||
},
|
||||
|
||||
// 取得伺服器列表。
|
||||
// use_server_cache : true,
|
||||
server_URL : 'js/config.js',
|
||||
parse_server_list : function(html) {
|
||||
var server_list = [], SinConf;
|
||||
// console.trace(html);
|
||||
if (/^\s*var cars/.test(html)) {
|
||||
// for manhuaniu.js 2021/1/19 改版
|
||||
html = html.replace('var SinConf', 'SinConf').replace(
|
||||
/\n}\(\);[\s\S]*/, '}();SinConf.cars=cars;');
|
||||
} else {
|
||||
html = html.replace('var ', '').replace(/(}\(\))[\s\S]*/, '$1');
|
||||
}
|
||||
// console.trace(html);
|
||||
eval(html);
|
||||
|
||||
function append_path(host) {
|
||||
return host.endsWith('/') ? host : host + '/';
|
||||
}
|
||||
SinConf.resHost.map(function(data) {
|
||||
server_list.append(data.domain.map(append_path));
|
||||
});
|
||||
if (SinConf.cars) {
|
||||
server_list.append(SinConf.cars.map(append_path));
|
||||
}
|
||||
server_list = server_list.unique();
|
||||
// for manhuaniu.js 2021/1/19 改版
|
||||
server_list = server_list.filter(function(server) {
|
||||
return !server.includes('restp.dongqiniqin');
|
||||
});
|
||||
server_list.conf = SinConf;
|
||||
// console.log(SinConf);
|
||||
// console.log(server_list);
|
||||
return server_list;
|
||||
},
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
// 1. 使用 PC端 網頁取得搜尋所得的作品資料。 (default)
|
||||
search_URL : 'search/?keywords=',
|
||||
// 2. 使用API取得搜尋所得的作品資料。 (set search_URL:'API')
|
||||
search_URL_API : function(work_title) {
|
||||
// SinConf.apiHost
|
||||
var apiHost = this.api_base_URL
|
||||
|| this.base_URL.replace(/\/\/[a-z]+/, '//api');
|
||||
return [ apiHost + 'comic/search', {
|
||||
keywords : work_title
|
||||
} ];
|
||||
},
|
||||
parse_search_result : function(html, get_label) {
|
||||
// console.log(html);
|
||||
if (html.startsWith('{')) {
|
||||
// 2. 使用API取得搜尋所得的作品資料。
|
||||
/**
|
||||
* e.g.,<code>
|
||||
{"items":[{"id":3542,"status":1,"commend":0,"is_original":0,"is_vip":0,"name":"军阀霸宠:纯情妖女火辣辣","title":"民国妖闻录","alias":"","original_name":"","letter":"j","slug":"junfabachongchunqingyaonuhuolala","coverUrl":"http://res.gufengmh.com/images/cover/201711/1509877682Xreq-5mrrSsDm82P.jpg","uri":"/manhua/junfabachongchunqingyaonuhuolala/","last_chapter_name":"040:纯良少年的堕落","last_chapter_id":235075,"author":"逐浪动漫","author_id":3901,"serialise":1}],"_links":{"self":{"href":"http://api.gufengmh.com/comic/search?page=1"}},"_meta":{"totalCount":1,"pageCount":1,"currentPage":1,"perPage":20},"status":0}
|
||||
</code>
|
||||
*/
|
||||
var id_data = html ? JSON.parse(html).items : [];
|
||||
// console.log(id_data);
|
||||
return [ id_data, id_data ];
|
||||
}
|
||||
|
||||
// 1. 使用 PC端 網頁取得搜尋所得的作品資料。
|
||||
// e.g., 36mh.js
|
||||
var id_list = [], id_data = [], matched,
|
||||
// matched: [ all, url, inner (title) ]
|
||||
PATTERN_search = /<p class="ell"><a href="([^<>"]+)">([^<>]+)/g;
|
||||
|
||||
if (matched = html.between('<h4 class="fl">')) {
|
||||
html = matched;
|
||||
// matched: [ all, url, inner (title) ]
|
||||
// PATTERN_search = /<p class="ell"><a
|
||||
// href="([^<>"]+)">([^<>]+)/g;
|
||||
|
||||
} else if (matched = html.between('<div id="update_list">')) {
|
||||
// 行動版 mobile version
|
||||
// e.g., <div id="update_list"><div class='UpdateList'><div
|
||||
// class="itemBox" data-key="10992">
|
||||
html = matched;
|
||||
// e.g., <a class="title"
|
||||
// href="https://m.36mh.com/manhua/dushizhixiuzhenguilai/"
|
||||
// target="_blank">都市之修真归来</a>
|
||||
// matched: [ all, url, inner (title) ]
|
||||
PATTERN_search = /<a class="title" href="([^<>"]+)"[^<>]*>([^<>]+)/g;
|
||||
|
||||
} else {
|
||||
// throw new Error('Unknown site!');
|
||||
}
|
||||
|
||||
while (matched = PATTERN_search.exec(html)) {
|
||||
// .html: mh1234.js
|
||||
matched[1] = matched[1].match(/([^\/]+)(?:\/|\.html)$/);
|
||||
id_list.push(matched[1][1]);
|
||||
id_data.push(get_label(matched[2]));
|
||||
}
|
||||
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
// e.g., 50mh.js
|
||||
// id_of_search_result : 'slug',
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
// console.log(work_id);
|
||||
return 'manhua/' + work_id + '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
// console.log(html);
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(html.between('<h1>', '</h1>')),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
description : get_label(html.between('intro-all', '</div>')
|
||||
.between('>')
|
||||
// 930mh.js
|
||||
|| html.between('<p class="comic_deCon_d">', '</p>')
|
||||
// copy from 733mh.js: for mh1234.js
|
||||
|| html.between(
|
||||
'<div class="introduction" id="intro1">',
|
||||
'</div>'))
|
||||
};
|
||||
|
||||
// <div class="book-detail pr fr">
|
||||
// <ul class="detail-list cf">
|
||||
// ...
|
||||
// </ul>
|
||||
// <a class="intro-act" id="intro-act" href="javascript:;">展開詳情</a>
|
||||
extract_work_data(work_data, html.between('detail-list', '</ul>'),
|
||||
// e.g., "<strong>漫画别名:</strong>暂无</span>"
|
||||
// gufengmh.js:<li><span><strong>漫画类型:</strong>...</span><span><strong>漫画作者:</strong>...</span></li>
|
||||
/<strong[^<>]*>([^<>]+)<\/strong>([\s\S]+?)<\/(?:li|span)>/g);
|
||||
// console.log(html.between('detail-list', '</ul>'));
|
||||
// console.log(work_data);
|
||||
|
||||
// 930mh.js
|
||||
extract_work_data(work_data, html
|
||||
.between('<ul class="comic_deCon_liT">',
|
||||
'<p class="comic_deCon_d">')
|
||||
// <li>时间:2019-02-04 <li>最新:<a
|
||||
// href="/manhua/17884/668443.html">第6话</a></li>
|
||||
.replace(/<li>/g, '</li><li>'),
|
||||
// e.g., "<li>类别:<a href="/list/shaonian/">少年</a></li>"
|
||||
/<li>([^:]+):([\s\S]+?)<\/li>/g);
|
||||
|
||||
// copy from 733mh.js: for mh1234.js
|
||||
extract_work_data(work_data, html.between('<div class="info">',
|
||||
'<div class="info_cover">'),
|
||||
/<em>([^<>]+?)<\/em>([\s\S]*?)<\/p>/g);
|
||||
|
||||
// 由 meta data 取得作品資訊。
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
Object.assign(work_data, {
|
||||
author : work_data.漫画作者 || work_data.漫畫作者 || work_data.作者
|
||||
|| work_data.原著作者,
|
||||
status : work_data.漫画状态 || work_data.漫畫狀態 || work_data.状态,
|
||||
last_update : work_data.更新时间 || work_data.时间,
|
||||
latest_chapter : work_data.最新 || work_data.更新至
|
||||
|| get_label(html.between('<span class="text">更新至',
|
||||
// for 36mh.js: "更新至:", 999comics.js: "更新至:"
|
||||
'</span>').replace(/^[::]/, '')),
|
||||
latest_chapter_url : html.between('最新:<a href="', '"')
|
||||
// for 36mh.js
|
||||
|| html.between('更新至 [ <a href="', '"')
|
||||
// gufengmh.js
|
||||
|| html.between('更新至:</strong><a href="', '"')
|
||||
});
|
||||
|
||||
// console.log(work_data);
|
||||
if (!work_data.last_update && work_data.status) {
|
||||
// for 36mh.js
|
||||
var matched = work_data.status
|
||||
.match(/^([\s\S]+?)最近[于於]([\s\S]+?)$/);
|
||||
if (matched) {
|
||||
Object.assign(work_data, {
|
||||
status : matched[1],
|
||||
last_update : matched[2].replace(
|
||||
/^[\s\n]*\[|\][\s\n]*$/g, '').trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!work_data.last_update) {
|
||||
// for 999comics.js
|
||||
var matched = html.match(/最近[于於]([\s\S]+?)<\//);
|
||||
// console.log(matched);
|
||||
if (matched) {
|
||||
work_data.last_update = get_label(matched[1].replace(
|
||||
/^[\s\n]*\[|\][\s\n]*$/g, ''));
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
// console.log(work_data);
|
||||
|
||||
var chapter_block, PATTERN_chapter_block = html
|
||||
.includes('class="chapter-body')
|
||||
// <div class="chapter-category clearfix">
|
||||
// <div class="chapter-body clearfix">
|
||||
? /class="chapter-(body|category)[^<>]+>([\s\S]+?)<\/div>/g
|
||||
// 930mh.js
|
||||
// <div class="zj_list_head">...<h2>章节<em class="c_3">列表</em></h2>
|
||||
// <div class="zj_list_head_px" data-key="6"><span>排序 :...</div>
|
||||
// <div class="zj_list_con autoHeight">...</div>
|
||||
: /class="zj_list_(con|head)[^<>]+>([\s\S]+?)<\/div>/g,
|
||||
//
|
||||
latest_chapter_list = work_data.chapter_list;
|
||||
// reset work_data.chapter_list
|
||||
work_data.chapter_list = [];
|
||||
// 漫畫目錄名稱不須包含分部號碼。使章節目錄名稱不包含 part_NO。
|
||||
// 將會在 function get_chapter_directory_name() 自動設定。
|
||||
// work_data.chapter_list.add_part_NO = false;
|
||||
|
||||
while (chapter_block = PATTERN_chapter_block.exec(html)) {
|
||||
// delete chapter_block.input;
|
||||
// console.log(chapter_block);
|
||||
if (chapter_block[1] === 'category') {
|
||||
// console.log(chapter_block[2]);
|
||||
// e.g., 决断地 @ gufengmh
|
||||
chapter_block = chapter_block[2]
|
||||
// <div class="caption pull-left"><span>章节</span></div>
|
||||
// <div class="caption pull-left"><span>单话</span></div>
|
||||
.match(/class="caption[^<>]+>([\s\S]+)/);
|
||||
// console.log(chapter_block);
|
||||
if (chapter_block) {
|
||||
this.set_part(work_data, chapter_block[1]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chapter_block[1] === 'head') {
|
||||
// console.log(chapter_block[2]);
|
||||
// 930mh.js
|
||||
// e.g., http://www.duzhez.com/manhua/269/
|
||||
chapter_block = chapter_block[2]
|
||||
// <h2>章节<em class="c_3">列表</em></h2>
|
||||
// <h2>番外篇<em class="c_3">列表</em></h2>
|
||||
.between('<h2>', '<em class="c_3">列表</em>');
|
||||
// console.log(chapter_block);
|
||||
if (chapter_block) {
|
||||
this.set_part(work_data, chapter_block);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
chapter_block = chapter_block[2];
|
||||
var link, PATTERN_chapter_link =
|
||||
//
|
||||
/<a href="([^<>"]+)"[^<>]*>([\s\S]+?)<\/a>/g;
|
||||
while (link = PATTERN_chapter_link.exec(chapter_block)) {
|
||||
if (link[1].startsWith('javascript:')) {
|
||||
// 本站应《 》版权方要求现已屏蔽删除本漫画所有章节链接,只保留作品文字信息简介以及章节目录
|
||||
continue;
|
||||
}
|
||||
var chapter_data = {
|
||||
url : link[1],
|
||||
title : get_label(link[2])
|
||||
};
|
||||
this.add_chapter(work_data, chapter_data);
|
||||
// console.log(work_data.chapter_list);
|
||||
// console.log(chapter_data);
|
||||
}
|
||||
}
|
||||
|
||||
this.check_filtered(work_data, html, get_label,
|
||||
//
|
||||
latest_chapter_list);
|
||||
work_data.inverted_order = this.chapter_inverted_order;
|
||||
// console.log(work_data.chapter_list);
|
||||
// throw work_data.chapter_list.length;
|
||||
},
|
||||
// 注意:在呼叫本函數之前,不可改變 html!
|
||||
check_filtered : function(work_data, html, get_label,
|
||||
latest_chapter_list) {
|
||||
// console.log(work_data);
|
||||
// console.log(work_data.chapter_list);
|
||||
var text = work_data.chapter_list.length === 0 && get_label(
|
||||
/**
|
||||
* 已屏蔽删除本漫画所有章节链接 e.g., <code>
|
||||
|
||||
// 930mh.js 一人之下
|
||||
<div class="zj_list_con autoHeight">
|
||||
<p class="ip-notice" style="padding:10px;color: red;background:snow;font-size:14px;width:875px;">
|
||||
尊敬的各位喜爱一人之下漫画的用户,本站应《一人之下》版权方要求现已屏蔽删除本漫画所有章节链接,只保留作品文字信息简介以及章节目录,请喜欢一人之下的漫友购买杂志或到官网付费欣赏。为此给各位漫友带来的不便,敬请谅解!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
// mh1234.js
|
||||
<div class="ip-body">
|
||||
<p class="ip-notice">
|
||||
尊敬的各位喜爱妖精种植手册漫画的用户,本站应《妖精种植手册 》版权方要求现已屏蔽删除本漫画所有章节链接,只保留作品文字信息简介以及章节目录,请喜欢妖精种植手册 的漫友购买杂志或到官网付费欣赏。为此给各位漫友带来的不便,敬请谅解!
|
||||
</p>
|
||||
<p>
|
||||
版权方在线阅读地址: <span><a href="http://www.mh1234.com" rel="nofollow">http://www.mh1234.com</a></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</code>
|
||||
*/
|
||||
html.between('<p class="ip-notice"', '</p>').between('>')
|
||||
//
|
||||
|| html.between('class="ip-body">', '</div>'));
|
||||
|
||||
// console.log(text);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
work_data.removed = text;
|
||||
|
||||
var chapter_id = html.between('href="/comic/read/?id=', '"')
|
||||
|| html.between('SinMH.initComic(', ')')
|
||||
|| html.between('SinTheme.initComic(', ')')
|
||||
|| html.between('var pageId = "comic.', '"');
|
||||
|
||||
if (this.try_to_get_blocked_work && chapter_id) {
|
||||
library_namespace.info([ work_data.title || work_data.id, ': ',
|
||||
{
|
||||
// gettext_config:{"id":"trying-to-get-the-blocked-work"}
|
||||
T : '嘗試取得被屏蔽的作品。'
|
||||
} ]);
|
||||
if (Array.isArray(latest_chapter_list)
|
||||
// e.g., 全职法师, 一人之下 http://www.duzhez.com/manhua/1532/
|
||||
&& latest_chapter_list.length > 1
|
||||
//
|
||||
&& (!this.recheck || this.recheck in {
|
||||
changed : true,
|
||||
multi_parts_changed : true
|
||||
})) {
|
||||
library_namespace.info({
|
||||
// gettext_config:{"id":"using-the-previous-cache-to-download-§$1"}
|
||||
T : [ '使用之前的快取,自 §%1 接續下載。',
|
||||
latest_chapter_list.length ]
|
||||
});
|
||||
// 這可以保留 work_data.chapter_list 先前的屬性。
|
||||
work_data.chapter_list = Object.assign(latest_chapter_list,
|
||||
work_data.chapter_list);
|
||||
work_data.last_download.chapter = latest_chapter_list.length;
|
||||
|
||||
} else {
|
||||
this.add_chapter(work_data,
|
||||
//
|
||||
'/comic/read/?id=' + chapter_id);
|
||||
}
|
||||
|
||||
} else {
|
||||
library_namespace.warn(text);
|
||||
}
|
||||
},
|
||||
|
||||
pre_parse_chapter_data
|
||||
// 執行在解析章節資料 process_chapter_data() 之前的作業 (async)。
|
||||
// 必須自行保證執行 callback(),不丟出異常、中斷。
|
||||
: function(XMLHttp, work_data, callback, chapter_NO) {
|
||||
var html = XMLHttp.responseText;
|
||||
if (work_data.removed && chapter_NO === 1) {
|
||||
var first_chapter_id = html.between('SinMH.initChapter(', ',')
|
||||
|| html.between('SinTheme.initChapter(', ',');
|
||||
// console.log(html);
|
||||
if (first_chapter_id) {
|
||||
library_namespace.debug('add first chapter: '
|
||||
+ first_chapter_id);
|
||||
var url = this.work_URL(work_data.id) + first_chapter_id
|
||||
+ '.html';
|
||||
work_data.chapter_list[chapter_NO - 1].url = url;
|
||||
this.get_URL(url, callback, null, {
|
||||
error_retry : this.MAX_ERROR_RETRY,
|
||||
no_warning : true
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var crypto_url = html
|
||||
// 930mh.js: Error on http://www.duzhez.com/manhua/449/245193.html
|
||||
&& html
|
||||
// https://www.manhuadui.com/manhua/haizeiwang/296660.html :
|
||||
// <script
|
||||
// src="https://cdn.staticfile.org/crypto-js/3.1.9-1/crypto-js.js"></script>
|
||||
.match(/<script src="([^"]+\/crypto(?:-js)?\.js)"><\/script>/);
|
||||
// console.log(crypto_url);
|
||||
if (crypto_url) {
|
||||
var file_name = this.main_directory + 'crypto.js';
|
||||
// TODO: this is a workaround to pass to require()
|
||||
if (!library_namespace.is_absolute_path(file_name)) {
|
||||
file_name = process.cwd()
|
||||
+ library_namespace.env.path_separator + file_name;
|
||||
}
|
||||
// console.log(file_name);
|
||||
|
||||
library_namespace.get_URL_cache(this.full_URL(crypto_url[1]),
|
||||
// @see function cops201921() @
|
||||
// http://www.duzhez.com/js/cops201921.js
|
||||
function(data, error, XMLHttp) {
|
||||
// data = data.toString();
|
||||
|
||||
// @see https://code.google.com/archive/p/crypto-js/
|
||||
// 懶得自己寫,直接 including。
|
||||
global.CryptoJS = require(file_name);
|
||||
|
||||
callback();
|
||||
}, {
|
||||
file_name : file_name,
|
||||
get_URL_options : this.get_URL_options
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
|
||||
// 取得每一個章節的各個影像內容資料。 get_chapter_data()
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// console.log(html);
|
||||
if (work_data.removed && !work_data.chapter_filtered) {
|
||||
var next_chapter_data = html.between('nextChapterData =', ';');
|
||||
// console.log(next_chapter_data || html);
|
||||
if (next_chapter_data
|
||||
// next_chapter_data==='' @
|
||||
// https://www.mh1234.com/comic/9384.html
|
||||
&& (next_chapter_data = JSON.parse(next_chapter_data))
|
||||
&& next_chapter_data.id > 0) {
|
||||
library_namespace.debug('add chapter: '
|
||||
+ next_chapter_data.id);
|
||||
next_chapter_data.url = this.work_URL(work_data.id)
|
||||
+ next_chapter_data.id + '.html';
|
||||
// 動態增加章節。
|
||||
work_data.chapter_count++;
|
||||
work_data.chapter_list.push(next_chapter_data);
|
||||
} else {
|
||||
// console.log(html);
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(work_data.chapter_list);
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1],
|
||||
// <!--全站头部导航 结束-->\n<script>
|
||||
chapter_data_code = html
|
||||
// 930mh.js: Error on http://www.duzhez.com/manhua/449/245193.html
|
||||
&& (html.match(/<script>(;var [\s\S]+?)<\/script>/)
|
||||
// for manhuaniu.js 2021/1/19 改版
|
||||
|| html.match(/<script>(var siteName = "";[\s\S]+?)<\/script>/));
|
||||
// console.trace(chapter_data_code);
|
||||
if (!chapter_data_code) {
|
||||
library_namespace.warn({
|
||||
// gettext_config:{"id":"unable-to-parse-chapter-data-for-«$1»-§$2"}
|
||||
T : [ '無法解析《%1》§%2 之章節資料!', work_data.title, chapter_NO ]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// console.trace(chapter_data_code[1]);
|
||||
// eval(chapter_data_code[1].replace(/;var /g, ';chapter_data.'));
|
||||
chapter_data_code[1].split(';var ').forEach(function(token) {
|
||||
if (!token.includes('='))
|
||||
return;
|
||||
|
||||
token = token.replace(/^\s*var\s/, '');
|
||||
// console.trace(token);
|
||||
try {
|
||||
eval('chapter_data.' + token);
|
||||
} catch (e) {
|
||||
console.error(new SyntaxError(
|
||||
// Ignore SyntaxError. e.g.,
|
||||
// https://www.gufengmh8.com/manhua/wodeshashounanyou/742494.html
|
||||
// ;var pageTitle = "我的杀手男友第65、66话 "肉偿在线观看";
|
||||
'parse_chapter_data: ' + token));
|
||||
}
|
||||
});
|
||||
// console.log(chapter_data);
|
||||
|
||||
// 設定必要的屬性。
|
||||
chapter_data.title = get_label(html.between('<h2>', '</h2>'))
|
||||
// e.g., mh1234.js has no <h2>...</h2>'
|
||||
|| chapter_data.title;
|
||||
// e.g., 'images/comic/4/7592/'
|
||||
var path = encodeURI(chapter_data.chapterPath);
|
||||
// console.log(chapter_data.chapterImages);
|
||||
if (global.CryptoJS
|
||||
&& typeof chapter_data.chapterImages === 'string') {
|
||||
// console.log(chapter_data.chapterImages);
|
||||
// console.log(this.crypto);
|
||||
|
||||
/**
|
||||
* <code>
|
||||
JSON.parse(CryptoJS.AES.decrypt(chapterImages,CryptoJS.enc.Utf8.parse("6133AFVvxas55841"),{iv:CryptoJS.enc.Utf8.parse("A25vcxQQrpmbV51t"),mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.Pkcs7}).toString(CryptoJS.enc.Utf8))
|
||||
</code>
|
||||
*
|
||||
* @see https://segmentfault.com/q/1010000011225051
|
||||
*/
|
||||
chapter_data.chapterImages =
|
||||
// 使用 CryptoJS https://code.google.com/archive/p/crypto-js/
|
||||
// https://github.com/brix/crypto-js
|
||||
JSON.parse(CryptoJS.AES.decrypt(chapter_data.chapterImages,
|
||||
// 930mh.js key 密鑰 "十六位字符作为密钥"
|
||||
CryptoJS.enc.Utf8.parse(this.crypto.key), {
|
||||
iv : CryptoJS.enc.Utf8.parse(this.crypto.iv),
|
||||
mode : CryptoJS.mode.CBC,
|
||||
padding : CryptoJS.pad.Pkcs7
|
||||
}).toString(CryptoJS.enc.Utf8));
|
||||
}
|
||||
|
||||
// assert: Array.isArray(chapter_data.chapterImages)
|
||||
chapter_data.image_list = chapter_data.chapterImages.map(function(
|
||||
url) {
|
||||
return {
|
||||
// e.g., 外挂仙尊 184 第76话
|
||||
// 但是這還是沒辦法取得圖片...
|
||||
url : encodeURI(/^https?:\/\//.test(url) ? url
|
||||
//
|
||||
: path + url)
|
||||
}
|
||||
});
|
||||
|
||||
if (chapter_data.image_list.length === 0
|
||||
&& (html = html.between('class="ip-notice">', '<'))) {
|
||||
// 避免若連內容被屏蔽,會從頭檢查到尾都沒有成果。
|
||||
work_data.chapter_filtered = true;
|
||||
if (work_data.removed) {
|
||||
library_namespace.info({
|
||||
// gettext_config:{"id":"§$1-has-been-blocked-and-no-longer-attempts-to-resolve-other-chapters"}
|
||||
T : [ '§%1 已被屏蔽,不再嘗試解析其他章節。', chapter_NO ]
|
||||
});
|
||||
} else {
|
||||
library_namespace.warn(get_label(html));
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(chapter_data);
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_SinMH_comics_crawler(configuration) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
if (configuration.search_URL === 'API') {
|
||||
configuration.search_URL = default_configuration.search_URL_API;
|
||||
// 因為不見得會執行到 parse_search_result(),不可放在 parse_search_result() 裡面。
|
||||
if (!configuration.id_of_search_result) {
|
||||
// gufengmh.js: using 'slug'
|
||||
configuration.id_of_search_result = 'id';
|
||||
}
|
||||
configuration.title_of_search_result = 'title';
|
||||
}
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
return new library_namespace.work_crawler(configuration);
|
||||
}
|
||||
|
||||
// for CeL.application.net.work_crawler.sites.SinMH2013
|
||||
new_SinMH_comics_crawler.default_configuration = default_configuration;
|
||||
|
||||
return new_SinMH_comics_crawler;
|
||||
}
|
288
app/node_modules/cejs/application/net/work_crawler/sites/SinMH2013.js
generated
vendored
288
app/node_modules/cejs/application/net/work_crawler/sites/SinMH2013.js
generated
vendored
@@ -1,288 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading SinMH CMS? comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸常見漫畫管理系統: 可能是 圣樱漫画管理系统 (圣樱CMS) 2013年版 的工具。
|
||||
*
|
||||
* TODO: https://m.999comics.com/
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.SinMH2013(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* modify from archive/2manhua.js
|
||||
*
|
||||
* 57mh 介面程式碼類似於 999comics。manhuagui 似乎是在這基礎上經過修改? 57mh 這一批介面外觀與
|
||||
* CeL.application.net.work_crawler.sites.SinMH 類似,但介面程式碼有些差距。或可稱為
|
||||
* CeL.application.net.work_crawler.sites.SinMH2013 或
|
||||
* CeL.application.net.work_crawler.sites.SMH。
|
||||
*
|
||||
* @see https://www.999comics.com/static/scripts/main.js?v=1.0 MHD (MHD: 漫画岛
|
||||
* http://www.manhuadao.com/book/baiqianjiadeyaoguaiwangzi/)
|
||||
* http://www.wuqimh.com/templates/wuqi/default/scripts/main.js?v=1.0.3 MHW
|
||||
* https://cf.hamreus.com/scripts_tw/main_EB87BCACAD66FA68CA738D0C925DC508.js
|
||||
* main_EB87BCACAD66FA68CA738D0C925DC508.js 末: SMH = { update: "2013/4/1" }
|
||||
*
|
||||
* @since 2019/6/18 6:13:11 模組化 MHD模板?
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/57mh.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.SinMH2013',
|
||||
|
||||
require : 'application.net.work_crawler.'
|
||||
//
|
||||
+ '|application.net.work_crawler.sites.SinMH.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 所有的子檔案要修訂註解說明時,應該都要順便更改在CeL.application.net.comic中Comic_site.prototype內的母comments,並以其為主體。
|
||||
|
||||
// 本站常常無法取得圖片,因此得多重新檢查。
|
||||
// recheck:從頭檢測所有作品之所有章節與所有圖片。不會重新擷取圖片。對漫畫應該僅在偶爾需要從頭檢查時開啟此選項。
|
||||
// recheck : true,
|
||||
// 當無法取得chapter資料時,直接嘗試下一章節。在手動+監視下recheck時可併用此項。
|
||||
// skip_chapter_data_error : true,
|
||||
|
||||
// 36145 前任攻略 19话 057.jpg
|
||||
// {Natural}MIN_LENGTH:最小容許圖案檔案大小 (bytes)。
|
||||
MIN_LENGTH : 400,
|
||||
|
||||
// 當圖像檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。
|
||||
skip_error : true,
|
||||
|
||||
// one_by_one : true,
|
||||
|
||||
// base_URL : '',
|
||||
|
||||
// 取得伺服器列表。
|
||||
// use_server_cache : true,
|
||||
// http://www.5qmh.com/templates/wuqi/default/scripts/configs.js?v=1.0.3
|
||||
server_URL : 'templates/wuqi/default/scripts/configs.js',
|
||||
parse_server_list : function(html) {
|
||||
// console.log(html);
|
||||
var server_list = [];
|
||||
Object.values(JSON.parse(
|
||||
// var pageConfig = { 'host': { ...
|
||||
html.replace(/^[^{]+/, '').replace(/[^}]+$/, '')
|
||||
//
|
||||
.replace(/'/g, '"')).host)
|
||||
//
|
||||
.forEach(function(_server_list) {
|
||||
_server_list.forEach(function(server) {
|
||||
if (server) {
|
||||
if (!server.endsWith('/'))
|
||||
server += '/';
|
||||
server_list.push(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
return server_list;
|
||||
},
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : 'handler/suggest?cb=_&key=',
|
||||
parse_search_result : function(html) {
|
||||
// console.log(html);
|
||||
// e.g.,
|
||||
// _([{"id":"28015","t":"民工勇者","u":"/comic/28015/","cid":"/comic/28015/0208","ct":"207话","s":"0"},{"id":"28093","t":"无敌勇者王(民工勇者)","u":"/comic/28093/","cid":"/comic/28093/02","ct":"199话","s":"0"}])
|
||||
var id_data = html ? JSON.parse(html.between('(').replace(
|
||||
/\)[^)]*$/, '')) : [];
|
||||
return [ id_data, id_data ];
|
||||
},
|
||||
id_of_search_result : function(cached_data) {
|
||||
return cached_data.id | 0;
|
||||
},
|
||||
title_of_search_result : 't',
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
// e.g., http://www.5qmh.com/28437/
|
||||
return work_id + '/';
|
||||
},
|
||||
parse_work_data : library_namespace.SinMH.default_configuration.parse_work_data,
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
// console.log(html);
|
||||
work_data.chapter_list = [];
|
||||
var matched, part_title, part_NO = 0, page,
|
||||
// 2017/7/22: 57mh.js 章節編號順序為 21 43 65.
|
||||
// e.g., 银魂 http://www.wuqimh.com/8/
|
||||
// matched: [ all, part_title, page inner ]
|
||||
PATTERN_page = /<h4><span>(.+?)<\/span><\/h4>|<ul (?:style="display:block;")?>([\s\S]+?)<\/ul>/g,
|
||||
/**
|
||||
* e.g., 57mh.js 只發現過多page頁面,沒有發現過多part <code>
|
||||
|
||||
<h4><span>单话</span></h4><div class="chapter-page cf mt10" id="chpater-page-1"><ul>
|
||||
<li ><a href="javascript:;" title="第1页">第1页<i></i></a></li>
|
||||
...
|
||||
<li class="on"><a href="javascript:;" title="第7页">第7页<i></i></a></li>
|
||||
</ul></div>
|
||||
|
||||
<div class="chapter-list cf mt10" id="chpater-list-1"><ul >
|
||||
<li><a href="/8/065.html" title="155话" class="status0" target="_blank"><span>155话<i>21p</i></span></a></li>
|
||||
...
|
||||
</ul></div>
|
||||
|
||||
</code>
|
||||
*
|
||||
* e.g., 999comics.js 只發現過多part,沒有發現過多page頁面 <code>
|
||||
|
||||
<h4><span>單話</span></h4>
|
||||
<div class="chapter-list cf mt10">
|
||||
<ul style="display:block;">
|
||||
<li><a href="/comic/26060/72192e0511125993c37cbd5264c971b6.html" title="第371回" class="status0" target="_blank"><span>371回</span></a></li>
|
||||
...
|
||||
</ul>
|
||||
</div><h4><span>番外篇</span></h4>
|
||||
<div class="chapter-list cf mt10">
|
||||
<ul style="display:block;">
|
||||
<li><a href="/comic/26060/b0da0b7c0767f1332684a0ae111b3696.html" title="Jump next出張篇" class="status0" target="_blank"><span>Jump</span></a></li>
|
||||
...
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</code> 單話 番外篇 單行本
|
||||
*
|
||||
* <code>
|
||||
|
||||
<li><a href="/comic/25652/072.html" title="72回 碧霞坠" class="status0" target="_blank"><span>72回<i>14p</i></span></a></li>
|
||||
|
||||
</code>
|
||||
*/
|
||||
PATTERN_chapter =
|
||||
// matched: [ all, href, title, inner ]
|
||||
/<li><a href="([^"<>]+)" title="([^"<>]+)"[^<>]*>(.+?)<\/a><\/li>/g;
|
||||
while (page = PATTERN_page.exec(html)) {
|
||||
if (page[1]) {
|
||||
part_title = get_label(page[1]);
|
||||
part_NO++;
|
||||
// library_namespace.info('part_title: ' + part_title);
|
||||
continue;
|
||||
}
|
||||
|
||||
page = page[2];
|
||||
// console.log(page);
|
||||
var chapter_list = [];
|
||||
while (matched = PATTERN_chapter.exec(page)) {
|
||||
matched[2] = matched[2].trim();
|
||||
if (matched[3] = matched[3].between('<i>', '</i>')) {
|
||||
// add page count
|
||||
matched[2] = matched[2] + ' ' + matched[3];
|
||||
}
|
||||
chapter_list.push({
|
||||
part : part_title,
|
||||
part_NO : part_NO,
|
||||
title : get_label(matched[2]),
|
||||
url : encodeURI(matched[1])
|
||||
});
|
||||
}
|
||||
if (!this.no_need_to_revert)
|
||||
chapter_list = chapter_list.reverse();
|
||||
work_data.chapter_list.append(chapter_list);
|
||||
}
|
||||
|
||||
work_data.chapter_list.part_NO = part_NO;
|
||||
|
||||
return;
|
||||
|
||||
// 已被棄置的排序方法。
|
||||
work_data.chapter_list
|
||||
.sort(function(chapter_data_1, chapter_data_2) {
|
||||
var matched_1 = chapter_data_1.url.match(/(\d+)\.htm/),
|
||||
// 依照.url排序。
|
||||
matched_2 = chapter_data_2.url.match(/(\d+)\.htm/);
|
||||
if (matched_1 && matched_2) {
|
||||
return matched_1[1] - matched_2[1];
|
||||
}
|
||||
return chapter_data_1.url < chapter_data_2.url ? -1 : 1;
|
||||
// 依照.title排序。
|
||||
return chapter_data_1.title < chapter_data_2.title ? -1
|
||||
: 1;
|
||||
});
|
||||
},
|
||||
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// decode chapter data
|
||||
function decode(code) {
|
||||
code = eval(code).replace(/^[^=]+/, 'code');
|
||||
return eval(code);
|
||||
}
|
||||
|
||||
var chapter_data = html
|
||||
.match(/[>;\n\s]var\s+cInfo\s*=\s*(\{[\s\S]+?\})[;\n]/);
|
||||
if (chapter_data) {
|
||||
// 999comics.js
|
||||
// console.log(chapter_data[1]);
|
||||
eval('chapter_data=' + chapter_data[1]);
|
||||
// https://www.999comics.com/static/scripts/core2.js?v=20180206
|
||||
// preload: function(t) {...}
|
||||
// r("<img />")[0].src = "//www.999comics.com/g.php?"+o.cid+'/'+
|
||||
// o.fs[i - 1]
|
||||
chapter_data.fs = chapter_data.fs.map(function(i) {
|
||||
return this.full_URL('g.php?'
|
||||
//
|
||||
+ chapter_data.cid + '/' + i);
|
||||
}, this);
|
||||
} else if (chapter_data = html.between(
|
||||
'<script type="text/javascript">eval', '\n')) {
|
||||
// 57mh.js
|
||||
chapter_data = decode(chapter_data);
|
||||
}
|
||||
if (!chapter_data) {
|
||||
library_namespace.warn({
|
||||
// gettext_config:{"id":"unable-to-parse-chapter-data-for-«$1»-§$2"}
|
||||
T : [ '無法解析《%1》§%2 之章節資料!', work_data.title, chapter_NO ]
|
||||
});
|
||||
return;
|
||||
}
|
||||
// console.log(chapter_data);
|
||||
|
||||
// 設定必要的屬性。
|
||||
chapter_data.title = get_label(html.between('<h2>', '</h2>'));
|
||||
chapter_data.image_count = chapter_data.fc;
|
||||
chapter_data.image_list = chapter_data.fs;
|
||||
if (!chapter_data.fs.at(-1)) {
|
||||
// for http://www.5qmh.com/6908/0296.html?p=9
|
||||
chapter_data.fs.pop();
|
||||
chapter_data.image_count--;
|
||||
}
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_SinMH2013_comics_crawler(configuration) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
return new library_namespace.work_crawler(configuration);
|
||||
}
|
||||
|
||||
return new_SinMH2013_comics_crawler;
|
||||
}
|
168
app/node_modules/cejs/application/net/work_crawler/sites/ace.js
generated
vendored
168
app/node_modules/cejs/application/net/work_crawler/sites/ace.js
generated
vendored
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading YOUNG ACE UP, TYPE-MOON comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載 KADOKAWA CORPORATION webエース ヤングエースUP(アップ)
|
||||
* Webコミック、TYPE-MOONコミックエース 漫画 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.ace({
|
||||
// configuration
|
||||
site : '' || CeL.get_script_name()
|
||||
}).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @since 2020/4/26 5:58:57 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.ja-JP/youngaceup.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.ace',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
// 日本的網路漫畫網站習慣刪掉舊章節,因此每一次都必須從頭檢查。
|
||||
// e.g., ヱデンズボゥイ
|
||||
recheck : true,
|
||||
|
||||
// one_by_one : true,
|
||||
base_URL : 'https://web-ace.jp/',
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return 'contents/' + work_id + '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
var work_data = JSON.parse(html.between(
|
||||
'<script type="application/ld+json">', '</script>'));
|
||||
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
// 放在這裡以預防被extract_work_data()覆蓋。
|
||||
Object.assign(work_data, {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(html.between('<h1>', '</h1>')),
|
||||
authors : html.all_between('<p class="author">', '</p>').map(
|
||||
get_label),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
subtitle : get_label(html.between('<p class="subtitle">',
|
||||
'</p>')),
|
||||
description : get_label(html.between(
|
||||
'<div class="description">', '</div>')),
|
||||
status : html.between('<p class="genre">', '</p>').replace(
|
||||
'ジャンル:', '').split(' / ').map(get_label),
|
||||
last_update : get_label(html.between(
|
||||
'<span class="updated-date">', '</span>'))
|
||||
|| (new Date).toISOString(),
|
||||
next_update : html.all_between(
|
||||
'<span class="label_day-of-the-week">', '</span>').map(
|
||||
get_label)
|
||||
// 隔週火曜日更新 次回更新予定日:2018年11月27日
|
||||
.map(function(token) {
|
||||
return token.replace('次回更新予定日:', '');
|
||||
})
|
||||
});
|
||||
|
||||
work_data.author = work_data.authors.map(function(name) {
|
||||
// 原作: 漫画: キャラクター原案:
|
||||
return name.replace(/^[^:]+:/, '').trim();
|
||||
});
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
chapter_list_URL : function(work_id, work_data) {
|
||||
return this.work_URL(work_id) + 'episode/';
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
// <div class="container" id="read">
|
||||
html = html.between(' id="read">', '</section>')
|
||||
// <ul class="table-view">
|
||||
.between('<ul', '</ul>');
|
||||
|
||||
work_data.chapter_list = [];
|
||||
var some_skipped;
|
||||
html.each_between('<li', '</li>', function(token) {
|
||||
var matched = token.between('<p class="yudo_wa">', '</div>');
|
||||
if (matched) {
|
||||
library_namespace.info(work_data.title + ': '
|
||||
+ get_label(matched).replace(/\s{2,}/g, ' '));
|
||||
some_skipped = true;
|
||||
return;
|
||||
}
|
||||
matched = token
|
||||
.match(/<a [^<>]*?href=["']([^"'<>]+)["'][^<>]*>/);
|
||||
var chapter_data = {
|
||||
title : get_label(token
|
||||
//
|
||||
.between('<p class="text-bold">', '</p>')),
|
||||
date : token.between('<span class="updated-date">',
|
||||
'</span>'),
|
||||
// 直接取得圖片網址資訊。
|
||||
url : matched[1] + 'json/'
|
||||
};
|
||||
work_data.chapter_list.push(chapter_data);
|
||||
});
|
||||
work_data.chapter_list.reverse();
|
||||
|
||||
if (some_skipped) {
|
||||
// 因為中間的章節可能已經被下架,因此依章節標題來定章節編號。
|
||||
this.set_chapter_NO_via_title(work_data);
|
||||
}
|
||||
},
|
||||
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// console.log(html);
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
Object.assign(chapter_data, {
|
||||
// 設定必要的屬性。
|
||||
image_list : JSON.parse(html)
|
||||
});
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_ace_comics_crawler(configuration, callback, initializer) {
|
||||
// library_namespace.set_debug(9);
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
configuration.base_URL += (configuration.site
|
||||
// || library_namespace.get_script_name()
|
||||
) + '/';
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
return crawler;
|
||||
}
|
||||
|
||||
return new_ace_comics_crawler;
|
||||
}
|
346
app/node_modules/cejs/application/net/work_crawler/sites/baozimh.js
generated
vendored
346
app/node_modules/cejs/application/net/work_crawler/sites/baozimh.js
generated
vendored
@@ -1,346 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading baozimh comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載 包子漫畫 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.baozimh({
|
||||
// configuration
|
||||
site : '' || CeL.get_script_name()
|
||||
}).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @since 2022/11/3 5:55:24
|
||||
* @since 2022/11/3 5:55:24 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hant-TW/baozimh.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.baozimh',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
/**
|
||||
* <code>
|
||||
<a href="/user/page_direct?comic_id=yuanlaiwoshixiuxiandalao-luanshijiaren&section_slot=0&chapter_slot=0" rel="noopener" class="comics-chapters__item" data-v-0c0802bc><div style="flex: 1;" data-v-0c0802bc><span data-v-0c0802bc>預告</span></div></a>
|
||||
<code>
|
||||
*/
|
||||
var PATTERN_chapter_link = /<a [^<>]*?href="([^<>"]+?)" [^<>]* class="comics-chapters__item"[^<>]*>([\s\S]+?)<\/a>/g;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
base_URL : 'https://www.baozimh.com/',
|
||||
|
||||
// 最小容許圖案檔案大小 (bytes)。
|
||||
// 對於極少出現錯誤的網站,可以設定一個比較小的數值,並且設定.allow_EOI_error=false。因為這類型的網站要不是無法取得檔案,要不就是能夠取得完整的檔案;要取得破損檔案,並且已通過EOI測試的機會比較少。
|
||||
// 對於有些圖片只有一條細橫桿的情況。
|
||||
MIN_LENGTH : 50,
|
||||
// e.g., 都是黑丝惹的祸2 0409 第二季 第409话 因为我喜欢他
|
||||
|
||||
// allow .jpg without EOI mark.
|
||||
// allow_EOI_error : true,
|
||||
// 當圖像檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。
|
||||
skip_error : true,
|
||||
// e.g., woshenshangyoutiaolong-pikapi 我身上有条龙/
|
||||
// 0590 第590话 父母过往/woshenshangyoutiaolong-pikapi-590-017.jpg
|
||||
|
||||
// one_by_one : true,
|
||||
|
||||
acceptable_types : 'jpg|webp',
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : 'search?q=',
|
||||
parse_search_result : function(html, get_label) {
|
||||
html = html.between('<div class="pure-g classify-items">');
|
||||
// console.log(html);
|
||||
var id_list = [], id_data = [];
|
||||
html.each_between('<a href="/comic/', '</a>', function(text) {
|
||||
id_list.push(text.between(null, '"'));
|
||||
id_data.push(get_label(text.between('title="', '"')));
|
||||
});
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return 'comic/' + work_id;
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(html.between(
|
||||
// <h1 class="comics-detail__title"
|
||||
// data-v-6f225890>原來我是修仙大佬</h1>
|
||||
'<h1 class="comics-detail__title"', '</h1>').between('>')),
|
||||
author : get_label(html.between(
|
||||
// <h2 class="comics-detail__author" data-v-6f225890>亂室佳人</h2>
|
||||
'<h2 class="comics-detail__author"', '</h2>').between('>')),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
tags : html.all_between('<span class="tag"', '</span>')
|
||||
//
|
||||
.map(function(tag) {
|
||||
return get_label(tag.between('>'));
|
||||
}),
|
||||
last_update : get_label(html.between('最新:').between('<em',
|
||||
'</em>').between('>').replace(/\((.+) 更新\)/, '$1')),
|
||||
latest_chapter : get_label(html.between('最新:', '</a>')),
|
||||
description : get_label(html.between('<p class="comics-detail',
|
||||
'</p>').between('>')),
|
||||
/**
|
||||
* cover image<code>
|
||||
<amp-img alt="原來我是修仙大佬" width="180" height="240" layout="responsive" src="https://static-tw.baozimh.com/cover/yuanlaiwoshixiuxiandalao-luanshijiaren.jpg" data-v-6f225890>
|
||||
<code>
|
||||
*/
|
||||
cover_image : html.between('layout="responsive" src="', '"')
|
||||
};
|
||||
|
||||
// 由 meta data 取得作品資訊。
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
var _this = this;
|
||||
// reset chapter list
|
||||
work_data.chapter_list = [];
|
||||
var part_count = html.all_between('<div class="section-title"',
|
||||
'</div>').length;
|
||||
var skip_latest_chapters = true;
|
||||
html.each_between('<div class="section-title"', null,
|
||||
//
|
||||
function(text) {
|
||||
/**
|
||||
* <code>
|
||||
<div class="section-title" data-v-6f225890>章節目錄</div>
|
||||
<code>
|
||||
*/
|
||||
var part_title = text.between('>', '</div>');
|
||||
// 最新章節 最新章节
|
||||
if (/^最新章[節节]$/.test(part_title)
|
||||
// 假如只有一個 part,那就必須留下最新章節。 e.g., 妖精种植手册黑白轮回篇
|
||||
&& (skip_latest_chapters = part_count > 1)) {
|
||||
return;
|
||||
}
|
||||
_this.set_part(work_data, part_title);
|
||||
// console.log(text);
|
||||
var matched;
|
||||
while (matched = PATTERN_chapter_link.exec(text)) {
|
||||
var chapter_data = {
|
||||
// reset sub_chapter_list
|
||||
sub_chapter_list : null,
|
||||
title : get_label(matched[2]),
|
||||
// TODO: fix "&"
|
||||
url : matched[1].replace(/&/g, '&')
|
||||
};
|
||||
_this.add_chapter(work_data, chapter_data);
|
||||
}
|
||||
});
|
||||
|
||||
// console.trace([ part_count, skip_latest_chapters ]);
|
||||
if (!skip_latest_chapters) {
|
||||
// 最新章節 為倒序。
|
||||
// e.g., 妖精种植手册黑白轮回篇
|
||||
// https://cn.baozimh.com/comic/yaojingchongzhishouceheibailunhuipian-dazui
|
||||
// 我独自升级
|
||||
// https://www.baozimh.com/comic/woduzishengji-duburedicestudio_n6ip31
|
||||
work_data.inverted_order = true;
|
||||
}
|
||||
|
||||
// console.log(work_data.chapter_list);
|
||||
},
|
||||
|
||||
pre_parse_chapter_data
|
||||
// 執行在解析章節資料 process_chapter_data() 之前的作業 (async)。
|
||||
// 必須自行保證執行 callback(),不丟出異常、中斷。
|
||||
: function(XMLHttp, work_data, callback, chapter_NO) {
|
||||
// console.log(XMLHttp);
|
||||
// console.log(work_data);
|
||||
|
||||
// 模擬歸一化
|
||||
// https://www.webmota.com/comic/chapter/wangyouzhijinzhanfashi-samanhua/0_188.html
|
||||
// https://cn.webmota.com/comic/chapter/wangyouzhijinzhanfashi-samanhua/0_188.html
|
||||
function simulated_normalized_url(url) {
|
||||
return url.replace(/\/\/[a-z]+\./, '//www.');
|
||||
}
|
||||
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
// console.trace(chapter_data);
|
||||
if (!chapter_data.sub_chapter_list) {
|
||||
// get_chapter_list() 獲得的是動態的 url,會轉成靜態的。
|
||||
chapter_data.sub_chapter_list = [ XMLHttp.responseURL ];
|
||||
}
|
||||
if (chapter_data.static_url) {
|
||||
if (simulated_normalized_url(chapter_data.static_url) !== simulated_normalized_url(chapter_data.sub_chapter_list[0])) {
|
||||
// console.log(chapter_data);
|
||||
library_namespace.warn('§' + chapter_NO + '《'
|
||||
+ chapter_data.title
|
||||
+ '》: 從上一章的章節內容頁面獲得的 URL 不同於從章節列表獲得的 URL:\n '
|
||||
+ chapter_data.static_url + '\n '
|
||||
+ chapter_data.sub_chapter_list[0]);
|
||||
}
|
||||
// free
|
||||
delete chapter_data.static_url;
|
||||
}
|
||||
|
||||
var html = XMLHttp.responseText, matched, next_chapter_url = html;
|
||||
// console.log(html);
|
||||
|
||||
/**
|
||||
* <code>
|
||||
|
||||
// 可能有上下兩個 `<div class="next_chapter">`,取後一個。
|
||||
<div class="chapter-main scroll-mode"><div class="next_chapter"><a href="https://cn.webmota.com/comic/chapter/dubuxiaoyao-zhangyuewenhua/0_35.html#bottom">
|
||||
点击进入上一页
|
||||
</a></div>
|
||||
|
||||
<div class="next_chapter"><a href="https://www.webmota.com/comic/chapter/shenzhita-siu/0_738_2.html">
|
||||
點擊進入下一頁
|
||||
<span class="iconfont icon-xiangxia"></span></a></div>
|
||||
|
||||
<div class="next_chapter"><a href="https://cn.webmota.com/comic/chapter/shenzhita-siu/0_738.html">
|
||||
点击进入下一话
|
||||
<span class="iconfont icon-xiayibu"></span></a></div>
|
||||
<code>
|
||||
*/
|
||||
while (matched = next_chapter_url.between(' class="next_chapter">'))
|
||||
next_chapter_url = matched;
|
||||
next_chapter_url = next_chapter_url
|
||||
// 去掉網頁錨點。
|
||||
&& next_chapter_url.between(' href="', '"').replace(/#.*$/, '');
|
||||
if (!next_chapter_url
|
||||
//
|
||||
|| !/^_\d/.test(simulated_normalized_url(next_chapter_url)
|
||||
// 確定 url 以本章節 url 開頭。
|
||||
.between(simulated_normalized_url(
|
||||
//
|
||||
chapter_data.sub_chapter_list[0]).replace(/\.html$/, '')))) {
|
||||
// console.trace('下一話');
|
||||
// assert: next_chapter_url 為下一話的靜態 url。
|
||||
var next_chapter_data = work_data.chapter_list[chapter_NO];
|
||||
if (next_chapter_url && next_chapter_data) {
|
||||
// 做個記錄。
|
||||
if (false) {
|
||||
console.trace([ chapter_NO, next_chapter_url,
|
||||
next_chapter_data ]);
|
||||
}
|
||||
next_chapter_data.static_url = next_chapter_url;
|
||||
// 直接從靜態網頁獲取章節內容,避免採用 CGI。
|
||||
if (!next_chapter_data.sub_chapter_list)
|
||||
next_chapter_data.sub_chapter_list = [ next_chapter_url ];
|
||||
}
|
||||
|
||||
// console.trace(chapter_data);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// assert: next_chapter_url 為下一頁的靜態 url。
|
||||
// console.trace('下一頁');
|
||||
|
||||
// 做個記錄。
|
||||
chapter_data.sub_chapter_list.push(next_chapter_url);
|
||||
|
||||
// console.trace(next_chapter_url);
|
||||
this.get_URL(next_chapter_url, function(XMLHttp, error) {
|
||||
if (!chapter_data.next_chapter_HTMLs)
|
||||
chapter_data.next_chapter_HTMLs = [];
|
||||
chapter_data.next_chapter_HTMLs.push(XMLHttp.responseText);
|
||||
this.pre_parse_chapter_data(XMLHttp, work_data, callback,
|
||||
chapter_NO);
|
||||
});
|
||||
},
|
||||
// 取得每一個章節的各個影像內容資料。 get_chapter_data()
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
|
||||
var image_list = chapter_data.image_list = [], url_Set = new Set;
|
||||
|
||||
function handle_html(html) {
|
||||
html = html
|
||||
// <div class="chapter-main scroll-mode">
|
||||
.between('<div class="chapter-main scroll-mode">');
|
||||
// console.trace(html);
|
||||
|
||||
/**
|
||||
* <code>
|
||||
<img src="https://s1.baozimh.com/scomic/yuanlaiwoshixiuxiandalao-luanshijiaren/0/0-vmac/1.jpg" alt="原來我是修仙大佬 - 預告 - 1" width="1200" height="3484" data-v-25d25a4e>
|
||||
<code>
|
||||
*/
|
||||
html.each_between('<img src="', '>', function(text) {
|
||||
var url = encodeURI(text.between(null, '"'));
|
||||
if (url_Set.has(url)) {
|
||||
// 前面的部分會重複3張圖片。
|
||||
return;
|
||||
}
|
||||
url_Set.add(url);
|
||||
image_list.push({
|
||||
title : get_label(text.between('alt="', '"')),
|
||||
url : url
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handle_html(html);
|
||||
|
||||
if (chapter_data.next_chapter_HTMLs) {
|
||||
chapter_data.next_chapter_HTMLs.forEach(handle_html);
|
||||
// free
|
||||
delete chapter_data.next_chapter_HTMLs;
|
||||
}
|
||||
// console.log(image_list);
|
||||
|
||||
return chapter_data;
|
||||
},
|
||||
|
||||
is_limited_image_url : function(image_url, image_data) {
|
||||
// e.g.,
|
||||
// https://cn.webmota.com/comic/chapter/zunshang-mankewenhua_d/0_220.html
|
||||
if (typeof image_url === 'string'
|
||||
// https://static-tw.baozimh.com/cover/404.jpg
|
||||
&& image_url.endsWith('/cover/404.jpg')) {
|
||||
return '404 Not Found';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_baozimh_comics_crawler(configuration, callback, initializer) {
|
||||
// library_namespace.set_debug(9);
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
return crawler;
|
||||
}
|
||||
|
||||
return new_baozimh_comics_crawler;
|
||||
}
|
744
app/node_modules/cejs/application/net/work_crawler/sites/comico.js
generated
vendored
744
app/node_modules/cejs/application/net/work_crawler/sites/comico.js
generated
vendored
@@ -1,744 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading comico comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載 韓國 NHN comico Corp. 漫畫 的工具。
|
||||
*
|
||||
* 2021/12/22 改版
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.comico(configuration, function(crawler) {
|
||||
start_crawler(crawler, typeof module === 'object' && module);
|
||||
}, function(crawler) {
|
||||
setup_crawler(crawler, typeof module === 'object' && module);
|
||||
});
|
||||
|
||||
</code>
|
||||
*
|
||||
* @see http://comico.kr/
|
||||
*
|
||||
* @since 2018/8/19 5:49:8 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/comico.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.comico',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function add_navigation_data(data, html) {
|
||||
var navigation_data;
|
||||
try {
|
||||
navigation_data = JSON.parse(html.between(
|
||||
// コミコ 日文版有時 json 結構有問題。
|
||||
// e.g., http://www.comico.jp/articleList.nhn?titleNo=3410
|
||||
'<script type="application/ld+json">', '</script>')
|
||||
// e.g., http://www.comico.com.tw/2870/17/
|
||||
.replace(/\t+"/g, '"'));
|
||||
} catch (e) {
|
||||
// TODO: handle exception
|
||||
}
|
||||
return Object.assign(data, navigation_data);
|
||||
}
|
||||
|
||||
var PATTERN_work_info = /<(p|div) class="[^<>"]+?__(author|(?:sub-)?description|meta)">([\s\S]+?)<\/\1>/g,
|
||||
// assert: (NO_ticket_notified>=0) === false
|
||||
// gettext_config:{"id":"no-read-volumes-are-available"}
|
||||
NO_ticket_notified = '已無閱讀卷可用。', auto_use_ticket_notified,
|
||||
// 可以用閱讀卷閱讀的章節。
|
||||
READABLE_FLAG = 'W',
|
||||
//
|
||||
default_configuration = {
|
||||
// 所有的子檔案要修訂註解說明時,應該都要順便更改在CeL.application.net.comic中Comic_site.prototype內的母comments,並以其為主體。
|
||||
|
||||
// e.g., 20099 俺のメンタルは限界です\0003 3話 「マンガを描く原点」\20099-3-022.jpg
|
||||
MIN_LENGTH : 180,
|
||||
|
||||
// one_by_one : true,
|
||||
base_URL : '',
|
||||
|
||||
// have already read the chapter
|
||||
set_downloaded_if_read : true,
|
||||
|
||||
convert_id : {
|
||||
// switch
|
||||
// 警告: 需要自行呼叫 insert_id_list(id_list);
|
||||
adult : function(insert_id_list, get_label) {
|
||||
// TW only: 此前被當作是一般作品。
|
||||
library_namespace.info([ this.id + ': ', {
|
||||
// gettext_config:{"id":"subsequent-titles-of-the-work-are-considered-to-be-web-limited-works"}
|
||||
T : '此後的作品標題都被當作是網頁限定作品。'
|
||||
} ]);
|
||||
// webonly
|
||||
this.adult = true;
|
||||
insert_id_list();
|
||||
}
|
||||
},
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : function(work_title) {
|
||||
var url = (this.adult ? 'webonly/' : '')
|
||||
// ↑ webonly, オトナ限定: TW only
|
||||
+ 'search/index.nhn?searchWord='
|
||||
+ encodeURIComponent(work_title.replace(/\s+\d+$/, ''));
|
||||
if (this.base_URL.includes('\/\/plus.comico')) {
|
||||
url = this.base_URL
|
||||
.replace('\/\/plus.comico', '\/\/www.comico')
|
||||
+ url;
|
||||
}
|
||||
return url;
|
||||
},
|
||||
// 每個項目的<li>開頭。
|
||||
search_head_token : '<li class="list-article02__item">',
|
||||
// title 不能用 [^<>"]+ : for case of "薔薇的嘆息 <薔薇色的疑雲 I>"
|
||||
PATTERN_search : /<a href="[^<>"]*?\/(\d+)\/"[^<>]*? title="([^"]+)"/,
|
||||
parse_search_result : function(html, get_label) {
|
||||
// console.log(html);
|
||||
html = html.between(' id="officialList">') || html;
|
||||
// console.log(html);
|
||||
var _this = this, id_list = [], id_data = [];
|
||||
html.each_between(this.search_head_token, '</li>', function(token) {
|
||||
// console.log(token);
|
||||
var matched = token.match(_this.PATTERN_search);
|
||||
// console.log(matched);
|
||||
if (matched) {
|
||||
// コミコ有些整本賣的作品,而非一話一話。
|
||||
id_list.push(matched[1]);
|
||||
id_data.push(get_label(matched[2]));
|
||||
}
|
||||
});
|
||||
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return work_id + '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
// console.log(html);
|
||||
|
||||
if (this.archive_old_works) {
|
||||
// 因為不會遍歷所有章節檔案,得到的是錯誤的 `work_data.last_file_modified_date`。
|
||||
// 因此必須避免執行 check_and_archive_old_work()。
|
||||
library_namespace.warn([ this.id + ': ', {
|
||||
// gettext_config:{"id":"this-website-does-not-support-the-function-of-archiving-old-works-(.archive_old_works)"}
|
||||
T : '本網站不支援封存舊作品功能 (.archive_old_works)!'
|
||||
} ]);
|
||||
this.archive_old_works = false;
|
||||
}
|
||||
|
||||
var cmnData = html.between('var cmnData =', '</script>'), matched;
|
||||
if (!cmnData) {
|
||||
// 公式作品の「掲載終了日」について、お知らせいたします。
|
||||
// 出版社の都合により、以下2作品を掲載終了とさせていただきます。
|
||||
// 更新中の掲載終了につき、大変ご迷惑をおかけし申し訳ございません。
|
||||
|
||||
// 下記の公式作品は既に掲載終了しています。
|
||||
matched = get_label(html.between(
|
||||
// <p class="m-section-error__heading">お探しのページは存在しません</p>
|
||||
'<p class="m-section-error__heading">', '</p>'));
|
||||
if (matched) {
|
||||
throw new Error(matched);
|
||||
}
|
||||
}
|
||||
|
||||
eval('cmnData=' + cmnData);
|
||||
|
||||
var title = (html.between('<h1 class="article', '</h1>') || html
|
||||
.between('<h1', '</h1>')).between('>'), tags = [];
|
||||
/**
|
||||
* e.g., http://www.comico.com.tw/challenge/3263/ 去除label
|
||||
*
|
||||
* <code>
|
||||
|
||||
<h1 class="article-hero03__ttl"><i class="i-label i-label--fill article-hero03__ttl-icon">精選</i><span class="o-hidden _challengeTitle">小惡魔與草莓男友</span></h1>
|
||||
|
||||
</code>
|
||||
*/
|
||||
if (title.includes('</i>')) {
|
||||
tags.push(get_label(title.between(null, '</i>')));
|
||||
title = title.between('</i>');
|
||||
}
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
// <h1 class="article-hero05__ttl">美麗的代價</h1>
|
||||
title : get_label(title),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
tags : tags
|
||||
};
|
||||
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
// e.g., '<li class="article-hero03__list-tag-item">中篇故事</li>'
|
||||
html.each_between('list-tag-item">', '</li>', function(text) {
|
||||
tags.push(get_label(text));
|
||||
});
|
||||
|
||||
// JavaScript Object Notation for Linked Data 關聯的資料
|
||||
matched = html.between('<script type="application/ld+json">',
|
||||
'</script>');
|
||||
if (matched) {
|
||||
// Structured Data 結構化資料
|
||||
// https://search.google.com/structured-data/testing-tool
|
||||
try {
|
||||
matched = JSON.parse(matched);
|
||||
work_data.linked_data = matched;
|
||||
} catch (e) {
|
||||
// library_namespace.error(matched);
|
||||
}
|
||||
}
|
||||
|
||||
while (matched = PATTERN_work_info.exec(html)) {
|
||||
if (matched[3] = get_label(matched[3]).replace(/\t/g, ''))
|
||||
work_data[matched[2]] = matched[3];
|
||||
}
|
||||
|
||||
Object.assign(add_navigation_data(work_data, html),
|
||||
// 警告: 這會留下個人資訊!
|
||||
cmnData, {
|
||||
// 更新日期:每週連載時間/是否為完結作品。 e.g., 完結作品, 每週六, 隔週週日
|
||||
status : get_label(html.between('__info-value">', '</dd>')
|
||||
// コミコ e.g., 完結作品, 毎週金曜日
|
||||
|| html.between('<span class="o-txt-bold">', '</span>'))
|
||||
.replace(/[\s\n]{2,}/g, ' '),
|
||||
// 可用的閱讀券數量。
|
||||
ticket_left : (cmnData.eventRentalTicket || 0)
|
||||
// 若是不用等的話,表示已收到閱讀券,還有一張可用。
|
||||
+ (cmnData.time && cmnData.time.leftTime === 0 ? 1 : 0),
|
||||
last_checked : null
|
||||
});
|
||||
if (cmnData.time && cmnData.time.leftTime > 0) {
|
||||
library_namespace.info({
|
||||
// gettext_config:{"id":"the-next-time-you-receive-a-reading-voucher-you-will-need-$1"}
|
||||
T : [ '下次收到閱讀券還要 %1。',
|
||||
// レンタル券で無料 レンタル券が届きました(1日で回復)
|
||||
// 作品を1話レンタルできます
|
||||
library_namespace.age_of(0, 1000 * cmnData.time.leftTime) ]
|
||||
});
|
||||
}
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
chapter_list_URL : function(work_id, work_data) {
|
||||
// console.log(work_data);
|
||||
// library_namespace.set_debug(9);
|
||||
var api = work_data.api && work_data.api.articleListAPI;
|
||||
if (!api) {
|
||||
api = work_data.isOfficial === false
|
||||
// 2019/10: 'api/getArticleListAll.nhn' 沒有 .freeFlg
|
||||
// 標記,無法自動使用閱讀卷。
|
||||
// 但是對新手村作品如 '3729 神光拜達摩' 來說,
|
||||
// 用 'api/getArticleListAll.nhn' 才能取得所有作品之列表。
|
||||
//
|
||||
// https://github.com/kanasimi/work_crawler/issues/384
|
||||
// 第一次執行時,尚未取得 .isOfficial 標記,必須先採用 api/getArticleList.nhn
|
||||
? 'api/getArticleListAll.nhn'
|
||||
// 2019/9: 'api/getArticleList.nhn'
|
||||
: 'api/getArticleList.nhn'
|
||||
}
|
||||
return [ api, {
|
||||
titleNo : work_id
|
||||
} ];
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
if (!Array.isArray(work_data.downloaded_chapter_list))
|
||||
work_data.downloaded_chapter_list = [];
|
||||
|
||||
// console.log(html);
|
||||
var recerse_count = 0;
|
||||
html = JSON.parse(html);
|
||||
html = html.result;
|
||||
// for 'api/getArticleList.nhn', there is no .totalPageCnt
|
||||
if (('totalPageCnt' in html) && html.totalPageCnt !== 1) {
|
||||
console.log(html);
|
||||
throw new Error(work_data.title + ': ' + 'Total page is '
|
||||
+ html.totalPageCnt + ', not 1!');
|
||||
}
|
||||
// 作品改變 titleNo 時,舊 id 可能會回傳 `{"result":{}}`
|
||||
html.list.forEach(function(chapter_data, index) {
|
||||
chapter_data.url = chapter_data.articleDetailUrl;
|
||||
// 原先都將標題設在 subtitle,title 沒東西。
|
||||
chapter_data.title = get_label(chapter_data.subtitle);
|
||||
if (this.set_downloaded_if_read
|
||||
&& !work_data.downloaded_chapter_list[index]) {
|
||||
work_data.downloaded_chapter_list[index]
|
||||
// 記錄是否已經下載過本章節。
|
||||
= chapter_data.read;
|
||||
}
|
||||
if (index > 0) {
|
||||
recerse_count += Math.sign(html.list[index - 1].articleNo
|
||||
- chapter_data.articleNo);
|
||||
}
|
||||
}, this);
|
||||
work_data.chapter_list = html.list;
|
||||
// 預防尾大不掉。
|
||||
delete html.list;
|
||||
// console.log(recerse_count);
|
||||
if (recerse_count > 0) {
|
||||
library_namespace.info([ work_data.title + ': ', {
|
||||
// gettext_config:{"id":"change-the-list-of-reversed-chapters-to-positive-order"}
|
||||
T : '將倒序章節列表轉為正序。'
|
||||
} ]);
|
||||
work_data.chapter_list.reverse();
|
||||
}
|
||||
Object.assign(work_data, html);
|
||||
|
||||
// 先檢查是不是還有還有沒讀過的章節。
|
||||
if (work_data.ticket_left > 0) {
|
||||
if (work_data.last_download) {
|
||||
work_data.chapter_list.some(function(chapter_data, index) {
|
||||
if (++index >= work_data.last_download.chapter) {
|
||||
return true;
|
||||
}
|
||||
if (!chapter_data.freeFlg) {
|
||||
throw new Error(this.id + ': '
|
||||
+ '網站改版?未發現 .freeFlg!');
|
||||
}
|
||||
if (!work_data.downloaded_chapter_list[index]
|
||||
&& chapter_data.freeFlg === READABLE_FLAG) {
|
||||
library_namespace.info([ work_data.title + ': ', {
|
||||
// gettext_config:{"id":"there-are-still-$1-reading-volume-but-$2-$3-chapter-has-not-been-downloaded-yet.-so-checking-from-this-chapter"}
|
||||
T : [ '還有%1張{{PLURAL:%1|閱讀卷}},且第 %2/%3 章還有沒下載過,從此章開始檢查。',
|
||||
//
|
||||
work_data.ticket_left, index,
|
||||
//
|
||||
work_data.chapter_list.length ]
|
||||
} ]);
|
||||
work_data.last_checked
|
||||
// 記錄最後檢查過的章節。
|
||||
= work_data.last_download.chapter;
|
||||
work_data.last_download.chapter = index;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
work_data.recheck = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
consume_url : 'consume/index.nhn',
|
||||
pre_parse_chapter_data
|
||||
// 執行在解析章節資料 process_chapter_data() 之前的作業 (async)。
|
||||
// 必須自行保證執行 callback(),不丟出異常、中斷。
|
||||
: function(XMLHttp, work_data, callback, chapter_NO) {
|
||||
// console.log(work_data);
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1],
|
||||
//
|
||||
skip_chapter = !chapter_data.price || chapter_data.isPurchased && {
|
||||
// gettext_config:{"id":"«$1»-has-been-purchased"}
|
||||
T : [ '已購買章節《%1》。', chapter_data.title ]
|
||||
};
|
||||
// console.log(chapter_data);
|
||||
if (!skip_chapter && chapter_data.expireDate > 0) {
|
||||
skip_chapter = {
|
||||
// gettext_config:{"id":"you-can-read-«$3»-in-this-section-before-$1-(and-$2)"}
|
||||
T : [ '在 %1 之前(還有 %2)可以閱讀本章節《%3》。',
|
||||
//
|
||||
new Date(chapter_data.expireDate).format('%m/%d %H:%M'),
|
||||
//
|
||||
library_namespace.age_of(Date.now(),
|
||||
//
|
||||
chapter_data.expireDate), chapter_data.title ]
|
||||
};
|
||||
}
|
||||
if (!skip_chapter
|
||||
&& work_data.downloaded_chapter_list[chapter_NO - 1]) {
|
||||
// TODO: 應該檢查是不是真的有圖片檔案存在。若有檔案不見,
|
||||
// 或者有損壞檔案,
|
||||
// 那麼就把 work_data.downloaded_chapter_list[index] 設成 false。
|
||||
skip_chapter = {
|
||||
// gettext_config:{"id":"the-section-«$1»-has-been-downloaded-before-and-will-not-be-re-purchased"}
|
||||
T : [ '之前已下載過章節《%1》,不再重新購買。', chapter_data.title ]
|
||||
};
|
||||
}
|
||||
if (!skip_chapter && chapter_data.freeFlg !== READABLE_FLAG) {
|
||||
// N: TW: 本章節需要錢(coin)來閱讀。
|
||||
if (chapter_data.freeFlg === 'N'
|
||||
// P: JP: 本章節需要錢(30コイン) or point(15ポイント)來閱讀。
|
||||
|| chapter_data.freeFlg === 'P') {
|
||||
skip_chapter = true;
|
||||
} else {
|
||||
skip_chapter = {
|
||||
// gettext_config:{"id":"the-status-of-this-chapter-is-unknown-($1).-skipping-$1-does-not-use-reading-volumes"}
|
||||
T : [ '本章節狀況不明(%1)。跳過《%1》不採用閱讀卷。',
|
||||
chapter_data.freeFlg, chapter_data.title ]
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!skip_chapter && !(work_data.ticket_left > 0)) {
|
||||
if (work_data.ticket_left !== NO_ticket_notified) {
|
||||
work_data.ticket_left = NO_ticket_notified;
|
||||
skip_chapter = [ {
|
||||
T : NO_ticket_notified
|
||||
}, {
|
||||
T : [ '跳過《%1》,不使用閱讀券。', chapter_data.title ]
|
||||
} ];
|
||||
} else
|
||||
skip_chapter = true;
|
||||
}
|
||||
if (!skip_chapter && !this.auto_use_ticket) {
|
||||
skip_chapter = auto_use_ticket_notified ? true
|
||||
// @see https://github.com/kanasimi/work_crawler
|
||||
: {
|
||||
// gettext_config:{"id":"the-tool-is-not-set-to-automatically-use-the-reading-volume.-if-you-are-not-using-the-installation-package-and-want-to-have-the-tool-automatically-use-the-reading-volume-please-open-the-file-manager"}
|
||||
T : '未設定讓本工具自動使用閱讀卷。若您並非使用安裝包,並想要讓本工具自動使用閱讀卷,請打開檔案總管,到安裝本工具的目錄下(若是您使用安裝包,就不能夠設定帳號密碼了。),在「work_crawler.configuration.js」這個 .js 組態檔案中設定好帳號密碼資料,並設定「auto_use_ticket:true」。您可以參考 work_crawler.default_configuration.js 這個檔案來做設定。'
|
||||
};
|
||||
auto_use_ticket_notified = true;
|
||||
}
|
||||
|
||||
if (skip_chapter) {
|
||||
if (skip_chapter !== true) {
|
||||
if (!Array.isArray(skip_chapter)) {
|
||||
skip_chapter = [ skip_chapter ];
|
||||
}
|
||||
skip_chapter.unshift(work_data.title, ': ');
|
||||
library_namespace.info(skip_chapter);
|
||||
}
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// http://www.comico.com.tw/notice/detail.nhn?no=751
|
||||
library_namespace.info([ work_data.title + ': ', {
|
||||
// gettext_config:{"id":"reading-«$1»-with-a-reading-voucher"}
|
||||
T : [ '用閱讀券閱讀《%1》。', chapter_data.title ]
|
||||
} ]);
|
||||
var _this = this, html = XMLHttp.responseText;
|
||||
this.get_URL(this.consume_url, function(XMLHttp) {
|
||||
// console.log(XMLHttp.responseText);
|
||||
|
||||
var matched = XMLHttp.responseText && XMLHttp.responseText
|
||||
// var msg = '很抱歉,帳號需要通過電話認證才可購買。';
|
||||
.match(/[\s;]msg *= *'([^']+)/);
|
||||
if (matched) {
|
||||
library_namespace
|
||||
.error(work_data.title + ': ' + matched[1]);
|
||||
// 歸零。
|
||||
work_data.ticket_left = 1;
|
||||
}
|
||||
|
||||
if (--work_data.ticket_left === 0
|
||||
// 僅僅下載有閱讀券的章節,然後就回到最後讀取的章節。
|
||||
&& work_data.last_checked > 0) {
|
||||
// 回到原先應該檢查的章節號碼。
|
||||
work_data.jump_to_chapter = work_data.last_checked;
|
||||
delete work_data.last_checked;
|
||||
}
|
||||
// XMLHttp 只是一個轉址網頁,必須重新擷取網頁。
|
||||
_this.get_URL(chapter_data.url, callback);
|
||||
|
||||
}, {
|
||||
titleNo : work_data.id,
|
||||
articleNo : chapter_data.articleNo,
|
||||
// K: 專用閱讀券, P: 用point, C: 用coin購買
|
||||
paymentCode : 'K',
|
||||
// 使用coin時才需要
|
||||
// https://github.com/zhongfly/comico/blob/master/comico.py
|
||||
// ['http://www.comico.com.tw/consume/coin/publish.nhn',{paymentCode:'C'}]
|
||||
// JSON.parse(result).result.coinUseToken
|
||||
coinUseToken : '',
|
||||
// 5, 6, コミコ:36, ...?
|
||||
productNo : html
|
||||
//
|
||||
.between(' name="productNo" value="', '"') || 5,
|
||||
// coin price
|
||||
price : chapter_data.price,
|
||||
// 用coin租用價格,一般能租用的都可以用閱讀券。 コミコ: 20
|
||||
rentalPrice : html.between(' name="rentalPrice" value="', '"')
|
||||
|| '',
|
||||
// point price, コミコ 漫畫作品無此項, TW only
|
||||
pointRentalPrice : html.between(
|
||||
' name="pointRentalPrice" value="', '"') || 120
|
||||
});
|
||||
},
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// http://static.comico.com.tw/tw/syn/spn/js/manga/article/plusMangaDetailApp/app.1.12.0.js
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1],
|
||||
//
|
||||
cmnData = html.between('var cmnData =', '</script>');
|
||||
// console.log(cmnData);
|
||||
if (cmnData) {
|
||||
eval('cmnData=' + cmnData);
|
||||
} else if (cmnData = html.between(
|
||||
// e.g., http://plus.comico.jp/manga/24517/8/
|
||||
'<p class="m-section-error__heading">', '</p>')) {
|
||||
var message = work_data.title + ' §' + chapter_NO + ' '
|
||||
+ chapter_data.title + ': ' + cmnData;
|
||||
if (cmnData !== 'お探しのページは存在しません') {
|
||||
throw new Error(message);
|
||||
}
|
||||
library_namespace.error(message);
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
var matched, image_url_list = html.between(
|
||||
// TW: <div class="locked-episode__kv _lockedEpisodeKv"
|
||||
// コミコ: <div class="locked-episode locked-episode--show-kv">
|
||||
'<div class="locked-episode', '</div>');
|
||||
if (image_url_list) {
|
||||
chapter_data.limited = true;
|
||||
// TW: style="background-image: url('...');">
|
||||
image_url_list = image_url_list.between("url('", "'")
|
||||
// コミコ: <img src=".jpg" width="88" height="88" alt="" />
|
||||
|| image_url_list.between(' src="', '"');
|
||||
// 日文版plus.comico 此時雖然有 {Array}cmnData.imageData
|
||||
// 但缺少hash而不能取得。
|
||||
if (/\.jpg$/.test(image_url_list)) {
|
||||
// 日文版plus.comico 此時僅有一個縮圖可用,跳過不取。
|
||||
cmnData.imageData = [];
|
||||
} else if (cmnData.imageData
|
||||
&& cmnData.imageData.filter(function(url) {
|
||||
return url && !/\.jpg$/.test(url);
|
||||
}).length > 0) {
|
||||
// 應該不會到這裡來了。
|
||||
cmnData.imageData.unshift(image_url_list);
|
||||
} else {
|
||||
// 中文版的狀況。
|
||||
cmnData.imageData = [ image_url_list ];
|
||||
}
|
||||
|
||||
} else if (image_url_list = html
|
||||
// comico_jp: <div class="comic-image _comicImage">
|
||||
// e.g., 新手村
|
||||
.between(' _comicImage">', '</div>')) {
|
||||
// 一般正常可取得圖片的情況。
|
||||
// 去除 placeholder。 <div class="comic-image__blank-layer">
|
||||
image_url_list = image_url_list.between(null, '<div ')
|
||||
|| image_url_list;
|
||||
image_url_list = image_url_list.all_between(' src="', '"');
|
||||
// assert: {Array}image_url_list
|
||||
if (cmnData.imageData) {
|
||||
// 中文版, 日文版plus.comico 將除第一張外所有圖片網址放在
|
||||
// {Array}cmnData.imageData 裡面。
|
||||
if (image_url_list.length === 1) {
|
||||
cmnData.imageData.unshift(image_url_list[0]);
|
||||
|
||||
} else if (
|
||||
/**
|
||||
* e.g.,
|
||||
* http://www.comico.jp/detail.nhn?titleNo=27605&articleNo=1
|
||||
* <code>
|
||||
|
||||
<div class="swiper-wrapper _swiperWrapper _comicImage">
|
||||
<!-- Slides -->
|
||||
<div class="swiper-slide _swiperSlide o-hidden">
|
||||
<div dir="ltr">
|
||||
<div id="_popIn_video"></div>
|
||||
|
||||
</code>
|
||||
*/
|
||||
!html.includes(' class="swiper-slide _swiperSlide')) {
|
||||
// gettext_config:{"id":"web-page-revision?-unable-to-parse"}
|
||||
var message = '網頁改版?無法解析!';
|
||||
if (library_namespace.gettext)
|
||||
message = library_namespace.gettext(message);
|
||||
throw new Error('parse_chapter_data: '
|
||||
+ work_data.title + ' §' + chapter_NO + ': '
|
||||
+ message);
|
||||
Array.prototype.unshift.apply(cmnData.imageData,
|
||||
image_url_list);
|
||||
}
|
||||
|
||||
} else {
|
||||
// コミコ 日文版一般漫畫將所有圖片放在這之間,無 cmnData.imageData。
|
||||
cmnData.imageData = image_url_list;
|
||||
}
|
||||
|
||||
} else if (matched = html
|
||||
// e.g., https://www.comico.jp/detail.nhn?titleNo=4235&articleNo=219
|
||||
.match(/<h2 class="[^"<>]*o-txt-error[^"<>]*">([\s\S]+?)<\/h2>/)) {
|
||||
var message = get_label(matched[1]);
|
||||
throw new Error(work_data.title + ' §' + chapter_NO + ': '
|
||||
+ message);
|
||||
|
||||
} else if (html.includes('<p class="error-section__ttl">')) {
|
||||
var message = get_label(html.between(
|
||||
/**
|
||||
* <code>
|
||||
|
||||
<div class="error-section o-mt100 o-mb100">
|
||||
<p class="error-section__ttl">無法閱讀</p>
|
||||
<p class="error-section__description">本話正在審查當中。<br/>審查完成後將會開放閱讀</p>
|
||||
<p class="o-mt30"><a href="" class="btn03" onclick="javascript:history.back();">離開</a></p>
|
||||
<!-- /.m-section-error --></div>
|
||||
|
||||
</code>
|
||||
*/
|
||||
'<p class="error-section__ttl">', '</p>'))
|
||||
//
|
||||
+ ': ' + get_label(html.between(
|
||||
//
|
||||
'<p class="error-section__description">', '</p>'));
|
||||
throw new Error(work_data.title + ' §' + chapter_NO + ': '
|
||||
+ message);
|
||||
|
||||
} else {
|
||||
console.log(html);
|
||||
// gettext_config:{"id":"web-page-revision?-unable-to-parse"}
|
||||
var message = '網頁改版?無法解析!';
|
||||
if (library_namespace.gettext)
|
||||
message = library_namespace.gettext(message);
|
||||
throw new Error(work_data.title + ' §' + chapter_NO + ': '
|
||||
+ message);
|
||||
}
|
||||
|
||||
if (cmnData.url) {
|
||||
// e.g., http://plus.comico.jp/manga/24529/13/
|
||||
// 預防 chapter_data.url 被污染。
|
||||
cmnData._url = cmnData.url;
|
||||
delete cmnData.url;
|
||||
}
|
||||
// console.log(work_data);
|
||||
// console.log(chapter_data);
|
||||
// console.log(cmnData);
|
||||
|
||||
Object.assign(add_navigation_data(chapter_data, html), {
|
||||
// 設定必要的屬性。
|
||||
image_list : cmnData.imageData.map(function(url) {
|
||||
if (chapter_data.limited
|
||||
// http://comicimg.comico.jp/tmb/00000/1/hexhex_hexhexhex.jpg"
|
||||
&& url.includes('.jp/tmb/') && /\.jpg$/.test(url))
|
||||
return;
|
||||
// chapter_data.isOfficial ? 官方作品 : 新手村
|
||||
if (chapter_data.isOfficial && chapter_data.freeFlg !== 'Y'
|
||||
// 付費章節: 中文版提供第一張的完整版,日文版只提供縮圖。
|
||||
// 圖片都應該要有hash,且不該是縮圖。
|
||||
&& (url.includes('.jp/tmb/') || /\.jpg$/.test(url))) {
|
||||
var message = library_namespace.gettext
|
||||
// gettext_config:{"id":"invalid-image-url-$1"}
|
||||
? library_namespace.gettext('Invalid image url: %1',
|
||||
url) : 'Invalid image url: ' + url;
|
||||
throw new Error(work_data.title + ' §' + chapter_NO
|
||||
+ ' ' + chapter_data.title + ': ' + message);
|
||||
}
|
||||
return {
|
||||
url : url
|
||||
};
|
||||
})
|
||||
}, cmnData);
|
||||
|
||||
return chapter_data;
|
||||
},
|
||||
|
||||
// @see work_crawler_loader.js
|
||||
after_download_list : function() {
|
||||
// logout
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* full module name.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
var module_name = this.id;
|
||||
|
||||
function new_comico_comics_crawler(configuration, callback, initializer) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
if (typeof initializer === 'function') {
|
||||
initializer(crawler);
|
||||
}
|
||||
|
||||
// for 年齡確認您是否已滿18歲?
|
||||
crawler.setup_value('cookie', 'islt18age=' + Date.now());
|
||||
|
||||
// https://github.com/nodejs/node/issues/27384
|
||||
// node.js v12 disable TLS v1.0 and v1.1 by default
|
||||
require('tls').DEFAULT_MIN_VERSION = 'TLSv1';
|
||||
|
||||
if (crawler.password && crawler.loginid) {
|
||||
library_namespace.log([ (crawler.id || module_name) + ': ', {
|
||||
// gettext_config:{"id":"login-as-$1"}
|
||||
T : [ 'Login as [%1]', crawler.loginid ]
|
||||
} ]);
|
||||
|
||||
var account_api_host = crawler.base_URL.replace(/^.+?[a-z]+\./,
|
||||
// https://id.comico.com.tw/login/login.nhn
|
||||
// https://id.comico.jp/login/login.nhn
|
||||
'https://id.');
|
||||
|
||||
crawler.get_URL(account_api_host + 'login/login.nhn', after_login,
|
||||
//
|
||||
{
|
||||
autoLoginChk : 'Y',
|
||||
loginid : crawler.loginid,
|
||||
password : crawler.password,
|
||||
nexturl : ''
|
||||
});
|
||||
|
||||
} else {
|
||||
callback(crawler);
|
||||
}
|
||||
|
||||
function after_login(XMLHttp) {
|
||||
// XMLHttp 只是一個轉址網頁。
|
||||
|
||||
// 必須先進入收件箱才能取得所有"有期限的物品"
|
||||
crawler.get_URL(
|
||||
// https://id.comico.com.tw/settings/inbox/
|
||||
account_api_host + 'settings/inbox/', function to_inbox(XMLHttp) {
|
||||
|
||||
// 收件箱: 全部接收 有期限的物品
|
||||
// 受け取りBOX: すべて受け取る
|
||||
crawler.get_URL(
|
||||
// https://id.comico.com.tw/api/incentiveall/index.nhn
|
||||
account_api_host + 'api/incentiveall/index.nhn', get_ticket);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function get_ticket(XMLHttp) {
|
||||
// e.g., XMLHttp.responseText ===
|
||||
// '{"result":[121703625,121703626,121703627,121703628]}'
|
||||
// console.log(XMLHttp.responseText);
|
||||
var item_list = JSON.parse(XMLHttp.responseText).result;
|
||||
if (item_list.length > 0) {
|
||||
// TODO: 顯示物品的資訊。
|
||||
library_namespace.info({
|
||||
// gettext_config:{"id":"$1-items-with-a-time-limit-have-been-received"}
|
||||
T : [ '已收到 %1 個有期限的{{PLURAL:%1|項目}}。', item_list.length ]
|
||||
});
|
||||
}
|
||||
|
||||
// 最新消息
|
||||
// http://www.comico.com.tw/notice/
|
||||
|
||||
callback(crawler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new_comico_comics_crawler;
|
||||
}
|
1102
app/node_modules/cejs/application/net/work_crawler/sites/dm5.js
generated
vendored
1102
app/node_modules/cejs/application/net/work_crawler/sites/dm5.js
generated
vendored
File diff suppressed because it is too large
Load Diff
317
app/node_modules/cejs/application/net/work_crawler/sites/hhcool.js
generated
vendored
317
app/node_modules/cejs/application/net/work_crawler/sites/hhcool.js
generated
vendored
@@ -1,317 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading hhcool comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載 汗汗酷漫 漫畫 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.hhcool(configuration, function(crawler) {
|
||||
start_crawler(crawler, typeof module === 'object' && module);
|
||||
}, function(crawler) {
|
||||
setup_crawler(crawler, typeof module === 'object' && module);
|
||||
});
|
||||
|
||||
</code>
|
||||
*
|
||||
* using zh-cmn-Hant-CN .aspx
|
||||
*
|
||||
* TODO: http://coco.hhxxee.com/ http://99.hhxxee.com/ http://99770.hhxxee.com/
|
||||
*
|
||||
* @since 2019/4/25 5:6:7 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/hhcool.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.hhcool',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* e.g., <code>
|
||||
|
||||
// 汗汗酷漫
|
||||
<li><a title='野生的最终BOSS出现了' href='/manhua/32449.html'><img src='http://img.94201314.net/comicui/32449.JPG'><br>野生的最终BOSS出现了</a></li>
|
||||
|
||||
// 動漫伊甸園
|
||||
<li><a title='樹鶯吟' target=_blank href='/comicinfo/37621.html'><img src='http://img.94201314.net/comicui/37621.JPG'><br>樹鶯吟</a></li>
|
||||
|
||||
</code>
|
||||
*/
|
||||
var PATTERN_search = /<li><a title='([^<>'"]+)'[^<>]*? href='\/[^\/]+\/(\d+).html'>.+?<\/li>/g;
|
||||
|
||||
/**
|
||||
* e.g., <code>
|
||||
<div class='cVolTag'>周刊杂志每周每月连载单集</div><ul class='cVolUl'><li>...</a></li></ul>
|
||||
<div class='cVolTag'>漫画正片外的剧情之番外篇</div><ul class='cVolUl'><li>...</a></li></ul>
|
||||
|
||||
<li><a class='l_s' href='/cool282192/1.html?s=7' target='_blank' title='双星之阴阳师09卷'>双星之阴阳师09卷</a></li>
|
||||
</code>
|
||||
*/
|
||||
// matched: [all, part_title, url, title, inner]
|
||||
var PATTERN_chapter = /<div class='cVolTag'>([^<>]+)|<li><a [^<>]*?href='([^'<>]+)'[^<>]*? title='([^'<>]+)'[^<>]*>(.+?)<\/a>/g;
|
||||
|
||||
var PATTERN_image = /<img (?:.*?) name="([^<>"]+)" (?:.*?)hdNextImg" value="([^<>"]+)"/;
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 所有的子檔案要修訂註解說明時,應該都要順便更改在CeL.application.net.comic中Comic_site.prototype內的母comments,並以其為主體。
|
||||
|
||||
// 本站常常無法取得圖片,因此得多重新檢查。
|
||||
// 當有多個分部的時候才重新檢查。
|
||||
recheck : 'multi_parts_changed',
|
||||
// 當無法取得chapter資料時,直接嘗試下一章節。在手動+監視下recheck時可併用此項。
|
||||
// skip_chapter_data_error : true,
|
||||
|
||||
// 當圖像不存在 EOI (end of image) 標記,或是被偵測出非圖像時,依舊強制儲存檔案。
|
||||
// allow image without EOI (end of image) mark. default:false
|
||||
allow_EOI_error : true,
|
||||
// 當圖像檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。
|
||||
// skip_error : true,
|
||||
|
||||
// 最小容許圖案檔案大小 (bytes)。
|
||||
// 對於極少出現錯誤的網站,可以設定一個比較小的數值,並且設定.allow_EOI_error=false。因為這類型的網站要不是無法取得檔案,要不就是能夠取得完整的檔案;要取得破損檔案,並且已通過EOI測試的機會比較少。
|
||||
// 對於有些圖片只有一條細橫桿的情況。
|
||||
MIN_LENGTH : 400,
|
||||
|
||||
// one_by_one : true,
|
||||
|
||||
// base_URL : '',
|
||||
|
||||
// /manhua/
|
||||
base_comic_path : 'manhua',
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : 'comic/?act=search&st=',
|
||||
parse_search_result : function(html) {
|
||||
html = html.between('<div class="cComicList">', '</div>');
|
||||
var id_list = [], id_data = [], matched;
|
||||
while (matched = PATTERN_search.exec(html)) {
|
||||
id_list.push(+matched[2]);
|
||||
id_data.push(matched[1]);
|
||||
}
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
// e.g., http://www.hhcool.com/manhua/32449.html
|
||||
return this.base_comic_path + '/' + work_id + '.html';
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
html = html.between('<div id="about_kit">',
|
||||
'<div class="cVolList">');
|
||||
html = html.between(null, '<div class="cInfoAct">') || html;
|
||||
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(html.between('<h1>', '</h1>'))
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
// <meta property="og:novel:status" content="已完结"/>
|
||||
};
|
||||
extract_work_data(work_data, html, /<li>([^:]+)(.+?)<\/li>/g);
|
||||
work_data.status = work_data.状态;
|
||||
work_data.last_update = work_data.更新;
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
html = html.between('<div class="cVolList">', '<div id="foot">');
|
||||
|
||||
work_data.chapter_list = [];
|
||||
// 漫畫目錄名稱不須包含分部號碼。使章節目錄名稱不包含 part_NO。
|
||||
// 將會在 function get_chapter_directory_name() 自動設定。
|
||||
// work_data.chapter_list.add_part_NO = false;
|
||||
|
||||
// 轉成由舊至新之順序。
|
||||
work_data.inverted_order = true;
|
||||
|
||||
var matched;
|
||||
while (matched = PATTERN_chapter.exec(html)) {
|
||||
// delete matched.input;
|
||||
// console.log(matched);
|
||||
if (matched[1]) {
|
||||
this.set_part(work_data, get_label(matched[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
this.add_chapter(work_data, {
|
||||
title : get_label(matched[3].replace(work_data.title, '')),
|
||||
url : matched[2]
|
||||
});
|
||||
}
|
||||
|
||||
// console.log(work_data.chapter_list);
|
||||
},
|
||||
|
||||
pre_parse_chapter_data
|
||||
// 執行在解析章節資料 process_chapter_data() 之前的作業 (async)。
|
||||
// 必須自行保證執行 callback(),不丟出異常、中斷。
|
||||
: function(XMLHttp, work_data, callback, chapter_NO) {
|
||||
var html = XMLHttp.responseText;
|
||||
|
||||
var chapter_list = [], URL = XMLHttp.responseURL,
|
||||
// 每一張圖片都得要從載入的頁面獲得資訊。
|
||||
matched, PATTERN = /csel2\((\d{1,3})\)/g;
|
||||
|
||||
while (matched = PATTERN.exec(html)) {
|
||||
chapter_list.push(matched[1]);
|
||||
}
|
||||
|
||||
work_data.cache_directory = work_data.directory
|
||||
+ this.cache_directory_name;
|
||||
library_namespace.create_directory(work_data.cache_directory);
|
||||
if (!work_data.image_list) {
|
||||
// image_list[chapter_NO] = [url, url, ...]
|
||||
work_data.image_list = [];
|
||||
}
|
||||
var _this = this,
|
||||
//
|
||||
this_image_list = work_data.image_list[chapter_NO] = [];
|
||||
|
||||
function for_each_chapter(run_next, NO, index) {
|
||||
var url = URL.replace(/\/\d{1,3}\.html/, '/' + NO + '.html'),
|
||||
//
|
||||
save_to = work_data.cache_directory
|
||||
+ chapter_NO.pad(work_data.chapter_NO_pad_digits || 3)
|
||||
+ '-' + NO.pad(work_data.chapter_NO_pad_digits || 3)
|
||||
+ '.html';
|
||||
|
||||
function for_each_image_page(html, error) {
|
||||
if (error) {
|
||||
library_namespace.error({
|
||||
// gettext_config:{"id":"an-error-occurred-while-downloading-and-the-file-contents-could-not-be-obtained-smoothly"}
|
||||
T : '下載時發生錯誤,無法順利取得檔案內容!'
|
||||
});
|
||||
library_namespace.error(error);
|
||||
_this.onerror(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var image_data = html.match(PATTERN_image);
|
||||
|
||||
// decode chapter image url data
|
||||
image_data = [ unsuan(image_data[1]), unsuan(image_data[2]) ];
|
||||
|
||||
if (image_data[0] !== '\x00') {
|
||||
if (!this_image_list[index]) {
|
||||
this_image_list[index] = image_data[0];
|
||||
} else if (this_image_list[index] !== image_data[0]) {
|
||||
_this.onerror([ {
|
||||
// gettext_config:{"id":"different-url-$1-≠-$2"}
|
||||
T : [ 'Different url: %1 ≠ %2',
|
||||
//
|
||||
this_image_list[index], image_data[0] ]
|
||||
}, '\n', {
|
||||
// gettext_config:{"id":"maybe-the-downloaded-file-has-an-error?-you-can-try-to-download-it-later-or-use-the-.recheck-option-to-ignore-the-cache-and-re-download-the-page-for-each-image"}
|
||||
T : '或許是下載的檔案出現錯誤?您可嘗試過段時間再下載,或選用 .recheck 選項來忽略快取、重新下載每個圖片的頁面。'
|
||||
} ]);
|
||||
run_next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (image_data[1] !== '\x00') {
|
||||
this_image_list[index + 1] = image_data[1];
|
||||
}
|
||||
// console.log([ index, image_data ])
|
||||
|
||||
run_next();
|
||||
}
|
||||
|
||||
// 沒 cache 的話,每一次都要重新取得每個圖片的頁面,速度比較慢。
|
||||
library_namespace.get_URL_cache(url, for_each_image_page, {
|
||||
get_URL_options : _this.get_URL_options,
|
||||
no_write_info : true,
|
||||
file_name : save_to,
|
||||
reget : _this.recheck
|
||||
});
|
||||
}
|
||||
|
||||
chapter_list.run_serial(for_each_chapter, callback);
|
||||
},
|
||||
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
var PATTERN = / id="hdDomain"(?:.*?) value="([^<>"]+)"/,
|
||||
// 不同作品放在不同的location。
|
||||
matched = html.match(PATTERN);
|
||||
this.server_list = matched[1].split('|');
|
||||
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
|
||||
// console.log(work_data.image_list[chapter_NO]);
|
||||
chapter_data.image_list = work_data.image_list[chapter_NO]
|
||||
.map(function(url) {
|
||||
return encodeURI(library_namespace.HTML_to_Unicode(url));
|
||||
});
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function unsuan(s) {
|
||||
var x = s.substring(s.length - 1);
|
||||
var w = "abcdefghijklmnopqrstuvwxyz";
|
||||
var xi = w.indexOf(x) + 1;
|
||||
var sk = s.substring(s.length - xi - 12, s.length - xi - 1);
|
||||
s = s.substring(0, s.length - xi - 12);
|
||||
var k = sk.substring(0, sk.length - 1);
|
||||
var f = sk.substring(sk.length - 1);
|
||||
for (var i = 0; i < k.length; i++) {
|
||||
eval("s=s.replace(/" + k.substring(i, i + 1) + "/g,'" + i + "')");
|
||||
}
|
||||
var ss = s.split(f);
|
||||
s = "";
|
||||
for (i = 0; i < ss.length; i++) {
|
||||
s += String.fromCharCode(ss[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_hhcool_comics_crawler(configuration, callback, initializer) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
if (typeof initializer === 'function') {
|
||||
initializer(crawler);
|
||||
}
|
||||
|
||||
var decode_filename = 'script/view.js', unsuan;
|
||||
library_namespace.get_URL_cache(crawler.base_URL + decode_filename,
|
||||
//
|
||||
function(contents, error) {
|
||||
if (false) {
|
||||
eval('unsuan=function'
|
||||
+ contents.between('function unsuan', '\nvar'));
|
||||
}
|
||||
callback(crawler);
|
||||
}, crawler.main_directory + decode_filename.match(/[^\\\/]+$/)[0]);
|
||||
|
||||
}
|
||||
|
||||
return new_hhcool_comics_crawler;
|
||||
}
|
201
app/node_modules/cejs/application/net/work_crawler/sites/jieqi_article.js
generated
vendored
201
app/node_modules/cejs/application/net/work_crawler/sites/jieqi_article.js
generated
vendored
@@ -1,201 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading jieqi article novels.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸常見小說管理系統: 杰奇小说连载系统 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.jieqi_article(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @see http://www.jieqi.com/files/page/html/product/article.html 杰奇网络 杰奇小说连载系统
|
||||
* (2004-2015?,新版为杰奇原创文学系统)
|
||||
*
|
||||
* @since 2019/2/20 16:58:20 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see https://github.com/kanasimi/work_crawler/blob/master/81xsw.js
|
||||
// @see https://github.com/kanasimi/work_crawler/blob/master/23us.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.jieqi_article',
|
||||
|
||||
require : 'application.net.work_crawler.'
|
||||
//
|
||||
+ '|application.storage.EPUB.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// auto_create_ebook, automatic create ebook
|
||||
// MUST includes CeL.application.locale!
|
||||
need_create_ebook : true,
|
||||
// recheck:從頭檢測所有作品之所有章節與所有圖片。不會重新擷取圖片。對漫畫應該僅在偶爾需要從頭檢查時開啟此選項。default:false
|
||||
// recheck='changed': 若是已變更,例如有新的章節,則重新下載/檢查所有章節內容。否則只會自上次下載過的章節接續下載。
|
||||
recheck : 'changed',
|
||||
|
||||
// base_URL : '',
|
||||
charset : 'gbk',
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : function(work_title) {
|
||||
return [ 'modules/article/search.php', {
|
||||
searchtype : 'articlename',
|
||||
searchkey : work_title
|
||||
} ];
|
||||
},
|
||||
parse_search_result : function(html, get_label) {
|
||||
// console.log(html);
|
||||
var id_list = [], id_data = [];
|
||||
var matched = html.match(/og:novel:book_name" content="([^<>"]+)"/);
|
||||
|
||||
if (matched) {
|
||||
// 直接進入作品資訊頁面。
|
||||
id_data.push(get_label(matched[1]));
|
||||
matched = html
|
||||
// e.g., <meta property="og:novel:read_url"
|
||||
// content="https://www.zhuishubang.com/120382/"/>
|
||||
.match(/og:novel:read_url" content="[^<>"]*?\/(\d+)\/"/);
|
||||
id_list.push(+matched[1]);
|
||||
|
||||
} else {
|
||||
html.between('<div id="content">', '</table>')
|
||||
//
|
||||
.between('<table').each_between('<tr>', '</tr>',
|
||||
//
|
||||
function(text) {
|
||||
var matched = text
|
||||
// <td class="odd"><a
|
||||
// href="https://www.kanshushenzhan.com/132800/">万古剑神</a></td>
|
||||
.match(/ href="[^<>"]+\/(\d+)\/">(.+?)<\/a>/);
|
||||
id_list.push(+matched[1]);
|
||||
id_data.push(get_label(matched[2]));
|
||||
});
|
||||
}
|
||||
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return work_id + '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
// <div class="routeLeft"><a href="/">看书神站</a> > <a
|
||||
// href="/all/">书库</a>
|
||||
site_name : get_label(html.between('<div class="routeLeft">',
|
||||
'</a>'))
|
||||
};
|
||||
// overwrite .description
|
||||
extract_work_data(work_data, html, null, true);
|
||||
extract_work_data(work_data, html.between('<div class="bookPhr">',
|
||||
'<div class="renew">'), /<dd>([^:]+)(.+)<\/dd>/g);
|
||||
|
||||
var matched = html.between('<div class="renew">', '</div>').match(
|
||||
// <div class="renew">最新章节:<a href="/111269/40729086.html">第535章
|
||||
// 大结局</a><span>2018-08-01</span></div>
|
||||
/<a href="([^<>"]+)">(.+?)<\/a>(?:<span>([^<>]+?)<\/span>)?/);
|
||||
Object.assign(work_data, {
|
||||
latest_chapter : work_data.latest_chapter_name,
|
||||
latest_chapter_url : matched[1],
|
||||
last_update : work_data.update_time || matched[3]
|
||||
});
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
work_data.chapter_list = [];
|
||||
|
||||
html = html.between('<div class="chapterCon">', '</ul>');
|
||||
|
||||
var matched,
|
||||
// <li><a href="/111269/40724950.html">第1章 甩你一脸</a></li>
|
||||
PATTERN_chapter = /<a href="([^"<>]+)">([^<>]+)<\/a>/g;
|
||||
|
||||
while (matched = PATTERN_chapter.exec(html)) {
|
||||
// console.log(matched);
|
||||
var chapter_data = {
|
||||
url : matched[1],
|
||||
title : get_label(matched[2])
|
||||
};
|
||||
work_data.chapter_list.push(chapter_data);
|
||||
}
|
||||
|
||||
if (this.inverted_order)
|
||||
work_data.chapter_list.reverse();
|
||||
// console.log(work_data);
|
||||
},
|
||||
// inverted_order : true,
|
||||
|
||||
// 取得每一個章節的內容與各個影像資料。 get_chapter_data()
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// 在取得小說章節內容的時候,若發現有章節被目錄漏掉,則將之補上。
|
||||
this.check_next_chapter(work_data, chapter_NO, html);
|
||||
|
||||
// var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
|
||||
var sub_title = work_data.previous_sub_title
|
||||
|| get_label(html.between('<h2>', '</h1>')),
|
||||
// e.g., https://www.huaxiangju.com/25087/6323179.html
|
||||
text = html.between('<div class="articleCon">');
|
||||
text = text.between(null, '<div class="page">')
|
||||
|| html.between(null, '</div>');
|
||||
text = text.between('<p>', {
|
||||
tail : '</p>'
|
||||
});
|
||||
|
||||
if (false && !html.includes('<div class="articleCon">')) {
|
||||
// console.log(html);
|
||||
console.log(html.between('<div class="articleCon">', '</div>'));
|
||||
console.log(text);
|
||||
}
|
||||
|
||||
if (this.remove_ads) {
|
||||
text = this.remove_ads(text);
|
||||
}
|
||||
// console.log(text);
|
||||
|
||||
this.add_ebook_chapter(work_data, chapter_NO, {
|
||||
sub_title : sub_title,
|
||||
text : text
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_jieqi_article_novels_crawler(configuration) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
return new library_namespace.work_crawler(configuration);
|
||||
}
|
||||
|
||||
return new_jieqi_article_novels_crawler;
|
||||
}
|
410
app/node_modules/cejs/application/net/work_crawler/sites/manhuadb.js
generated
vendored
410
app/node_modules/cejs/application/net/work_crawler/sites/manhuadb.js
generated
vendored
@@ -1,410 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading manhuadb comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸漫畫網站 漫画DB 平臺 的工具。
|
||||
*
|
||||
* modify from 9mdm.js→dagu.js
|
||||
*
|
||||
* 由於 漫画DB 系列網站下載機制較複雜,下載圖片功能為獨立撰寫出來,不支援 achive_images 功能。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.manhuadb(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @since 2021/4/9 19:42:20 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/manhuadb.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/manhuacat.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.manhuadb',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// manhuadb.js:
|
||||
// <a class="fixed-a-es" target="_blank" href="/manhua/000/....html"
|
||||
// title="第01回">第01回</a>
|
||||
//
|
||||
// <div class="align-self-center pr-2"><h3 class="h4 mb-0 font-weight-normal
|
||||
// comic_version_title">[辉夜姬想让人告白~天才们的恋爱头脑战~ 连载]</h3></div>
|
||||
|
||||
// manhuacat.js:
|
||||
// <a class="fixed-a-es"
|
||||
// href="https://www.manhuacat.com/manga/475/523727.html"
|
||||
// title="第03话">第03话</a>
|
||||
//
|
||||
// <h2 class="h2 mb-0 font-weight-normal comic_version_title">单话</h2>
|
||||
|
||||
// [ all, chapter url, title, part title tag name, part title ]
|
||||
var PATTERN_chapter = /<li[\s\S]+?<a [^<>]*?href="([^<>"]+)"[^<>]*? title="([^<>"]+)"|<(h[23])[^<>]*>(.+?)<\/\3>/g;
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 本站常常無法取得圖片,因此得多重新檢查。
|
||||
// recheck:從頭檢測所有作品之所有章節與所有圖片。不會重新擷取圖片。對漫畫應該僅在偶爾需要從頭檢查時開啟此選項。
|
||||
// recheck : true,
|
||||
// 當有多個分部的時候才重新檢查。
|
||||
recheck : 'multi_parts_changed',
|
||||
// 當無法取得chapter資料時,直接嘗試下一章節。在手動+監視下recheck時可併用此項。
|
||||
// skip_chapter_data_error : true,
|
||||
|
||||
// allow .jpg without EOI mark.
|
||||
// allow_EOI_error : true,
|
||||
// 當圖像檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。
|
||||
// e.g., 736 黄昏流星群/单行本 0005
|
||||
// [黄昏流星群][弘兼宪史][尖端][volink]Vol_005/736-5-005.jpg
|
||||
skip_error : true,
|
||||
|
||||
// 單行本圖片較多且大,因此採用一個圖一個圖取得的方式。
|
||||
one_by_one : true,
|
||||
// 下載圖片的逾時ms數。若逾時時間太小(如10秒),下載大檔案容易失敗。
|
||||
timeout : 90 * 1000,
|
||||
|
||||
// reget_image_page : true,
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : 'search?q=',
|
||||
PATTERN_search : /<a href="\/manhua\/(\d+)" title="([^<>"]+)"/,
|
||||
parse_search_result : function(html, get_label) {
|
||||
// console.log(html);
|
||||
var PATTERN_search = this.PATTERN_search;
|
||||
var id_list = [], id_data = [];
|
||||
html.each_between('<div class="comicbook-index', '</div>',
|
||||
function(token) {
|
||||
// console.log(token);
|
||||
var matched = token.match(PATTERN_search);
|
||||
id_list.push(matched[1]);
|
||||
id_data.push(get_label(matched[2]));
|
||||
});
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return 'manhua/' + work_id;
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
// console.log(html);
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
// 漫画出版信息
|
||||
publish : get_label(html.between(
|
||||
'<div class="comic-pub-data-section', '</div>')
|
||||
.between('>')),
|
||||
// 概要 synopsis
|
||||
description : get_label(html.between(
|
||||
// manhuadb.js:
|
||||
'<div class="comic_detail_content">', '</div>')
|
||||
// manhuacat.js:
|
||||
|| html.between('<p class="comic_story">', '</p>')
|
||||
//
|
||||
.between('漫画简介:'))
|
||||
};
|
||||
|
||||
extract_work_data(work_data, html);
|
||||
extract_work_data(work_data, html,
|
||||
/<th scope="row">([^<>]+)<\/th>([\s\S]*?)<\/td>/g);
|
||||
|
||||
Object.assign(work_data, {
|
||||
title : work_data.book_name
|
||||
});
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
add_part : true,
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
// <div class="comic-toc-section bg-white p-3">
|
||||
// e.g., 一拳超人
|
||||
var part_title_list = html.between('<div class="comic-toc-section',
|
||||
'</ul>').all_between('<li', '</li>').map(function(token) {
|
||||
return get_label(token.between('>')).replace(/列表$/, '');
|
||||
});
|
||||
// console.log(part_title_list);
|
||||
|
||||
// <div class="tab-content" id="comic-book-list">
|
||||
html = html.between(' id="comic-book-list">', '<script ').between(
|
||||
null, {
|
||||
tail : '</ol>'
|
||||
});
|
||||
|
||||
var matched, part_NO = 0, part_title, PATTERN_title = new RegExp(
|
||||
work_data.title + '\\s*'), NO_in_part;
|
||||
|
||||
work_data.chapter_list = [];
|
||||
while (matched = PATTERN_chapter.exec(html)) {
|
||||
// delete matched.input;
|
||||
// console.log(matched);
|
||||
if (matched[4]) {
|
||||
part_title = get_label(matched[3]).replace(PATTERN_title,
|
||||
'').replace(/\[\]/g, '');
|
||||
part_title = part_title_list[part_NO++];
|
||||
NO_in_part = 0;
|
||||
continue;
|
||||
}
|
||||
++NO_in_part;
|
||||
var chapter_data = {
|
||||
// 漫畫目錄名稱不須包含分部號碼。使章節目錄名稱不包含 part_NO。
|
||||
// part_NO : part_NO,
|
||||
part_title : part_title,
|
||||
NO_in_part : NO_in_part,
|
||||
chapter_NO : NO_in_part,
|
||||
url : matched[1],
|
||||
title : get_label(matched[2])
|
||||
};
|
||||
work_data.chapter_list.push(chapter_data);
|
||||
continue;
|
||||
|
||||
// ----------------------------------
|
||||
// 以下: 若是存在舊格式的檔案就把它移成新格式。
|
||||
// @deprecated
|
||||
// console.log(chapter_data);
|
||||
|
||||
// chapter_data.title = chapter_data.title.replace('文传', '文傳');
|
||||
var old_directory = work_data.directory
|
||||
+ work_data.chapter_list.length
|
||||
// 4: @see chapter_directory_name
|
||||
// @ CeL.application.net.work_crawler.chapter
|
||||
.pad(work_data.chapter_NO_pad_digits || 4)
|
||||
+ ' '
|
||||
+ (chapter_data.title.includes('[') ? chapter_data.title
|
||||
: '[' + chapter_data.title + ']'),
|
||||
//
|
||||
new_directory = work_data.directory + part_title + ' '
|
||||
+ NO_in_part.pad(work_data.chapter_NO_pad_digits || 4)
|
||||
+ ' ' + chapter_data.title;
|
||||
if (library_namespace.directory_exists(old_directory)) {
|
||||
library_namespace.move_fso(old_directory, new_directory);
|
||||
}
|
||||
|
||||
var old_archive = old_directory + '.'
|
||||
+ this.images_archive_extension;
|
||||
if (library_namespace.file_exists(old_archive)) {
|
||||
library_namespace.log(old_archive + '\n→ ' + new_directory);
|
||||
var images_archive = new library_namespace.storage.archive(
|
||||
old_archive);
|
||||
images_archive.extract({
|
||||
cwd : images_archive
|
||||
});
|
||||
library_namespace.move_fso(old_directory, new_directory);
|
||||
library_namespace.remove_file(old_archive);
|
||||
}
|
||||
}
|
||||
|
||||
work_data.inverted_order = this.inverted_order;
|
||||
// console.log(work_data.chapter_list);
|
||||
// console.log(work_data);
|
||||
},
|
||||
|
||||
decode_chapter_data : function(chapter_data) {
|
||||
return JSON.parse(atob(chapter_data));
|
||||
},
|
||||
pre_parse_chapter_data
|
||||
// 執行在解析章節資料 process_chapter_data() 之前的作業 (async)。
|
||||
// 必須自行保證執行 callback(),不丟出異常、中斷。
|
||||
: function(XMLHttp, work_data, callback, chapter_NO) {
|
||||
// console.log(XMLHttp);
|
||||
// console.log(work_data);
|
||||
// console.log(work_data.chapter_list);
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1],
|
||||
//
|
||||
html = XMLHttp.responseText, _this = this, image_page_list = [];
|
||||
// console.log(html);
|
||||
|
||||
chapter_data.title = html.between('<h2 class="h4 text-center">',
|
||||
'</h2>')
|
||||
|| chapter_data.title;
|
||||
var matched = chapter_data.title.match(/^\[([^\[\]]+)\]$/);
|
||||
if (matched)
|
||||
chapter_data.title = matched[1];
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
// 2019/9/17 漫画DB 網站改版
|
||||
matched = html.between(" img_data = '", "';")
|
||||
// manhuacat.js
|
||||
|| html.between(' img_data = "', '"')
|
||||
// 2019/9/17 5:0
|
||||
|| html.between('localStorage.setItem("data:"', ');')
|
||||
.between("'", {
|
||||
tail : "'"
|
||||
});
|
||||
if (matched) {
|
||||
// console.log(atob(matched));
|
||||
// console.log(chapter_data);
|
||||
|
||||
// 2020/4 漫画DB 網站改版
|
||||
// @see https://www.manhuadb.com/assets/js/vg-read.js
|
||||
var image_prefix = this.image_prefix
|
||||
|| html.between(' data-host="', '"')
|
||||
+ html.between(' data-img_pre="', '"');
|
||||
// console.log(image_prefix);
|
||||
|
||||
// img_data is base64 encoded, need to do base64 decode before
|
||||
// json
|
||||
// decode
|
||||
chapter_data.image_list = this.decode_chapter_data(matched)
|
||||
// assert: Array.isArray(chapter_data.image_list);
|
||||
.map(function(image_data) {
|
||||
return {
|
||||
url : encodeURI(image_prefix
|
||||
//
|
||||
+ (image_data.img || image_data))
|
||||
};
|
||||
});
|
||||
// console.log(chapter_data.image_list);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
html.between('id="page-selector"', '</select>').each_between(
|
||||
//
|
||||
'<option value="', '</option>', function(token) {
|
||||
image_page_list.push({
|
||||
title : token.between('>'),
|
||||
url : token.between(null, '"')
|
||||
});
|
||||
});
|
||||
var image_count = image_page_list.length;
|
||||
// console.log(image_page_list);
|
||||
|
||||
if (!(image_count >= 0)) {
|
||||
throw work_data.title + ' #' + chapter_NO + ' '
|
||||
+ chapter_data.title + ': Cannot get image count!';
|
||||
}
|
||||
|
||||
// 將過去的 chapter_data.image_list cache 於 work_data.image_list。
|
||||
if (work_data.image_list) {
|
||||
chapter_data.image_list = work_data.image_list[chapter_NO - 1];
|
||||
if (!this.reget_image_page && chapter_data.image_list
|
||||
&& chapter_data.image_list.length === image_count) {
|
||||
library_namespace.debug(work_data.title + ' #' + chapter_NO
|
||||
+ ' ' + chapter_data.title + ': Already got '
|
||||
+ image_count + ' images.');
|
||||
chapter_data.image_list = chapter_data.image_list
|
||||
// .slice() 重建以節省記憶體用量。
|
||||
.slice().map(function(image_data) {
|
||||
// 僅保留網址資訊,節省記憶體用量。
|
||||
return typeof image_data === 'string' ? image_data
|
||||
// else assert: library_namespace.is_Object(image_data)
|
||||
: image_data.url;
|
||||
});
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
work_data.image_list = [];
|
||||
}
|
||||
|
||||
function extract_image(XMLHttp) {
|
||||
var html = XMLHttp.responseText,
|
||||
//
|
||||
url = html.between('<img class="img-fluid"', '>').between(
|
||||
' src="', '"');
|
||||
library_namespace.debug('Add image '
|
||||
+ chapter_data.image_list.length + '/' + image_count
|
||||
+ ': ' + url, 2, 'extract_image');
|
||||
if (!url && !_this.skip_error) {
|
||||
_this.onerror('No image url got: #'
|
||||
+ chapter_data.image_list.length + '/'
|
||||
+ image_count);
|
||||
}
|
||||
// 僅保留網址資訊,節省記憶體用量。
|
||||
chapter_data.image_list.push(url);
|
||||
}
|
||||
|
||||
chapter_data.image_list = [];
|
||||
if (image_count > 0)
|
||||
extract_image(XMLHttp);
|
||||
|
||||
library_namespace.run_serial(function(run_next, image_NO, index) {
|
||||
var image_page_url
|
||||
//
|
||||
= _this.full_URL(image_page_list[index - 1].url);
|
||||
// console.log('Get #' + index + ': ' + image_page_url);
|
||||
library_namespace.log_temporary('Get image data page of §'
|
||||
+ chapter_NO + ': ' + image_NO + '/' + image_count);
|
||||
library_namespace.get_URL(image_page_url, function(XMLHttp) {
|
||||
extract_image(XMLHttp);
|
||||
run_next();
|
||||
}, _this.charset, null, Object.assign({
|
||||
error_retry : _this.MAX_ERROR_RETRY
|
||||
}, _this.get_URL_options));
|
||||
}, image_count, 2, function() {
|
||||
work_data.image_list[chapter_NO - 1] = chapter_data.image_list
|
||||
// .slice() 重建以節省記憶體用量。
|
||||
.slice();
|
||||
callback();
|
||||
});
|
||||
},
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
// console.log(chapter_data);
|
||||
|
||||
// 已在 pre_parse_chapter_data() 設定完 {Array}chapter_data.image_list
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_manhuadb_comics_crawler(configuration, callback, initializer) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
if (typeof initializer === 'function') {
|
||||
initializer(crawler);
|
||||
}
|
||||
|
||||
if (!crawler.decoder_URL) {
|
||||
// e.g., comic.cmn-Hans-CN/manhuadb.js
|
||||
return crawler;
|
||||
}
|
||||
|
||||
// e.g., comic.cmn-Hans-CN/manhuacat.js
|
||||
library_namespace.get_URL_cache(crawler.decoder_URL, function(contents,
|
||||
error) {
|
||||
var LZString;
|
||||
contents = contents.replace(/var\s+(LZString)/, '$1');
|
||||
eval(contents);
|
||||
crawler.LZString = LZString;
|
||||
callback(crawler);
|
||||
}, {
|
||||
directory : crawler.main_directory
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return new_manhuadb_comics_crawler;
|
||||
}
|
426
app/node_modules/cejs/application/net/work_crawler/sites/manhuagui.js
generated
vendored
426
app/node_modules/cejs/application/net/work_crawler/sites/manhuagui.js
generated
vendored
@@ -1,426 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading manhuagui comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸漫畫網站 漫画柜 的工具。
|
||||
*
|
||||
* 2017/10: 爱看漫/看漫画改名(DNS被導引到)漫画柜
|
||||
*
|
||||
* 57mh 介面程式碼類似於 999comics。manhuagui 似乎是在這基礎上經過修改?
|
||||
* @see CeL.application.net.work_crawler.sites.SinMH2013
|
||||
*
|
||||
* @see http://www.manhua.demo.shenl.com/?theme=mhd
|
||||
* @see qTcms 晴天漫画程序 晴天漫画系统 http://manhua3.qingtiancms.net/
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.manhuagui(configuration, function(crawler) {
|
||||
start_crawler(crawler, typeof module === 'object' && module);
|
||||
}, function(crawler) {
|
||||
setup_crawler(crawler, typeof module === 'object' && module);
|
||||
});
|
||||
|
||||
</code>
|
||||
*
|
||||
* @since 2019/6/17 19:32:13 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/manhuagui.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/manhuagui_tw.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.manhuagui',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// core_9D227AD5A911B7758A332C9CA35C640C.js
|
||||
// 2017/3/11 20:6:35: core_33A91659E79CDC4A0F31ED884877F3EF.js
|
||||
// 2018/1/30與2/2之間改版:
|
||||
// http://c.3qfm.com/scripts/core_E95EAFDD32D7F97E369526C7AD9A8837.js
|
||||
// 2018/2/14:
|
||||
// http://cf.hamreus.com/scripts/core_6B1519CED0A3FA5ED82E8FBDA8F1AB90.js
|
||||
// 2018/3/16 00:17:25 GMT: add https: for image files
|
||||
// https://cf.hamreus.com/scripts/core_ABBA2B6ADC1DABE325D505BE3314C273.js
|
||||
// 2019/6/17 19:47:4
|
||||
// https://cf.hamreus.com/scripts/core_C0683FDCDEE69940232A703BDEB0F64F.js
|
||||
// https://cf.hamreus.com/scripts_tw/core_C0683FDCDEE69940232A703BDEB0F64F.js
|
||||
var core_filename = 'core_C0683FDCDEE69940232A703BDEB0F64F.js',
|
||||
// https://raw.githubusercontent.com/pieroxy/lz-string/master/libs/lz-string.js
|
||||
// 2017: main_3A454149B2D2500411BC344B15DB58A4.js'
|
||||
// 2018/2:
|
||||
// http://c.3qfm.com/scripts/config_25855B4C08F7A6545A30D049ABD0F9EE.js
|
||||
// 2018/2/14:
|
||||
// http://cf.hamreus.com/scripts/config_25855B4C08F7A6545A30D049ABD0F9EE.js
|
||||
// 2018/3/11 15:59:14 GMT:
|
||||
// https://cf.hamreus.com/scripts/config_FAF1BF617BAF8A691A828F80672D3588.js
|
||||
// https://cf.hamreus.com/scripts_tw/config_FAF1BF617BAF8A691A828F80672D3588.js
|
||||
decode_filename = 'config_FAF1BF617BAF8A691A828F80672D3588.js',
|
||||
/**
|
||||
* e.g., <code>
|
||||
<li><a href="/comic/17515/272218.html" title="第72话:一虎进击" class="status0" target="_blank"><span>第72话:一…<i>31p</i><em class="new"></em></span></a></li>
|
||||
|
||||
https://www.manhuagui.com/comic/4076/
|
||||
<script type="text/javascript">$.Tabs('#chapter-page-1 li', '#chapter-list-1 ul',{'trigger':'click'});</script><h4><span>单行本</span></h4><div class="chapter-list cf mt10" id='chapter-list-1'><ul style="display:block"><li><a href="/comic/4076/390110.html" title="第67卷" class="status0" target="_blank"><span>第67卷<i>180p</i></span></a></li>
|
||||
</code>
|
||||
*
|
||||
* [:]: incase href="javascript:;"
|
||||
*
|
||||
* matched: [ all, href, title, inner, part_title ]
|
||||
*/
|
||||
PATTERN_chapter = /<li><a href="([^"<>:]+)" title="([^"<>]+)"[^<>]*>(.+?)<\/a><\/li>|<h4>(.+?)<\/h4>/g;
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 當有多個分部的時候才重新檢查。
|
||||
recheck : 'multi_parts_changed',
|
||||
one_by_one : true,
|
||||
|
||||
base_URL : 'https://www.manhuagui.com/',
|
||||
script_base_URL : 'https://cf.hamreus.com/scripts/',
|
||||
|
||||
// {Natural}MIN_LENGTH:最小容許圖案檔案大小 (bytes)。
|
||||
MIN_LENGTH : 400,
|
||||
|
||||
// 當網站不允許太過頻繁的訪問/access時,可以設定下載之前的等待時間(ms)。
|
||||
// 模仿實際人工請求。
|
||||
// 2018/4: manhuagui 不允許過於頻繁的 access,會直接 ban IP。
|
||||
// 2018/7/12 22:29:18: 9s: NG, ban 2 hr.
|
||||
// 10s, 15s 在下載過100章(1 hr)之後一樣會 ban 5hr。
|
||||
// 20s, 30s 在下載過200章(~2 hr)之後一樣會 ban。
|
||||
// 60s 大致OK
|
||||
// 2019/2/6: 40s: NG, ban 1 day. 50s 在下載過50章後一樣會 ban。.5 day?
|
||||
// 2019/3/1: 181s: NG. 3~4min 時,似乎會不固定時間檢查、平均每天被封鎖一次,每次封鎖一日?
|
||||
chapter_time_interval : '4min',
|
||||
|
||||
// 2018/3/3 已經不再有常常出現錯誤的情況。
|
||||
// allow .jpg without EOI mark.
|
||||
// allow_EOI_error : true,
|
||||
// 當圖像檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。
|
||||
// skip_error : true,
|
||||
|
||||
// 取得伺服器列表。
|
||||
// use_server_cache : true,
|
||||
server_URL : function() {
|
||||
return this.script_base_URL + core_filename;
|
||||
},
|
||||
parse_server_list : function(html) {
|
||||
// console.log(html);
|
||||
var server_list = [];
|
||||
eval(html.between('var servs=', ',pfuncs=')).forEach(
|
||||
function(data) {
|
||||
data.hosts.forEach(function(server_data) {
|
||||
// @see SMH.utils.getPath() @ ((core_filename))
|
||||
server_list.push(server_data.h + '.hamreus.com');
|
||||
});
|
||||
});
|
||||
return server_list;
|
||||
},
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : '/tools/word.ashx?key=',
|
||||
parse_search_result : function(html) {
|
||||
/**
|
||||
* e.g.,<code>
|
||||
[ { "t": "西游", "u": "/comic/17515/", "s": false, "cid": 272218, "ct": "第72话:一虎进击", "a": "郑健和,邓志辉" } ]
|
||||
</code>
|
||||
*/
|
||||
var id_data = html ? JSON.parse(html) : [];
|
||||
return [ id_data, id_data ];
|
||||
},
|
||||
id_of_search_result : function(search_result) {
|
||||
// e.g., "/comic/123/"
|
||||
return +search_result.u.match(/^\/comic\/(\d+)\/$/)[1];
|
||||
},
|
||||
title_of_search_result : 't',
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return 'comic/' + work_id + '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(html.between('<h1>', '</h1>')),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
status : get_label(html.between('<li class="status">',
|
||||
'</span>').between('</strong>')),
|
||||
sub_title : get_label(html.between('<h1>', '</div>').between(
|
||||
'</h1>')),
|
||||
description : get_label(html.between('intro-all', '</div>')
|
||||
.between('>'))
|
||||
}, data = html.between('detail-list', '</ul>');
|
||||
extract_work_data(work_data, data,
|
||||
// e.g., "<strong>漫画别名:</strong>暂无</span>"
|
||||
/<strong[^<>]*>([^<>]+)<\/strong>(.+?)<\/span>/g);
|
||||
if (data = get_label(data.between('<li class="status">', '</li>'))) {
|
||||
library_namespace.log(data);
|
||||
}
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
var data, chapter_list = [], matched,
|
||||
//
|
||||
part_title, part_title_hash = Object.create(null), part_NO = 0;
|
||||
|
||||
// 有些尚使用舊模式。
|
||||
// @see http://www.ikanman.com/comic/8004/
|
||||
data = html.between('<div class="chapter-bar">',
|
||||
// <div class="comment mt10" id="Comment">
|
||||
'class="comment')
|
||||
// 2017/3/3? ikanman 改版
|
||||
|| LZString.decompressFromBase64(
|
||||
//
|
||||
html.between('id="__VIEWSTATE"', '>').between('value="', '"'));
|
||||
|
||||
while (matched = PATTERN_chapter.exec(data)) {
|
||||
// delete matched.input;
|
||||
// console.log(matched);
|
||||
var chapter_data = get_label(matched[4]);
|
||||
// console.log(chapter_data);
|
||||
if (chapter_data) {
|
||||
// console.log(chapter_data);
|
||||
part_title = chapter_data;
|
||||
part_title_hash[part_title]
|
||||
// last part NO. part_NO starts from 1
|
||||
= ++part_NO;
|
||||
continue;
|
||||
}
|
||||
|
||||
chapter_data = {
|
||||
url : matched[1],
|
||||
title : get_label(matched[2]
|
||||
// .check_downloaded_chapters() 必須先確保已獲得最終之
|
||||
// chapter_data.title。
|
||||
// + ' ' + matched[3].between('<i>', '</i>')
|
||||
)
|
||||
};
|
||||
if (matched = matched[1].match(/(\d+)\.html$/)) {
|
||||
chapter_data.id = matched[1] | 0;
|
||||
} else {
|
||||
chapter_list.some_without_id = chapter_data;
|
||||
}
|
||||
if (part_title) {
|
||||
chapter_data.part_title = part_title
|
||||
}
|
||||
chapter_list.push(chapter_data);
|
||||
}
|
||||
// console.log(chapter_list);
|
||||
|
||||
if (chapter_list.length === 0
|
||||
// e.g., <div class="book-btn"><a href="/comic/8772/86612.html"
|
||||
// target="_blank" title="1话" class="btn-read">开始阅读</a>
|
||||
&& (data = html.between('book-btn', '</a>'))) {
|
||||
// 尊敬的看漫画用户,应《》版权方的要求,现已删除屏蔽《》漫画所有卷和册,仅保留作品文字简介
|
||||
this.pre_chapter_URL = this._pre_chapter_URL;
|
||||
if (Array.isArray(work_data.chapter_list)
|
||||
&& work_data.chapter_list.length > 1) {
|
||||
work_data.last_download.chapter
|
||||
// use cache (old data)
|
||||
= work_data.chapter_list.length;
|
||||
} else {
|
||||
work_data.chapter_list = [ {
|
||||
url : data.match(/ href="([^<>"]+)"/)[1],
|
||||
title : data.match(/ title="([^<>"]+)"/)[1]
|
||||
} ];
|
||||
}
|
||||
chapter_list = work_data.chapter_list;
|
||||
} else {
|
||||
if (chapter_list.length > 1) {
|
||||
// 轉成由舊至新之順序。
|
||||
if (chapter_list.some_without_id) {
|
||||
library_namespace.warn({
|
||||
// gettext_config:{"id":"some-chapter-url-names-are-not-numbers-$1"}
|
||||
T : [ '有些篇章之URL檔名非數字:%1',
|
||||
//
|
||||
JSON.stringify(chapter_list.some_without_id) ]
|
||||
});
|
||||
chapter_list.reverse();
|
||||
} else {
|
||||
// 按照章節添加時間排序。
|
||||
chapter_list.sort(function(a, b) {
|
||||
// 排序以.html檔案檔名(序號)為準。
|
||||
// assert: 後來的檔名,序號會比較大。
|
||||
// @see http://www.ikanman.com/comic/8928/
|
||||
return a.id - b.id;
|
||||
});
|
||||
}
|
||||
// console.log(chapter_list);
|
||||
|
||||
// set latest/max part_NO
|
||||
chapter_list.part_NO = part_NO;
|
||||
|
||||
if (part_NO > 1) {
|
||||
// rearrange part_NO
|
||||
// 初始化 NO_in_part
|
||||
var NO_in_part_hash = new Array(part_NO + 1).fill(0);
|
||||
chapter_list.forEach(function(chapter_data) {
|
||||
chapter_data.part_NO
|
||||
// 在維持分部順序不動的情況下排序 NO_in_part
|
||||
= part_title_hash[chapter_data.part_title];
|
||||
chapter_data.NO_in_part
|
||||
//
|
||||
= ++NO_in_part_hash[chapter_data.part_NO];
|
||||
});
|
||||
// 重新按照 分部→章節順序 排序。
|
||||
chapter_list.sort(function(a, b) {
|
||||
// assert: max(NO_in_part) < 1e4
|
||||
return (a.part_NO - b.part_NO) * 1e4 + a.NO_in_part
|
||||
- b.NO_in_part;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(chapter_list);
|
||||
// console.log(JSON.stringify(chapter_list));
|
||||
// console.log(chapter_list.slice(0, 20));
|
||||
// console.log(chapter_list.slice(-20));
|
||||
|
||||
// console.log(work_data.chapter_list);
|
||||
work_data.chapter_list = chapter_list;
|
||||
|
||||
if (this.recheck === 'multi_parts_changed'
|
||||
&& chapter_list.part_NO > 1) {
|
||||
this.check_downloaded_chapters(work_data, chapter_list);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 取得每一個章節的各個影像內容資料。 get_chapter_data()
|
||||
_pre_chapter_URL : function(work_data, chapter_NO, callback) {
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1],
|
||||
// e.g., "/comic/8772/86612.html"
|
||||
chapter_id = +chapter_data.url.match(/^\/comic\/\d+\/(\d+)\.html$/)[1];
|
||||
library_namespace.get_URL(this.base_URL
|
||||
+ 'support/chapter.ashx?bid=' + work_data.id + '&cid='
|
||||
+ chapter_id, function(XMLHttp) {
|
||||
// console.log(XMLHttp.responseText);
|
||||
chapter_data.sibling = JSON.parse(XMLHttp.responseText);
|
||||
if (chapter_data.sibling.n > 0
|
||||
&& work_data.chapter_count === chapter_NO) {
|
||||
// 還有下一chapter。
|
||||
work_data.chapter_list.push({
|
||||
url : chapter_data.url.replace(/(\d+)\.html$/,
|
||||
chapter_data.sibling.n + '.html')
|
||||
});
|
||||
work_data.chapter_count = work_data.chapter_list.length;
|
||||
}
|
||||
callback();
|
||||
}, null, null, this.get_URL_options);
|
||||
},
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// decode chapter data
|
||||
function decode_2016(code) {
|
||||
code = eval(code);
|
||||
eval(code.replace('eval', 'code='));
|
||||
eval(code.replace(/^[^=]+/, 'code'));
|
||||
return code;
|
||||
}
|
||||
|
||||
// 2017/3/3? ikanman 改版
|
||||
// String.prototype.splic: used in chapter
|
||||
function decode(code) {
|
||||
code = eval(code);
|
||||
// 2018/3/16 改版
|
||||
eval(code.between(null, {
|
||||
tail : ').preInit()'
|
||||
}).replace('SMH.imgData(', 'code='));
|
||||
return code;
|
||||
}
|
||||
|
||||
var chapter_data = html.between(
|
||||
// window["eval"], window["\x65\x76\x61\x6c"]
|
||||
'<script type="text/javascript">window["\\x65\\x76\\x61\\x6c"]',
|
||||
'</script>');
|
||||
if (!chapter_data || !(chapter_data = decode(chapter_data))) {
|
||||
library_namespace.warn({
|
||||
// gettext_config:{"id":"unable-to-parse-chapter-data-for-«$1»-§$2"}
|
||||
T : [ '無法解析《%1》§%2 之章節資料!', work_data.title, chapter_NO ]
|
||||
});
|
||||
return;
|
||||
}
|
||||
chapter_data = Object.assign(
|
||||
work_data.chapter_list[chapter_NO - 1], chapter_data);
|
||||
// for debug
|
||||
// console.log(chapter_data);
|
||||
// throw this.id + ': debug throw';
|
||||
|
||||
// 設定必要的屬性。
|
||||
chapter_data.title = chapter_data.cname;
|
||||
chapter_data.image_count = chapter_data.len;
|
||||
|
||||
// e.g., "/ps3/q/qilingu_xmh/第01回上/"
|
||||
var path = encodeURI(chapter_data.path),
|
||||
// 令牌 @see SMH.utils.getPicUrl() @ ((core_filename))
|
||||
token = '?cid=' + chapter_data.cid + '&'
|
||||
//
|
||||
+ new URLSearchParams(chapter_data.sl);
|
||||
// 漫畫櫃的webp圖像檔案可能是即時生成的? 大小常常不一樣。
|
||||
chapter_data.image_list = chapter_data.files.map(function(url) {
|
||||
return {
|
||||
url : path + url + token
|
||||
}
|
||||
});
|
||||
// 當一次下載上百張相片的時候,就會被封鎖IP。因此改成一個個下載圖像。
|
||||
this.one_by_one = chapter_data.image_list.length > 30;
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_manhuagui_comics_crawler(configuration, callback, initializer) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
if (typeof initializer === 'function') {
|
||||
initializer(crawler);
|
||||
}
|
||||
|
||||
// 創建 main directory。
|
||||
library_namespace.create_directory(crawler.main_directory);
|
||||
|
||||
var LZString;
|
||||
library_namespace.get_URL_cache(crawler.script_base_URL
|
||||
+ decode_filename,
|
||||
// 2017/3/3? ikanman 改版
|
||||
function(contents, error) {
|
||||
contents = contents.between('\nwindow["\\x65\\x76\\x61\\x6c"]',
|
||||
';\n')
|
||||
//
|
||||
.replace(/window\[([^\[\]]+)\]/g, function($0, key) {
|
||||
return eval(key);
|
||||
});
|
||||
contents = eval(contents).replace(/^var /, '');
|
||||
eval(contents);
|
||||
callback(crawler);
|
||||
}, crawler.main_directory + decode_filename);
|
||||
|
||||
}
|
||||
|
||||
return new_manhuagui_comics_crawler;
|
||||
}
|
206
app/node_modules/cejs/application/net/work_crawler/sites/qTcms2014.js
generated
vendored
206
app/node_modules/cejs/application/net/work_crawler/sites/qTcms2014.js
generated
vendored
@@ -1,206 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading **maybe** qTcms 2014 version comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸常見漫畫管理系統: **可能為** 舊型晴天漫画CMS (晴天漫画系统 晴天漫画程序,
|
||||
* NOT 晴天新漫画系统) 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.qTcms2014(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @see http://manhua.qingtiancms.com/
|
||||
*
|
||||
* @since 2019/1/21 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see archive/733dm.201811.js , archive/733dm.201808.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/katui.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.qTcms2014',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 所有的子檔案要修訂註解說明時,應該都要順便更改在CeL.application.net.comic中Comic_site.prototype內的母comments,並以其為主體。
|
||||
|
||||
// 本站常常無法取得圖片,因此得多重新檢查。
|
||||
// recheck:從頭檢測所有作品之所有章節與所有圖片。不會重新擷取圖片。對漫畫應該僅在偶爾需要從頭檢查時開啟此選項。
|
||||
// recheck : true,
|
||||
// 當無法取得chapter資料時,直接嘗試下一章節。在手動+監視下recheck時可併用此項。
|
||||
// skip_chapter_data_error : true,
|
||||
|
||||
// allow .jpg without EOI mark.
|
||||
// allow_EOI_error : true,
|
||||
// 當圖像檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。
|
||||
// skip_error : true,
|
||||
|
||||
// one_by_one : true,
|
||||
// base_URL : 'http://www.___.net/',
|
||||
charset : 'gb2312',
|
||||
|
||||
// 取得伺服器列表。
|
||||
// use_server_cache : true,
|
||||
// katui: /skin/2014mh/global.js
|
||||
// pufei: /skin/2014mh/global.js?v=41
|
||||
// taduo: /skin/2014mh/global.js?v=42
|
||||
server_URL : 'skin/2014mh/global.js',
|
||||
parse_server_list : function(html) {
|
||||
var server_list = [],
|
||||
// e.g., WebimgServerURL[0]="http://img.tsjjx.com/"
|
||||
// WebimgServerURL[0]="http://www.733mh.com/fd.php?url=http://img.tsjjx.com/";
|
||||
matched, PATTERN = /\nWebimgServerURL\[\d\]\s*=\s*"([^"]+)"/g;
|
||||
while (matched = PATTERN.exec(html)) {
|
||||
server_list.push(matched[1].between('url=') || matched[1]);
|
||||
}
|
||||
// console.log(server_list);
|
||||
return server_list;
|
||||
},
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : function(work_title) {
|
||||
return [ 'e/search/index.php', {
|
||||
// orderby : 1,
|
||||
// myorder : 1,
|
||||
tbname : 'mh',
|
||||
// tempid:1 @ https://www.dagumanhua.com/
|
||||
tempid : 3,
|
||||
show : 'title,player,playadmin,bieming,pinyin',
|
||||
keyboard : work_title
|
||||
} ];
|
||||
},
|
||||
parse_search_result : function(html) {
|
||||
// console.log(html);
|
||||
html = html.between('id="dmList"', '</div>');
|
||||
var id_list = [], id_data = [];
|
||||
html.each_between('<li>', '</li>', function(token) {
|
||||
var matched = token.match(
|
||||
// pufei.js: <dt><a href="/manhua/32695/index.html"
|
||||
// title="我靠美食来升级">我靠美食来升级</a></dt>
|
||||
/<dt><a href="\/(?:mh|manhua)\/(\d+)(?:\/index\.html)?" title="([^"]+)">/
|
||||
//
|
||||
);
|
||||
id_list.push(matched[1]);
|
||||
id_data.push(matched[2]);
|
||||
});
|
||||
// console.log([ id_list, id_data ]);
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return 'manhua/' + work_id + '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label) {
|
||||
var text = html.between('<div class="detailInfo">',
|
||||
'<div class="intro'),
|
||||
// work_data={id,title,author,authors,chapters,last_update,last_download:{date,chapter}}
|
||||
work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(
|
||||
//
|
||||
text.between('<div class="titleInfo">', '</h1>')),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
status : get_label(text.between('</h1><span>', '</span>')),
|
||||
description : get_label(html.between(
|
||||
'<div class="introduction" id="intro1">', '</div>'))
|
||||
};
|
||||
text.each_between('<li class="twoCol">', '</li>', function(token) {
|
||||
work_data[get_label(token.between('<span>', '</span>'))
|
||||
.replace(/:$/, '')] = get_label(token
|
||||
.between('</span>'));
|
||||
});
|
||||
Object.assign(work_data, {
|
||||
author : work_data.作者,
|
||||
last_update : work_data.更新时间
|
||||
});
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
html = html.between('<div id="section">',
|
||||
'<div class="description">');
|
||||
work_data.chapter_list = [];
|
||||
var matched,
|
||||
// [ , chapter_url, chapter_title ]
|
||||
PATTERN_chapter = /<a href="(\/manhua\/[^"]+)" title="([^"]+)"/g;
|
||||
while (matched = PATTERN_chapter.exec(html)) {
|
||||
work_data.chapter_list.push({
|
||||
url : matched[1],
|
||||
title : get_label(matched[2])
|
||||
});
|
||||
}
|
||||
if (work_data.chapter_list.length > 1) {
|
||||
// 轉成由舊至新之順序。
|
||||
work_data.chapter_list.reverse();
|
||||
}
|
||||
},
|
||||
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
function decode(packed) {
|
||||
var photosr = [];
|
||||
// decode chapter data @ every picture page
|
||||
eval(eval(Buffer.from(packed, 'base64').toString().slice(4)));
|
||||
// 通常[0]===undefined
|
||||
return photosr.filter(function(url) {
|
||||
return url;
|
||||
});
|
||||
}
|
||||
|
||||
var chapter_data = html && html.between('packed="', '"');
|
||||
if (!chapter_data || !(chapter_data = decode(chapter_data))) {
|
||||
return;
|
||||
}
|
||||
// console.log(JSON.stringify(chapter_data));
|
||||
// console.log(chapter_data.length);
|
||||
// library_namespace.set_debug(6);
|
||||
|
||||
if (typeof this.postfix_image_url === 'function')
|
||||
chapter_data = chapter_data.map(this.postfix_image_url);
|
||||
|
||||
chapter_data = Object.assign(
|
||||
// 設定必要的屬性。
|
||||
work_data.chapter_list[chapter_NO - 1], {
|
||||
image_list : chapter_data
|
||||
});
|
||||
// console.log(JSON.stringify(chapter_data));
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_qTcms2014_comics_crawler(configuration) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
return new library_namespace.work_crawler(configuration);
|
||||
}
|
||||
|
||||
return new_qTcms2014_comics_crawler;
|
||||
}
|
553
app/node_modules/cejs/application/net/work_crawler/sites/qTcms2017.js
generated
vendored
553
app/node_modules/cejs/application/net/work_crawler/sites/qTcms2017.js
generated
vendored
@@ -1,553 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading qTcms version 20170501-20190606010315
|
||||
* comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載中國大陸常見漫畫管理系統: 晴天漫画CMS (晴天漫画系统 晴天漫画程序, 晴天新漫画系统)
|
||||
* PC端网站 + 手机端网站(行動版 mobile version) 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.qTcms2017(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* modify from 9mdm.js→dagu.js, mh160.js
|
||||
*
|
||||
* @see qTcms 晴天漫画程序 晴天漫画系统 http://manhua.qingtiancms.com/
|
||||
*
|
||||
* @since 2019/2/3 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see comic.cmn-Hans-CN/nokiacn.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.qTcms2017',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 所有的子檔案要修訂註解說明時,應該都要順便更改在CeL.application.net.comic中Comic_site.prototype內的母comments,並以其為主體。
|
||||
|
||||
// 因為要經過轉址,所以一個圖一個圖來。
|
||||
// one_by_one : true,
|
||||
// base_URL : '',
|
||||
|
||||
// fs.readdirSync('.').forEach(function(d){if(/^\d+\s/.test(d))fs.renameSync(d,'manhua-'+d);})
|
||||
// fs.readdirSync('.').forEach(function(d){if(/^manhua-/.test(d))fs.renameSync(d,d.replace(/^manhua-/,''));})
|
||||
// 所有作品都使用這種作品類別catalog前綴。
|
||||
// common_catalog : 'manhua',
|
||||
|
||||
// 規範 work id 的正規模式;提取出引數中的作品id 以回傳。
|
||||
extract_work_id : function(work_information) {
|
||||
if ((this.common_catalog ? /^[a-z\-\d]+$/ : /^[a-z]+_[a-z\-\d]+$/)
|
||||
.test(work_information))
|
||||
return work_information;
|
||||
},
|
||||
|
||||
// --------------------------------------
|
||||
// search comic via web page
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL_web : 'statics/search.aspx?key=',
|
||||
parse_search_result_web : function(html, get_label) {
|
||||
// console.log(html);
|
||||
html = html.between('<div class="cy_list">', '</div>');
|
||||
// console.log(html);
|
||||
var id_list = [], id_data = [];
|
||||
html.each_between('<li class="title">', '</li>', function(token) {
|
||||
// console.log(token);
|
||||
var matched = token.match(
|
||||
// [ id, title ]
|
||||
/<a href="\/([a-z]+\/[a-z\-\d]+)\/"[^<>]*?>([^<>]+)/);
|
||||
// console.log(matched);
|
||||
if (this.common_catalog
|
||||
// 去掉所有不包含作品類別catalog前綴者。
|
||||
&& !matched[1].startsWith(this.common_catalog + '/'))
|
||||
return;
|
||||
id_list.push(this.common_catalog
|
||||
//
|
||||
? matched[1].slice((this.common_catalog + '/').length)
|
||||
// catalog/latin name
|
||||
: matched[1].replace('/', '_'));
|
||||
id_data.push(get_label(matched[2]));
|
||||
}, this);
|
||||
// console.log([ id_list, id_data ]);
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// --------------------------------------
|
||||
// default: search comic via API
|
||||
// copy from 360taofu.js
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : function(work_title) {
|
||||
return [ 'statics/qingtiancms.ashx', {
|
||||
cb : 'jQuery' + ('1.7.2' + Math.random()).replace(/\D/g, "")
|
||||
// @see .expando
|
||||
+ '_' + Date.now(),
|
||||
key : work_title,
|
||||
action : 'GetSear1',
|
||||
_ : Date.now()
|
||||
} ];
|
||||
},
|
||||
parse_search_result : function(html, get_label) {
|
||||
// console.log(html);
|
||||
var data = eval(html.between('(', {
|
||||
tail : ')'
|
||||
}));
|
||||
// console.log(data);
|
||||
return [ data, data ];
|
||||
},
|
||||
id_of_search_result : function(data) {
|
||||
// console.log(data);
|
||||
|
||||
// PC version: .u: webdir + classid1pinyin + titlepinyin + "/"
|
||||
// webdir: "/"
|
||||
// classid1pinyin: latin + "/"
|
||||
// titlepinyin: latin
|
||||
var matched = data.u
|
||||
// mobile version
|
||||
|| data.url;
|
||||
matched = matched.match(/(?:\/|^)([a-z]+)\/([a-z\-\d]+)\/$/);
|
||||
|
||||
// assert: !!matched === true
|
||||
if (!this.common_catalog)
|
||||
return matched[1] + '_' + matched[2];
|
||||
|
||||
// assert: this.common_catalog === matched[1]
|
||||
return matched[2];
|
||||
},
|
||||
title_of_search_result : 't',
|
||||
|
||||
// --------------------------------------
|
||||
// for mobile version
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL_mobile : function(work_title) {
|
||||
return [ 'statics/qingtiancms.ashx', {
|
||||
action : 'GetWapSear1',
|
||||
key : work_title
|
||||
} ];
|
||||
},
|
||||
parse_search_result_mobile : function(html, get_label) {
|
||||
/**
|
||||
* @example <code>
|
||||
{"result": 1000,"msg": "提交成功","data": [{name:'读书成圣',last_update_chapter_name:'014 禁忌十八式',last_updatetime:'',types:'',authors:'',url:'/rexue/dushuchengsheng/'}],"page_data": ""}
|
||||
</code>
|
||||
*/
|
||||
// console.log(JSON.stringify(html));
|
||||
var data;
|
||||
try {
|
||||
eval('data=' + html);
|
||||
data = data.data;
|
||||
} catch (e) {
|
||||
// e.g., "{err!}"
|
||||
data = [];
|
||||
}
|
||||
// console.log(data);
|
||||
return [ data, data ];
|
||||
},
|
||||
title_of_search_result_mobile : 'name',
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return (this.common_catalog ? this.common_catalog + '/' + work_id
|
||||
// replace only the first '_' to '/'
|
||||
: work_id.replace('_', '/')) + '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
// console.log(html);
|
||||
var work_data = html.between('qingtiancms_Details=', ';var');
|
||||
if (work_data) {
|
||||
/**
|
||||
* PC version:
|
||||
*
|
||||
* @example <code>
|
||||
var qingtiancms_Details={G_mubanpage:".html",id:"6638",hits:"9454",webdir:"/",pinglunid:"10",pinglunid1:"",pinglunid2:"cytdbnhsU",pinglunid3:"prod_1368b8102b9177303c660debbbbd257c",title:"读书成圣",classid1pinyin:"rexue/",titlepinyin:"dushuchengsheng"};var uyan_config = {'su':'/6638/'};
|
||||
</code>
|
||||
*/
|
||||
eval('work_data=' + work_data);
|
||||
} else {
|
||||
// dagu.js: has NO `qingtiancms_Details`
|
||||
work_data = Object.create(null);
|
||||
}
|
||||
|
||||
// PC version: nokiacn.js, iqg365.js, 733dm.js
|
||||
extract_work_data(work_data, html.between(
|
||||
// <div class="cy_title">\n <h1>相合之物</h1>
|
||||
'<h1>', ' id="comic-description">'),
|
||||
/<span>([^<>:]+):([\s\S]*?)<\/span>/g);
|
||||
|
||||
// PC version: 360taofu.js
|
||||
extract_work_data(work_data, html.between(
|
||||
// <div class="mh-date-info fl">\n <div class="mh-date-info-name">
|
||||
'<div class="mh-date-info', '<div class="work-author">'),
|
||||
// <span class="one"> 作者: <em>... </span>
|
||||
// <span> 人气: <em... </span>
|
||||
// 人气: 收藏数: 吐槽: 状态:
|
||||
/<span[^<>]*>([^<>:]+):([\s\S]*?)<\/span>/g);
|
||||
|
||||
// PC version 共通
|
||||
extract_work_data(work_data, html.between(
|
||||
// <div class="cy_zhangjie">...<div class="cy_zhangjie_top">
|
||||
'<div class="cy_zhangjie_top">',
|
||||
// <div class="cy_plist" id="play_0">
|
||||
' class="cy_plist"'), /<p>([^<>:]+):([\s\S]*?)<\/p>/g);
|
||||
|
||||
// PC version, mobile version 共通
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
Object.assign(work_data, this.is_mobile ? {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
last_update : html.between('<span class="date">', '</span>'),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
|
||||
// 網頁中列的description比meta中的完整。
|
||||
description : get_label(html.between(
|
||||
// 友绘漫画网
|
||||
// <p class="txtDesc autoHeight">介绍:...</p>
|
||||
'<p class="txtDesc autoHeight">', '</p>'))
|
||||
} : {
|
||||
// 避免覆寫
|
||||
qTid : work_data.id,
|
||||
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : work_data.title
|
||||
// nokiacn.js, iqg365.js, 733dm.js
|
||||
|| get_label(html.between('<h1>', '</h1>')),
|
||||
author : work_data.作者,
|
||||
status : work_data.状态,
|
||||
last_update : work_data.更新时间,
|
||||
latest_chapter : work_data.最新话,
|
||||
latest_chapter_url : html.between('最新话:<a href="', '"'),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
评分 : work_data.评分 || get_label(html.between(
|
||||
// 360taofu.js: <p class="fl">评分:<strong class="ui-text-orange"
|
||||
// id="comicStarDis">...</p>
|
||||
' id="comicStarDis">', '</p>')),
|
||||
|
||||
// 網頁中列的description比meta中的完整。
|
||||
description : get_label(html.between(
|
||||
// nokiacn.js, iqg365.js, 733dm.js
|
||||
// <p id="comic-description">...</p>
|
||||
' id="comic-description">', '</')) || get_label(html.between(
|
||||
// 360taofu.js: <div id="workint" class="work-ov">
|
||||
' id="workint"', '</div>').between('>'))
|
||||
});
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
html = html.between('<div class="cy_plist', '</div>')
|
||||
// mobile version: <div id="list">
|
||||
// <ul class="Drama autoHeight" id="mh-chapter-list-ol-0">
|
||||
// 88bag.js: <div id="list" >
|
||||
|| html.between('<div id="list"', '</ul>');
|
||||
// console.log(html);
|
||||
|
||||
/**
|
||||
* <code>
|
||||
76.js: <li><a target="_blank" href="/chuanyue/tangyinzaiyijie/351280.html"><p>杀手唐寅</p><i></i></a></li>
|
||||
</code>
|
||||
*/
|
||||
var matched, PATTERN_chapter =
|
||||
// matched: [ all, url, inner ]
|
||||
/<li><a [^<>]*?href="([^<>"]+)"[^<>]*>([\s\S]+?)<\/li>/g;
|
||||
|
||||
work_data.chapter_list = [];
|
||||
while (matched = PATTERN_chapter.exec(html)) {
|
||||
var chapter_data = {
|
||||
url : matched[1],
|
||||
title : get_label(matched[2])
|
||||
};
|
||||
work_data.chapter_list.push(chapter_data);
|
||||
}
|
||||
// PC version, mobile version 共通
|
||||
work_data.chapter_list.reverse();
|
||||
// console.log(work_data.chapter_list);
|
||||
},
|
||||
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// modify from mh160.js
|
||||
// console.log(html);
|
||||
|
||||
var chapter_data = html.between('qTcms_S_m_murl_e="', '"');
|
||||
// console.log(chapter_data);
|
||||
if (chapter_data) {
|
||||
// 對於非utf-8編碼之中文,不能使用 atob()
|
||||
// e.g., http://www.aikanmh.cn/xuanhuan/zhutianji/499631.html
|
||||
chapter_data = base64_decode(chapter_data)
|
||||
.split("$qingtiandy$");
|
||||
}
|
||||
if (!Array.isArray(chapter_data)) {
|
||||
library_namespace.warn({
|
||||
// gettext_config:{"id":"unable-to-parse-chapter-data-for-«$1»-§$2"}
|
||||
T : [ '無法解析《%1》§%2 之章節資料!', work_data.title, chapter_NO ]
|
||||
});
|
||||
return;
|
||||
}
|
||||
// console.log(chapter_data);
|
||||
// console.log(JSON.stringify(chapter_data));
|
||||
// console.log(chapter_data.length);
|
||||
// library_namespace.set_debug(6);
|
||||
|
||||
// e.g., http://m.88bag.net/rexue/zuomeigongyu/36279.html
|
||||
// @see
|
||||
// http://m.88bag.net/template/wap1/css/d7s/js/show.20170501.js?20190722091626
|
||||
if (chapter_data.length === 1
|
||||
&& /^(--|\+)https?:\/\//.test(chapter_data[0])) {
|
||||
chapter_data = {
|
||||
limited : chapter_data[0].startsWith('+') ? '对不起,该章节已经下架!!本站仅提供检索服务,请尊重作品版权'
|
||||
: '请点击下方链接开始观看本期漫画:' + chapter_data[0].slice(2)
|
||||
};
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
// 設定必要的屬性。
|
||||
chapter_data = {
|
||||
image_list : chapter_data.map(function(url) {
|
||||
// 2019/10/20: 採用 base64_decode() 取代 atob() 後,
|
||||
// aikanmh 不可再 encodeURI()。
|
||||
// url = encodeURI(url);
|
||||
|
||||
// 获取当前图片 function f_qTcms_Pic_curUrl_realpic(v)
|
||||
// http://www.xatxwh.com/template/skin1/css/d7s/js/show.20170501.js?20190117082944
|
||||
|
||||
// f_qTcms_Pic_curUrl() → f_qTcms_Pic_curUrl_realpic(v) @
|
||||
// http://www.nokiacn.net/template/skin2/css/d7s/js/show.20170501.js?20180805095630
|
||||
|
||||
if (this.for_each_image) {
|
||||
// 733dm.js
|
||||
// for_each_image:function(url,parameters,base64_encode){return(url);}
|
||||
url = this.for_each_image(url, {
|
||||
qTcms_S_m_id : html
|
||||
.between('qTcms_Pic_m_if="', '"'),
|
||||
qTcms_S_p_id : html.between('qTcms_S_p_id="', '"')
|
||||
}, base64_encode);
|
||||
} else if (url.startsWith('/')) {
|
||||
// e.g., nokiacn.js
|
||||
var image_base_url = this.image_base_url;
|
||||
if (!image_base_url && image_base_url !== '') {
|
||||
// default: url = qTcms_m_weburl + url;
|
||||
image_base_url = html.between('qTcms_m_weburl="',
|
||||
'"');
|
||||
}
|
||||
url = image_base_url + url;
|
||||
|
||||
} else if (html.between('qTcms_Pic_m_if="', '"') !== "2") {
|
||||
// e.g.,
|
||||
// http://www.nokiacn.net/lianai/caozuo100/134257.html
|
||||
url = url.replace(/\?/gi, "a1a1")
|
||||
.replace(/&/gi, "b1b1").replace(/%/gi, "c1c1");
|
||||
url = (this.qTcms_m_indexurl
|
||||
// this.qTcms_m_indexurl: e.g., 517.js
|
||||
|| html.between('qTcms_m_indexurl="', '"') || '/')
|
||||
+ "statics/pic/?p="
|
||||
+ escape(url)
|
||||
+ "&picid="
|
||||
+ html.between('qTcms_S_m_id="', '"')
|
||||
+ "&m_httpurl="
|
||||
+ escape(base64_decode(html.between(
|
||||
'qTcms_S_m_mhttpurl="', '"')));
|
||||
// Should get Status Code: 302 Found
|
||||
}
|
||||
|
||||
return {
|
||||
url : url
|
||||
};
|
||||
}, this)
|
||||
};
|
||||
// console.log(JSON.stringify(chapter_data));
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// http://www.aikanmh.cn/template/skin1/css/d7s/js/show.20170501.js?20191014154954
|
||||
function utf8_decode(str_data) {
|
||||
var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0;
|
||||
str_data += '';
|
||||
while (i < str_data.length) {
|
||||
c1 = str_data.charCodeAt(i);
|
||||
if (c1 < 128) {
|
||||
tmp_arr[ac++] = String.fromCharCode(c1);
|
||||
i++;
|
||||
} else if ((c1 > 191) && (c1 < 224)) {
|
||||
c2 = str_data.charCodeAt(i + 1);
|
||||
tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6)
|
||||
| (c2 & 63));
|
||||
i += 2;
|
||||
} else {
|
||||
c2 = str_data.charCodeAt(i + 1);
|
||||
c3 = str_data.charCodeAt(i + 2);
|
||||
tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12)
|
||||
| ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
return tmp_arr.join('');
|
||||
}
|
||||
|
||||
// 對於非utf-8編碼之中文,不能使用 atob()
|
||||
function base64_decode(data) {
|
||||
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = "", tmp_arr = [];
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
data += '';
|
||||
do {
|
||||
h1 = b64.indexOf(data.charAt(i++));
|
||||
h2 = b64.indexOf(data.charAt(i++));
|
||||
h3 = b64.indexOf(data.charAt(i++));
|
||||
h4 = b64.indexOf(data.charAt(i++));
|
||||
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
|
||||
o1 = bits >> 16 & 0xff;
|
||||
o2 = bits >> 8 & 0xff;
|
||||
o3 = bits & 0xff;
|
||||
if (h3 == 64) {
|
||||
tmp_arr[ac++] = String.fromCharCode(o1);
|
||||
} else if (h4 == 64) {
|
||||
tmp_arr[ac++] = String.fromCharCode(o1, o2);
|
||||
} else {
|
||||
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
|
||||
}
|
||||
} while (i < data.length);
|
||||
dec = tmp_arr.join('');
|
||||
dec = utf8_decode(dec);
|
||||
return dec;
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
|
||||
function utf8_encode(argString) {
|
||||
var string = (argString + '');
|
||||
var utftext = "";
|
||||
var start, end;
|
||||
var stringl = 0;
|
||||
start = end = 0;
|
||||
stringl = string.length;
|
||||
for (var n = 0; n < stringl; n++) {
|
||||
var c1 = string.charCodeAt(n);
|
||||
var enc = null;
|
||||
if (c1 < 128) {
|
||||
end++;
|
||||
} else if (c1 > 127 && c1 < 2048) {
|
||||
enc = String.fromCharCode((c1 >> 6) | 192)
|
||||
+ String.fromCharCode((c1 & 63) | 128);
|
||||
} else {
|
||||
enc = String.fromCharCode((c1 >> 12) | 224)
|
||||
+ String.fromCharCode(((c1 >> 6) & 63) | 128)
|
||||
+ String.fromCharCode((c1 & 63) | 128);
|
||||
}
|
||||
if (enc !== null) {
|
||||
if (end > start) {
|
||||
utftext += string.substring(start, end);
|
||||
}
|
||||
utftext += enc;
|
||||
start = end = n + 1;
|
||||
}
|
||||
}
|
||||
if (end > start) {
|
||||
utftext += string.substring(start, string.length);
|
||||
}
|
||||
return utftext;
|
||||
}
|
||||
|
||||
// btoa()
|
||||
function base64_encode(data) {
|
||||
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "", tmp_arr = [];
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
data = utf8_encode(data + '');
|
||||
do {
|
||||
o1 = data.charCodeAt(i++);
|
||||
o2 = data.charCodeAt(i++);
|
||||
o3 = data.charCodeAt(i++);
|
||||
bits = o1 << 16 | o2 << 8 | o3;
|
||||
h1 = bits >> 18 & 0x3f;
|
||||
h2 = bits >> 12 & 0x3f;
|
||||
h3 = bits >> 6 & 0x3f;
|
||||
h4 = bits & 0x3f;
|
||||
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3)
|
||||
+ b64.charAt(h4);
|
||||
} while (i < data.length);
|
||||
enc = tmp_arr.join('');
|
||||
switch (data.length % 3) {
|
||||
case 1:
|
||||
enc = enc.slice(0, -2) + '==';
|
||||
break;
|
||||
case 2:
|
||||
enc = enc.slice(0, -1) + '=';
|
||||
break;
|
||||
}
|
||||
return enc;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_qTcms2017_comics_crawler(configuration) {
|
||||
var using_configuration = Object.clone(default_configuration);
|
||||
|
||||
if (configuration.using_web_search) {
|
||||
Object.assign(using_configuration, {
|
||||
search_URL : using_configuration.search_URL_web,
|
||||
parse_search_result :
|
||||
//
|
||||
using_configuration.parse_search_result_web,
|
||||
id_of_search_result : null,
|
||||
title_of_search_result : null
|
||||
});
|
||||
} else if (configuration.is_mobile === undefined) {
|
||||
using_configuration.is_mobile = configuration.base_URL
|
||||
.includes('://m.');
|
||||
if (using_configuration.is_mobile) {
|
||||
Object.assign(using_configuration, {
|
||||
search_URL : using_configuration.search_URL_mobile,
|
||||
parse_search_result :
|
||||
//
|
||||
using_configuration.parse_search_result_mobile,
|
||||
title_of_search_result :
|
||||
//
|
||||
using_configuration.title_of_search_result_mobile
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
return new library_namespace.work_crawler(Object.assign(
|
||||
using_configuration, configuration));
|
||||
}
|
||||
|
||||
return new_qTcms2017_comics_crawler;
|
||||
}
|
160
app/node_modules/cejs/application/net/work_crawler/sites/sequential.js
generated
vendored
160
app/node_modules/cejs/application/net/work_crawler/sites/sequential.js
generated
vendored
@@ -1,160 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading sequential comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了處理、批量下載 可預測圖片網址序列的漫畫 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.work_crawler.sequential(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* 本檔案為僅僅利用可預測的圖片網址序列去下載漫畫作品,不 fetch 作品與章節頁面的範例。
|
||||
*
|
||||
* @since 2019/6/17 21:5:52 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.en-US/mrblue.js
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.en-US/bookcube.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.sequential',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
one_by_one : true,
|
||||
// 這類型網站必須靠偵測到錯誤時,轉到下一個章節來運作;因此當圖片下載錯誤時不能直接中斷跳出。
|
||||
skip_error : true,
|
||||
// 但是不保留損壞的檔案。
|
||||
preserve_bad_image : false,
|
||||
MAX_ERROR_RETRY : 2,
|
||||
|
||||
// base_URL : '',
|
||||
|
||||
// 規範 work id 的正規模式;提取出引數中的作品id 以回傳。
|
||||
extract_work_id : function(work_information) {
|
||||
// e.g., "wt_HQ0005"
|
||||
if (/^[a-z_\-\d]+$/i.test(work_information))
|
||||
return work_information;
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
// 必須是圖片網址的起始部分。
|
||||
return '' + work_id + '/';
|
||||
},
|
||||
skip_get_work_page : true,
|
||||
// 解析出作品資料/作品詳情。
|
||||
parse_work_data : function(html, get_label) {
|
||||
// 先給一個空的初始化作品資料以便後續作業。
|
||||
return Object.create(null);
|
||||
},
|
||||
// 解析出章節列表。
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
if (!Object.hasOwn(this, 'start_chapter_NO')
|
||||
&& work_data.last_download.chapter > this.start_chapter_NO) {
|
||||
// 未設定 .start_chapter_NO 且之前下載過,則接續上一次的下載。
|
||||
this.start_chapter_NO = work_data.last_download.chapter;
|
||||
}
|
||||
|
||||
if (!Array.isArray(work_data.chapter_list)) {
|
||||
// 先給一個空的章節列表以便後續作業。
|
||||
work_data.chapter_list = [];
|
||||
}
|
||||
|
||||
// reuse work_data.chapter_list
|
||||
while (work_data.chapter_list.length < this.start_chapter_NO) {
|
||||
// 隨便墊入作品資料網址 給本次下載開始下載章節前所有未設定的章節資料,
|
||||
// 這樣才能準確從 .start_chapter_NO 開始下載。後續章節網址會動態增加。
|
||||
work_data.chapter_list.push(this.work_URL(work_data.id));
|
||||
}
|
||||
// console.log(work_data);
|
||||
},
|
||||
|
||||
// 依照給定序列取得圖片網址。
|
||||
get_image_url : function(work_data, chapter_NO, image_index) {
|
||||
return this.work_URL(work_data.id) + chapter_NO + '/'
|
||||
+ (image_index + 1) + '.jpg';
|
||||
},
|
||||
|
||||
skip_get_chapter_page : true,
|
||||
// 解析出章節資料。
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
// 設定必要的屬性。
|
||||
var chapter_data = {
|
||||
// 先給好本章節第一張圖片的網址。後續圖片網址會動態增加。
|
||||
image_list : [ this.get_image_url(work_data, chapter_NO, 0) ]
|
||||
};
|
||||
|
||||
// console.log(chapter_data);
|
||||
return chapter_data;
|
||||
},
|
||||
|
||||
// 設定動態改變章節中的圖片數量。
|
||||
dynamical_count_images : true,
|
||||
|
||||
// 每個圖片下載結束都會執行一次。
|
||||
after_get_image : function(image_list, work_data, chapter_NO) {
|
||||
// console.log(image_list);
|
||||
var latest_image_data = image_list[image_list.index];
|
||||
// console.log(latest_image_data);
|
||||
if (!latest_image_data.has_error) {
|
||||
library_namespace.debug([ work_data.id + ': ', {
|
||||
// gettext_config:{"id":"the-previous-image-in-this-chapter-was-successfully-downloaded.-download-the-next-image-in-this-chapter"}
|
||||
T : '本章節上一張圖片下載成功。下載本章節下一幅圖片。'
|
||||
} ], 3);
|
||||
image_list.push(this.get_image_url(work_data, chapter_NO,
|
||||
image_list.length));
|
||||
return;
|
||||
}
|
||||
|
||||
if (image_list.length === 1) {
|
||||
library_namespace.debug([ work_data.id + ': ', {
|
||||
// gettext_config:{"id":"the-first-image-failed-to-download.-ending-download-for-this-work"}
|
||||
T : '第一張圖就下載失敗了。結束下載本作品。'
|
||||
} ], 3);
|
||||
return;
|
||||
}
|
||||
|
||||
// CeL.debug(work_data.id + ': 本章節上一張圖片下載失敗。下載下一個章節的圖片。');
|
||||
work_data.chapter_list.push(this.work_URL(work_data.id));
|
||||
// 動態增加章節,必須手動增加章節數量。
|
||||
work_data.chapter_count++;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_sequential_comics_crawler(configuration, callback, initializer) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
return new library_namespace.work_crawler(configuration);
|
||||
}
|
||||
|
||||
return new_sequential_comics_crawler;
|
||||
}
|
242
app/node_modules/cejs/application/net/work_crawler/sites/toomics.js
generated
vendored
242
app/node_modules/cejs/application/net/work_crawler/sites/toomics.js
generated
vendored
@@ -1,242 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading Toomics comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載 Toomics 韓國漫畫 之 **非韓語版**(外語版本) 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.toomics(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @since 2019/7/12 20:21:25 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/toomics_sc.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.toomics',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// 所有的子檔案要修訂註解說明時,應該都要順便更改在CeL.application.net.comic中Comic_site.prototype內的母comments,並以其為主體。
|
||||
|
||||
// {Natural}MIN_LENGTH:最小容許圖案檔案大小 (bytes)。
|
||||
// 對於有些圖片只有一條細橫桿的情況。
|
||||
MIN_LENGTH : 200,
|
||||
|
||||
// 圖像檔案下載失敗處理方式:忽略/跳過圖像錯誤。當404圖像不存在、檔案過小,或是被偵測出非圖像(如不具有EOI)時,依舊強制儲存檔案。default:false
|
||||
// skip_error : true,
|
||||
|
||||
// one_by_one : true,
|
||||
base_URL : 'https://toomics.com/',
|
||||
|
||||
// LANG_PREFIX : '',
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : function(work_title) {
|
||||
return [ this.LANG_PREFIX + '/webtoon/ajax_search', {
|
||||
toonData : work_title,
|
||||
offset : 0,
|
||||
limit : 20
|
||||
} ];
|
||||
},
|
||||
parse_search_result : function(html, get_label) {
|
||||
html = html.between('<ul>', '</ul>');
|
||||
// console.log(html);
|
||||
|
||||
function parser(token) {
|
||||
// console.log(token);
|
||||
return [ +token.match(/toon=(\d+)&/)[1],
|
||||
/**
|
||||
* <code>
|
||||
|
||||
<a href="/sc/webtoon/search/?toon=4866&return=%2Fsc%2Fwebtoon%2Fepisode%2Ftoon%2F4866">
|
||||
<div class="search_box">
|
||||
<p class="img"><img src="https://thumb-g.toomics.com/upload/thumbnail/20180629102024/2019_03_15_15526329389053.jpg" alt="郑主任为何这样"></p>
|
||||
|
||||
</code>
|
||||
*/
|
||||
token.between('alt="', '"') ];
|
||||
}
|
||||
|
||||
return library_namespace.work_crawler
|
||||
.extract_work_id_from_search_result_link(
|
||||
/<li(?:[^<>]*)>([\s\S]+?)<\/li>/g, html, parser);
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : '/webtoon/episode/toon/',
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
// console.log(html);
|
||||
var text = html.between('<header class="ep-cover_ch"', '</header>');
|
||||
var work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(text.between('<h1>', '</h1>')),
|
||||
author : get_label(text.between(
|
||||
/**
|
||||
* <code>
|
||||
|
||||
<span class="writer">李玄敏 <span class="text-muted">|</span> Miyune</span>
|
||||
</p>
|
||||
|
||||
</code>
|
||||
*/
|
||||
'<span class="writer">', '</p>')).replace(/ +\|/g, ','),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
description : get_label(text.between('<h2>', '</h2>')),
|
||||
genre : get_label(
|
||||
text.between('<span class="type">', '</span>'))
|
||||
.replace(/\s{2,}/g, ' '),
|
||||
// e.g., Updated every Friday
|
||||
update_weekday : text.between('<span class="date">', '</span>')
|
||||
};
|
||||
|
||||
extract_work_data(work_data, html);
|
||||
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
get_chapter_list : function(work_data, html, get_label) {
|
||||
html = html.between('<ol class="list-ep">', '</ol>');
|
||||
|
||||
var matched, PATTERN_chapter =
|
||||
//
|
||||
/<li><a href="([^<>"]+)"[^<>]*>([\s\S]+?)<\/li>/g;
|
||||
|
||||
work_data.chapter_list = [];
|
||||
/**
|
||||
* <code>
|
||||
|
||||
<a href="javascript:;" onclick="Webtoon.chkec(this);location.href='/en/webtoon/detail/code/97236/ep/0/toon/4630'" onkeypress="this.onclick"
|
||||
data-e="NDYzMA==" data-c="OTcyMzY="
|
||||
data-v="">
|
||||
|
||||
</code>
|
||||
*/
|
||||
html.each_between('<li', '</li>', function(token) {
|
||||
var url = token.between("location.href='", "'");
|
||||
if (!url) {
|
||||
// limited
|
||||
return;
|
||||
}
|
||||
work_data.chapter_list.push({
|
||||
title : get_label(token.between('<div class="cell-num">',
|
||||
'</div>')),
|
||||
date : token.between('datetime="', '"'),
|
||||
type : token
|
||||
.between('<span class="coin-type1">', '</span>'),
|
||||
thumb : token.between('data-original="', '"'),
|
||||
rating : token.between('<span class="star-stat">',
|
||||
'</span>'),
|
||||
url : url
|
||||
});
|
||||
});
|
||||
// console.log(work_data.chapter_list);
|
||||
},
|
||||
|
||||
pre_parse_chapter_data
|
||||
// 執行在解析章節資料 process_chapter_data() 之前的作業 (async)。
|
||||
// 必須自行保證執行 callback(),不丟出異常、中斷。
|
||||
: function(XMLHttp, work_data, callback, chapter_NO) {
|
||||
if (this.verificated) {
|
||||
callback()
|
||||
return;
|
||||
}
|
||||
|
||||
this.get_URL(this.LANG_PREFIX
|
||||
// https://toomics.com/tc/age_verification
|
||||
// 年齡認證 如需關閉安全模式,請確認您已年滿18歲。
|
||||
+ '/age_verification', function(XMLHttp) {
|
||||
// console.log(XMLHttp);
|
||||
this.get_URL(this.LANG_PREFIX
|
||||
// https://toomics.com/sc/index/set_display/?display=A&return=/sc/webtoon/detail/code/55556/ep/1/toon/2509
|
||||
+ '/index/set_display/?display=A&return=/' + this.LANG_PREFIX
|
||||
+ '/help/notice_list', function(XMLHttp) {
|
||||
// console.log(XMLHttp);
|
||||
// console.log(this.get_URL_options);
|
||||
this.verificated = true;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
var chapter_data = work_data.chapter_list[chapter_NO - 1];
|
||||
// console.log(chapter_data);
|
||||
// console.log(html);
|
||||
|
||||
Object.assign(chapter_data, {
|
||||
title : html.between('<div class="viewer-title">', '</div>')
|
||||
/**
|
||||
* <code>
|
||||
|
||||
<div class="viewer-title">
|
||||
<a href="/en/webtoon/episode/toon/4630" title="List">Too Pretty<em>Episode 1</em></a>
|
||||
</div>
|
||||
|
||||
</code>
|
||||
*/
|
||||
.between('<em>', '</em>'),
|
||||
image_list : html
|
||||
// 2019: '<main class="viewer-body">'
|
||||
// 2020/8: '<main class="viewer-body viewer-body-scroll">'
|
||||
.between('<main class="viewer-body', '</main>')
|
||||
// 2019: .all_between('data-original="', '"')
|
||||
// 2020/8: .all_between('data-src="', '"')
|
||||
.all_between('data-src="', '"')
|
||||
});
|
||||
|
||||
if (chapter_data.image_list.length === 0) {
|
||||
// 改版?
|
||||
// console.trace(html);
|
||||
}
|
||||
|
||||
// console.log(chapter_data);
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_toomics_comics_crawler(configuration) {
|
||||
configuration = Object.assign(Object.create(null),
|
||||
//
|
||||
default_configuration, {
|
||||
work_URL : configuration.LANG_PREFIX
|
||||
+ default_configuration.work_URL
|
||||
}, configuration);
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
return crawler;
|
||||
}
|
||||
|
||||
return new_toomics_comics_crawler;
|
||||
}
|
217
app/node_modules/cejs/application/net/work_crawler/sites/webtoon.js
generated
vendored
217
app/node_modules/cejs/application/net/work_crawler/sites/webtoon.js
generated
vendored
@@ -1,217 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading webtoon comics.
|
||||
*
|
||||
* @fileoverview 本檔案包含了解析並處理、批量下載 WEBTOON 韓國漫畫 的工具。
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.webtoon(configuration).start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @see https://www.webtoons.com/ https://github.com/Fastcampus-WPS-9th/Webtoon
|
||||
*
|
||||
* @since 2018/7/27 18:16:19 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see
|
||||
// https://github.com/kanasimi/work_crawler/blob/master/comic.cmn-Hans-CN/dongman.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.webtoon',
|
||||
|
||||
require : 'application.net.work_crawler.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 2021/12/4 Error: unable to verify the first certificate
|
||||
//
|
||||
// https://stackoverflow.com/questions/20082893/unable-to-verify-leaf-signature
|
||||
// for Error: unable to verify the first certificate
|
||||
// code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
|
||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
|
||||
|
||||
var default_configuration = {
|
||||
|
||||
// one_by_one : true,
|
||||
base_URL : 'https://www.webtoons.com/',
|
||||
|
||||
// 最小容許圖案檔案大小 (bytes)。
|
||||
// 對於有些圖片只有一條細橫桿的情況。
|
||||
// e.g., webtoon\167 吸血鬼也沒關係\0003 [第2話] 健忘症\167-3-036.png
|
||||
MIN_LENGTH : 130,
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : function(work_title) {
|
||||
// 預設方法(callback var API)
|
||||
return 'https://ac.webtoons.com/ac?q='
|
||||
+ (this.language_code ? this.language_code + '%11' : '')
|
||||
+ encodeURIComponent(work_title)
|
||||
+ '&q_enc=UTF-8&st=1&r_lt=0&r_format=json&r_enc=UTF-8&_callback=jQuery'
|
||||
+ String(Math.floor(Math.random() * 1e10))
|
||||
+ String(Math.floor(Math.random() * 1e10)) + '_'
|
||||
+ Date.now() + '&_=' + Date.now();
|
||||
},
|
||||
parse_search_result : function(html) {
|
||||
// console.log(html);
|
||||
if (html.startsWith('{')) {
|
||||
html = JSON.parse(html);
|
||||
} else {
|
||||
// for callback
|
||||
html = eval('(' + html.between('(', {
|
||||
tail : ')'
|
||||
}) + ')');
|
||||
}
|
||||
|
||||
var id_list = [], id_data = [];
|
||||
html = html.items[0];
|
||||
if (html) {
|
||||
// assert: Array.isArray(html)
|
||||
html.forEach(function(work_data) {
|
||||
// console.log(work_data);
|
||||
if (work_data[1][0] === 'TITLE') {
|
||||
id_list.push(+work_data[3][0]);
|
||||
id_data.push(work_data[0][0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
var matched = typeof work_id === 'string'
|
||||
&& work_id.match(/^([a-z]+)_(\d+)$/);
|
||||
if (matched) {
|
||||
// e.g., 投稿新星專區作品
|
||||
// https://www.webtoons.com/challenge/episodeList?titleNo=
|
||||
return matched[1] + '/episodeList?titleNo=' + matched[2];
|
||||
}
|
||||
return 'episodeList?titleNo=' + work_id;
|
||||
},
|
||||
parse_work_data : function(html, get_label, extract_work_data) {
|
||||
var matched = html
|
||||
/**
|
||||
* 咚漫 2018/10/27? 改版 <code>
|
||||
<a data-buried-obj="1" data-sc-name="PC_detail-page_read-first-btn" href="//www.dongmanmanhua.cn/fantasy/zhexianlu/%E7%AC%AC%E9%9B%B6%E8%AF%9D-1/viewer?title_no=1307&episode_no=1" class="btn_type7 NPI=a:gofirst,g:zh_CN_zh-hans" id="_btnEpisode">阅读第一话<span class="ico_arr21"></span></a>
|
||||
</code>
|
||||
*/
|
||||
.match(/<a [^<>]*?href="([^<>"]+)"[^<>]+id="_btnEpisode">/),
|
||||
//
|
||||
text = html.between('<div class="info">', '</div>'),
|
||||
//
|
||||
work_data = {
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(text.between('<h1 class="subj">', '</h1>')
|
||||
// https://www.webtoons.com/zh-hant/challenge/%E5%A6%82%E4%BD%A0%E6%89%80%E9%A1%98/list?title_no=166730
|
||||
|| text.between('<h3 class="subj _challengeTitle">', '<')),
|
||||
author : get_label(html.between(
|
||||
// <meta property="com-linewebtoon:webtoon:author"
|
||||
// content="A / B" />
|
||||
':webtoon:author" content="', '"')),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
// 看第一集, 阅读第一话
|
||||
chapter_1_url : matched[1],
|
||||
status : [
|
||||
get_label(text.between('<h2 class="genre ', '</h2>')
|
||||
.between('>')),
|
||||
// 更新頻率 update_frequency
|
||||
get_label(html.between('<p class="day_info">', '</p>')) ],
|
||||
description : get_label(html.between(
|
||||
// ('<p class="summary">', '</p>')
|
||||
'<meta name="twitter:description" content="', '"')),
|
||||
last_update : get_label(html.between('<span class="date">',
|
||||
'</span>'))
|
||||
};
|
||||
extract_work_data(work_data, html);
|
||||
// console.log(work_data);
|
||||
return work_data;
|
||||
},
|
||||
chapter_list_URL : function(work_id, work_data) {
|
||||
// return url of the first chapter
|
||||
return work_data.chapter_1_url;
|
||||
},
|
||||
get_chapter_list : function(work_data, html) {
|
||||
// console.log(html);
|
||||
var data = html.between('<div class="episode_lst">', '</ul>'), matched,
|
||||
/**
|
||||
* 咚漫 2018/10/27? 改版 <code>
|
||||
<a data-sc-event-parameter="{ title_title:'谪仙录',titleNo:'1307',genre:FANTASY,subcategory_"0":DRAMAsubcategory_"1":FANTASY,picAuthor:泼克文化,wriAuthor:泼克文化,update_day:,serial_status:SERIES,reader_gender:男,episode_name:第零话 1,episodeNo:1,change_mode:'',is_read_complete:'',change_episode_direction:''}" data-sc-event-name="TitleReadChangeEpisode" data-buried-obj="1" data-sc-name="PC_read-page_image-episode-btn" href="//www.dongmanmanhua.cn/fantasy/zhexianlu/%E7%AC%AC%E9%9B%B6%E8%AF%9D-1/viewer?title_no=1307&episode_no=1" class="on N=a:vtw.llist,g:zh_CN_zh-hans">
|
||||
|
||||
// 穿越時空愚到你 https://www.webtoons.com/zh-hant/drama/2019foolsday/list?title_no=1562
|
||||
<span class="subj">水下那一分鐘 ft. 奇奇怪怪 <夢境與真實></span>
|
||||
</code>
|
||||
*/
|
||||
PATTERN_chapter = /<li[^<>]*>[\s\S]*?<a [^<>]*?href="([^"<>]+)"[^<>]*>[\s\S]*?<span class="subj">([\s\S]*?)<\/span>[\s\S]*?<\/li>/g;
|
||||
|
||||
work_data.chapter_list = [];
|
||||
while (matched = PATTERN_chapter.exec(data)) {
|
||||
var chapter_data = {
|
||||
url : matched[1],
|
||||
title : matched[2]
|
||||
};
|
||||
work_data.chapter_list.push(chapter_data);
|
||||
}
|
||||
// console.log(work_data.chapter_list);
|
||||
},
|
||||
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
var chapter_data = {
|
||||
// 設定必要的屬性。
|
||||
title : get_label(html.between(
|
||||
'<h1 class="subj_episode" title="', '"')),
|
||||
image_list : []
|
||||
}, PATTERN_image = /<img [^<>]+?data-url="([^<>"]+)"/g, matched;
|
||||
|
||||
html = html.between('<div class="viewer_lst">',
|
||||
'<div class="episode_area"');
|
||||
|
||||
while (matched = PATTERN_image.exec(html)) {
|
||||
matched = new library_namespace.URI(matched[1]);
|
||||
// 去掉?type=q70s的部分 畫質較好 q70是手機版 q90是電腦版
|
||||
delete matched.search_params.type;
|
||||
// 去除?x-oss-process=image/quality,q_90 可會有更高的畫質
|
||||
delete matched.search_params['x-oss-process'];
|
||||
chapter_data.image_list.push({
|
||||
url : encodeURI(matched.toString())
|
||||
});
|
||||
}
|
||||
|
||||
return chapter_data;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_webtoon_comics_crawler(configuration) {
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
return crawler;
|
||||
}
|
||||
|
||||
return new_webtoon_comics_crawler;
|
||||
}
|
304
app/node_modules/cejs/application/net/work_crawler/sites/yomou.js
generated
vendored
304
app/node_modules/cejs/application/net/work_crawler/sites/yomou.js
generated
vendored
@@ -1,304 +0,0 @@
|
||||
/**
|
||||
* @name CeL module for downloading syosetu.com novels.
|
||||
*
|
||||
* @fileoverview 本檔案包含了批量下載小説家になろう/小説を読もう!的工具。
|
||||
*
|
||||
* TODO: なろう小説API https://dev.syosetu.com/man/api/
|
||||
*
|
||||
* <code>
|
||||
|
||||
CeL.yomou().start(work_id);
|
||||
|
||||
</code>
|
||||
*
|
||||
* @see 小説投稿サイト https://matome.naver.jp/odai/2139450042041120001
|
||||
* http://www.akatsuki-novels.com/novels/ranking_total
|
||||
* http://www.mai-net.net/bbs/sst/sst.php?act=list&cate=all&page=1
|
||||
* https://github.com/whiteleaf7/narou https://github.com/59naga/naroujs
|
||||
* https://github.com/59naga/scrape-narou
|
||||
*
|
||||
* @since 2017/2/22 0:18:34 模組化。
|
||||
*/
|
||||
|
||||
// More examples:
|
||||
// @see https://github.com/kanasimi/work_crawler/blob/master/yomou.js
|
||||
// @see https://github.com/kanasimi/work_crawler/blob/master/noc.js
|
||||
'use strict';
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
|
||||
typeof CeL === 'function' && CeL.run({
|
||||
// module name
|
||||
name : 'application.net.work_crawler.sites.yomou',
|
||||
|
||||
require : 'application.net.work_crawler.'
|
||||
//
|
||||
+ '|application.storage.EPUB.',
|
||||
|
||||
// 設定不匯出的子函式。
|
||||
no_extend : '*',
|
||||
|
||||
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
|
||||
code : module_code
|
||||
});
|
||||
|
||||
function module_code(library_namespace) {
|
||||
|
||||
// requiring
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
var default_configuration = {
|
||||
// auto_create_ebook, automatic create ebook
|
||||
// MUST includes CeL.application.locale!
|
||||
need_create_ebook : true,
|
||||
// recheck:從頭檢測所有作品之所有章節與所有圖片。不會重新擷取圖片。對漫畫應該僅在偶爾需要從頭檢查時開啟此選項。default:false
|
||||
// recheck='changed': 若是已變更,例如有新的章節,則重新下載/檢查所有章節內容。否則只會自上次下載過的章節接續下載。
|
||||
recheck : 'changed',
|
||||
|
||||
site_name : '小説を読もう!',
|
||||
base_URL : 'https://yomou.syosetu.com/',
|
||||
novel_base_URL : 'https://ncode.syosetu.com/',
|
||||
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
search_URL : 'search.php?order=hyoka&word=',
|
||||
parse_search_result : function(html, get_label) {
|
||||
// console.log(html);
|
||||
var id_data = [],
|
||||
// {Array}id_list = [id,id,...]
|
||||
id_list = [],
|
||||
// 2019/6/24 目前僅有 ミッドナイトノベルズ 採用這個 header
|
||||
header = '<article class="search_novel">';
|
||||
if (!html.includes(header))
|
||||
header = '<div class="novel_h">';
|
||||
|
||||
html.each_between(header, '</a>', function(text) {
|
||||
id_list.push(text
|
||||
.between(' href="' + this.novel_base_URL, '/"'));
|
||||
id_data.push(get_label(text.between('/">')));
|
||||
}, this);
|
||||
|
||||
return [ id_list, id_data ];
|
||||
},
|
||||
|
||||
// 取得作品的章節資料。 get_work_data()
|
||||
work_URL : function(work_id) {
|
||||
return this.novel_base_URL + 'novelview/infotop/ncode/' + work_id
|
||||
+ '/';
|
||||
},
|
||||
parse_work_data : function(html, get_label) {
|
||||
var work_data = Object.create(null);
|
||||
html.between('<table', '<div id="ad_s_box">')
|
||||
//
|
||||
.each_between('<tr>', '</tr>', function(text) {
|
||||
work_data[get_label(text.between('<th', '</th>').between('>'))]
|
||||
//
|
||||
= get_label(text.between('<td', '</td>').between('>'));
|
||||
});
|
||||
// console.log(work_data);
|
||||
|
||||
work_data = Object.assign({
|
||||
// 必要屬性:須配合網站平台更改。
|
||||
title : get_label(html.between('dc:title="', '"')),
|
||||
|
||||
// 選擇性屬性:須配合網站平台更改。
|
||||
// e.g., 连载中, 連載中
|
||||
// <span id="noveltype">完結済</span>全1部
|
||||
// <span id="noveltype_notend">連載中</span>全1部
|
||||
status : [ html.between('<span id="noveltype', '<')
|
||||
.between('>') ],
|
||||
author : work_data.作者名,
|
||||
last_update : work_data.最終話掲載日 || work_data.掲載日,
|
||||
description : work_data.あらすじ
|
||||
}, work_data);
|
||||
|
||||
// 此兩者為未分割的字串。
|
||||
if (work_data.ジャンル)
|
||||
work_data.genre = work_data.ジャンル.split(/\s+/);
|
||||
if (work_data.キーワード) {
|
||||
// No キーワード:
|
||||
// https://novel18.syosetu.com/novelview/infotop/ncode/n3731fh/
|
||||
work_data.tags = work_data.キーワード.split(/\s+/);
|
||||
}
|
||||
|
||||
return work_data;
|
||||
},
|
||||
// 對於章節列表與作品資訊分列不同頁面(URL)的情況,應該另外指定.chapter_list_URL。
|
||||
chapter_list_URL : function(work_id) {
|
||||
return this.novel_base_URL + work_id + '/';
|
||||
},
|
||||
get_chapter_list : function(work_data, html) {
|
||||
// TODO: 對於單話,可能無目次。
|
||||
work_data.chapter_list = [];
|
||||
html.between('<div class="index_box">', '<div id="novel_footer">')
|
||||
//
|
||||
.each_between('<dl class="novel_sublist2">', '</dl>',
|
||||
//
|
||||
function(text) {
|
||||
var matched = text.match(
|
||||
// [ , href, inner ]
|
||||
/ href="\/[^\/]+\/([^ "<>]+)[^<>]*>(.+?)<\/a>/);
|
||||
if (!matched) {
|
||||
throw text;
|
||||
}
|
||||
|
||||
var chapter_data = {
|
||||
url : matched[1].replace(/^\.\//, ''),
|
||||
// 掲載日
|
||||
date : [ text.match(/>\s*(2\d{3}[年\/][^"<>]+?)</)[1]
|
||||
//
|
||||
.to_Date({
|
||||
zone : work_data.time_zone
|
||||
}) ],
|
||||
title : matched[2]
|
||||
};
|
||||
if (matched = text.match(/ title="(2\d{3}[年\/][^"<>]+?)改稿"/)) {
|
||||
chapter_data.date.push(matched[1].to_Date({
|
||||
zone : work_data.time_zone
|
||||
}) || matched[1]);
|
||||
}
|
||||
work_data.chapter_list.push(chapter_data);
|
||||
// console.log(chapter_data);
|
||||
});
|
||||
|
||||
if (work_data.chapter_list.length === 0
|
||||
&& html.includes('<div id="novel_honbun"')) {
|
||||
// 短編小説
|
||||
work_data.chapter_list.push({
|
||||
url : this.chapter_list_URL(work_data.id),
|
||||
// 掲載日
|
||||
date : [ work_data.last_update.to_Date({
|
||||
zone : work_data.time_zone
|
||||
}) ],
|
||||
title : work_data.title
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 取得每一個章節的各個影像內容資料。 get_chapter_data()
|
||||
chapter_URL : function(work_data, chapter_NO) {
|
||||
var url = work_data.chapter_list[chapter_NO - 1].url;
|
||||
if (url.includes('://')) {
|
||||
// e.g., 短編小説
|
||||
return url;
|
||||
}
|
||||
return this.chapter_list_URL(work_data.id) + url;
|
||||
},
|
||||
// 檢測所取得內容的章節編號是否相符。
|
||||
check_chapter_NO : [ '<div id="novel_no">', '/' ],
|
||||
parse_chapter_data : function(html, work_data, get_label, chapter_NO) {
|
||||
var
|
||||
/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
|
||||
NOT_FOUND = ''.indexOf('_'),
|
||||
//
|
||||
text = html.between('<div id="novel_color">',
|
||||
'</div><!--novel_color-->'),
|
||||
//
|
||||
index = text.indexOf('<div id="novel_p"');
|
||||
if (index === NOT_FOUND
|
||||
//
|
||||
&& (index = text.indexOf('<div id="novel_honbun"')) === NOT_FOUND) {
|
||||
// gettext_config:{"id":"unable-to-parse-chapter-data-and-get-chapter-content-text"}
|
||||
throw new Error('無法解析章節資料並取得章節內容文字!');
|
||||
}
|
||||
text = text.slice(index);
|
||||
text = text.between(null, {
|
||||
// 後半段的"次の話"
|
||||
tail : '<div class="novel_bn">'
|
||||
}) || text;
|
||||
|
||||
var links = [], ebook = work_data[this.KEY_EBOOK];
|
||||
|
||||
text.each_between('<a ', '</a>', function(text) {
|
||||
var matched = text.match(/(?:^| )href="([^"<>]+)"/);
|
||||
// @see https://ncode.syosetu.com/n8611bv/49/
|
||||
// e.g., <a href="https://11578.mitemin.net/i00000/"
|
||||
if (matched && matched[1].includes('.mitemin.net')) {
|
||||
// 下載mitemin.net的圖片
|
||||
links.push(matched[1]);
|
||||
}
|
||||
});
|
||||
|
||||
links.forEach(function(url) {
|
||||
// 登記有url正處理中,須等待。
|
||||
ebook.downloading[url] = url;
|
||||
library_namespace.get_URL(url, function(XMLHttp) {
|
||||
delete ebook.downloading[url];
|
||||
if (!XMLHttp || !XMLHttp.responseText) {
|
||||
return;
|
||||
}
|
||||
var matched = XMLHttp.responseText
|
||||
.match(/<a href="([^"<>]+)" target="_blank">/);
|
||||
if (matched) {
|
||||
// 因為.add()會自動添加.downloading並在事後檢查.on_all_downloaded,因此這邊不用再檢查。
|
||||
ebook.add({
|
||||
url : matched[1]
|
||||
});
|
||||
} else {
|
||||
library_namespace.error({
|
||||
// gettext_config:{"id":"unable-to-extract-image-url-from-link-in-chapter-content-$1"}
|
||||
T : [ '無法從章節內容中之連結抽取出圖片網址:%1', url ]
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var series_title = get_label(
|
||||
//
|
||||
html.between('<p class="series_title">', '</p>'));
|
||||
if (series_title) {
|
||||
ebook.set([ {
|
||||
meta : null,
|
||||
name : "calibre:series",
|
||||
content : series_title = get_label(series_title)
|
||||
} ]);
|
||||
}
|
||||
this.add_ebook_chapter(work_data, chapter_NO, {
|
||||
title : html.between('<p class="chapter_title">', '</p>')
|
||||
// 短編小説
|
||||
|| series_title,
|
||||
sub_title : html.between('<p class="novel_subtitle">', '</p>')
|
||||
// 短編小説
|
||||
|| html.between('<p class="novel_title">', '</p>'),
|
||||
text : text
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
function new_syosetu_crawler(configuration) {
|
||||
if (configuration && configuration.isR18) {
|
||||
// 放在這邊,避免覆蓋原設定。
|
||||
if (!configuration.novel_base_URL)
|
||||
configuration.novel_base_URL = 'https://novel18.syosetu.com/';
|
||||
configuration.search_URL = configuration.search_URL
|
||||
// 解析 作品名稱 → 作品id get_work()
|
||||
// search/search/search.php hyoka: 総合ポイントの高い順 総合評価の高い順
|
||||
|| 'search/search/?order=hyoka&word=';
|
||||
}
|
||||
|
||||
configuration = configuration ? Object.assign(Object.create(null),
|
||||
default_configuration, configuration) : default_configuration;
|
||||
|
||||
// 每次呼叫皆創建一個新的實體。
|
||||
var crawler = new library_namespace.work_crawler(configuration);
|
||||
|
||||
if (crawler.isR18) {
|
||||
// for なろうの関連サイト/R-18サイト 年齢確認
|
||||
// https://static.syosetu.com/sub/nl/view/js/event/redirect_ageauth.js
|
||||
crawler.setup_value('cookie', 'over18=yes');
|
||||
}
|
||||
|
||||
if (false) {
|
||||
crawler.data_of(work_id, function(work_data) {
|
||||
console.log(work_data);
|
||||
});
|
||||
}
|
||||
|
||||
return crawler;
|
||||
}
|
||||
|
||||
return new_syosetu_crawler;
|
||||
}
|
1235
app/node_modules/cejs/application/net/work_crawler/task.js
generated
vendored
1235
app/node_modules/cejs/application/net/work_crawler/task.js
generated
vendored
File diff suppressed because it is too large
Load Diff
1889
app/node_modules/cejs/application/net/work_crawler/work.js
generated
vendored
1889
app/node_modules/cejs/application/net/work_crawler/work.js
generated
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user