UNPKG

cejs

Version:

A JavaScript module framework that is simple to use.

745 lines (667 loc) 27.6 kB
/** * @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; }