// ==UserScript== // @name bangumi new wiki helper // @name:zh-CN bangumi 创建条目助手 // @namespace https://github.com/22earth // @description assist to create new subject // @description:zh-cn 辅助创建 bangumi.tv 上的条目 // @include http://www.getchu.com/soft.phtml?id=* // @include /^https?:\/\/www\.amazon\.co\.jp\/.*$/ // @include /^https?:\/\/(bangumi|bgm|chii)\.(tv|in)\/.*$/ // @match *://*/* // @author 22earth // @homepage https://github.com/22earth/bangumi-new-wiki-helper // @version 0.4.3 // @note 0.3.0 使用 typescript 重构,浏览器扩展和脚本使用公共代码 // @run-at document-end // @grant GM_addStyle // @grant GM_openInTab // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @require https://cdn.staticfile.org/fuse.js/6.4.0/fuse.min.js // @downloadURL none // ==/UserScript== /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } /** * 为页面添加样式 * @param style */ /** * 获取节点文本 * @param elem */ function getText(elem) { if (!elem) return ''; if (elem.tagName.toLowerCase() === 'meta') { return elem.content; } if (elem.tagName.toLowerCase() === 'input') { return elem.value; } return elem.textContent || elem.innerText || ''; } function getInnerText(elem) { if (!elem) return ''; return elem.innerText || elem.textContent || ''; } /** * dollar 选择单个 * @param {string} selector */ function $q(selector) { if (window._parsedEl) { return window._parsedEl.querySelector(selector); } return document.querySelector(selector); } /** * dollar 选择所有元素 * @param {string} selector */ function $qa(selector) { if (window._parsedEl) { return window._parsedEl.querySelectorAll(selector); } return document.querySelectorAll(selector); } /** * 查找包含文本的标签 * @param {string} selector * @param {string} text */ function contains(selector, text, $parent) { let elements; if ($parent) { elements = $parent.querySelectorAll(selector); } else { elements = $qa(selector); } let t; if (typeof text === 'string') { t = text; } else { t = text.join('|'); } return [].filter.call(elements, function (element) { return new RegExp(t, 'i').test(getText(element)); }); } function findElementByKeyWord(selector, $parent) { let res = null; $parent = $parent ? $parent : $q(selector.selector); if (!$parent) return res; const targets = contains(selector.subSelector, selector.keyWord, $parent); if (targets && targets.length) { let $t = targets[targets.length - 1]; // 相邻节点 if (selector.sibling) { $t = targets[targets.length - 1].nextElementSibling; } return $t; } return res; } function findElement(selector, $parent) { var _a; let r = null; if (selector) { if (selector instanceof Array) { let i = 0; let targetSelector = selector[i]; while (targetSelector && !(r = findElement(targetSelector, $parent))) { targetSelector = selector[++i]; } } else { if (!selector.subSelector) { r = $parent ? $parent.querySelector(selector.selector) : $q(selector.selector); } else if (selector.isIframe) { // iframe 暂时不支持 parent const $iframeDoc = (_a = $q(selector.selector)) === null || _a === void 0 ? void 0 : _a.contentDocument; r = $iframeDoc === null || $iframeDoc === void 0 ? void 0 : $iframeDoc.querySelector(selector.subSelector); } else { r = findElementByKeyWord(selector, $parent); } if (selector.closest) { r = r.closest(selector.closest); } // recursive if (r && selector.nextSelector) { const nextSelector = selector.nextSelector; r = findElement(nextSelector, r); } } } return r; } function findAllElement(selector, $parent) { var _a, _b; let res = []; if (selector instanceof Array) { let i = 0; let targetSelector = selector[i]; while (targetSelector) { const arr = findAllElement(targetSelector, $parent); if (arr.length) { res.push(...arr); break; } targetSelector = selector[++i]; } } else { // 没有下一步的选择器 if (!selector.nextSelector) { // 没子选择器 if (!selector.subSelector) { res = Array.from($parent ? $parent.querySelectorAll(selector.selector) : $qa(selector.selector)); } else if (selector.isIframe) { const $iframeDoc = (_a = $q(selector.selector)) === null || _a === void 0 ? void 0 : _a.contentDocument; res = Array.from($iframeDoc === null || $iframeDoc === void 0 ? void 0 : $iframeDoc.querySelectorAll(selector.subSelector)); } else { if (selector.isIframe) { const $iframeDoc = (_b = $q(selector.selector)) === null || _b === void 0 ? void 0 : _b.contentDocument; // iframe 时不需要 keyWord $parent = $iframeDoc === null || $iframeDoc === void 0 ? void 0 : $iframeDoc.querySelector(selector.subSelector); } else { $parent = $parent ? $parent : $q(selector.selector); } if (!$parent) return res; res = contains(selector.subSelector, selector.keyWord, $parent); if (selector.sibling) { res = res.map(($t) => $t.nextElementSibling); } } // closest if (selector.closest) { res = res.map((r) => r.closest(selector.closest)); } } else { // 有下一步的选择器时,selector 是用来定位父节点的 const localSel = Object.assign({}, selector); delete localSel.nextSelector; const $p = findElement(localSel); if ($p) { res = findAllElement(selector.nextSelector, $p); } } } return res; } /** * @param {String} HTML 字符串 * @return {Element} */ function htmlToElement(html) { var template = document.createElement('template'); html = html.trim(); template.innerHTML = html; // template.content.childNodes; return template.content.firstChild; } var SubjectTypeId; (function (SubjectTypeId) { SubjectTypeId[SubjectTypeId["book"] = 1] = "book"; SubjectTypeId[SubjectTypeId["anime"] = 2] = "anime"; SubjectTypeId[SubjectTypeId["music"] = 3] = "music"; SubjectTypeId[SubjectTypeId["game"] = 4] = "game"; SubjectTypeId[SubjectTypeId["real"] = 6] = "real"; SubjectTypeId["all"] = "all"; })(SubjectTypeId || (SubjectTypeId = {})); const getchuGameModel = { key: 'getchu_game', description: 'Getchu游戏', host: ['getchu.com', 'www.getchu.com'], type: SubjectTypeId.game, pageSelectors: [ { selector: '.genretab.current', subSelector: 'a', keyWord: ['ゲーム', '同人'], }, ], controlSelector: [ { selector: '#soft-title', }, ], itemList: [], }; const commonSelector = { selector: '#soft_table table', subSelector: 'td', sibling: true, }; const dict = { 定価: '售价', 発売日: '发行日期', ジャンル: '游戏类型', ブランド: '开发', 原画: '原画', 音楽: '音乐', シナリオ: '剧本', アーティスト: '主题歌演出', 作詞: '主题歌作词', 作曲: '主题歌作曲', }; const configArr = Object.keys(dict).map((key) => { const r = { name: dict[key], selector: Object.assign({ // 匹配关键字开头 2020/03/18 keyWord: '^' + key }, commonSelector), }; if (key === '発売日') { r.category = 'date'; } return r; }); getchuGameModel.itemList.push({ name: '游戏名', selector: { selector: '#soft-title', }, category: 'subject_title', }, { name: 'cover', selector: [ { selector: '#soft_table .highslide', }, { selector: '#soft_table .highslide img', }, ], category: 'cover', }, ...configArr, { name: '游戏简介', selector: [ { selector: '#wrapper', subSelector: '.tabletitle', sibling: true, keyWord: 'ストーリー', }, { selector: '#wrapper', subSelector: '.tabletitle', sibling: true, keyWord: '商品紹介', }, ], category: 'subject_summary', }); getchuGameModel.defaultInfos = [ { name: '平台', value: 'PC', category: 'platform', }, { name: 'subject_nsfw', value: '1', category: 'checkbox', }, ]; // TODO: 区分 kindle 页面和 纸质书页面 const amazonSubjectModel = { key: 'amazon_jp_book', host: ['amazon.co.jp', 'www.amazon.co.jp'], description: '日亚图书', type: SubjectTypeId.book, pageSelectors: [ { selector: '#nav-subnav .nav-a:first-child', subSelector: '.nav-a-content', keyWord: ['本', '书'], }, { selector: '#wayfinding-breadcrumbs_container .a-unordered-list .a-list-item:first-child', subSelector: '.a-link-normal', keyWord: ['本', '书'], }, ], controlSelector: { selector: '#title', }, itemList: [], }; const commonSelectors = [ // 2021-05 日亚改版 { selector: '#richProductInformation_feature_div', subSelector: 'ol.a-carousel li', }, { selector: '#detailBullets_feature_div .detail-bullet-list', subSelector: 'li .a-list-item', }, { selector: '#detail_bullets_id .bucket .content', subSelector: 'li', }, ]; amazonSubjectModel.itemList.push({ name: '名称', selector: { selector: '#productTitle', }, category: 'subject_title', }, { name: 'cover', selector: [ { selector: 'img#igImage', }, { selector: 'img#imgBlkFront', }, { selector: 'img#ebooksImgBlkFront', }, ], category: 'cover', }, { name: 'ASIN', selector: commonSelectors.map((s) => { return Object.assign(Object.assign({}, s), { keyWord: 'ISBN-10' }); }), category: 'ASIN', }, { name: 'ISBN', selector: commonSelectors.map((s) => { return Object.assign(Object.assign({}, s), { keyWord: 'ISBN-13' }); }), category: 'ISBN', }, { name: '发售日', selector: commonSelectors.map((s) => { return Object.assign(Object.assign({}, s), { keyWord: ['発売日', '出版日期'] }); }), category: 'date', pipes: ['k', 'date'], }, { name: '出版社', selector: commonSelectors.map((s) => { return Object.assign(Object.assign({}, s), { keyWord: '出版社' }); }), }, { name: '页数', selector: commonSelectors.map((s) => { return Object.assign(Object.assign({}, s), { keyWord: ['ページ', '页'] }); }), pipes: ['num'], }, { name: '作者', selector: [ { selector: '#bylineInfo', subSelector: '.author', keyWord: '\\(著\\)', nextSelector: [ { selector: '.contributorNameID', }, { selector: 'a', }, ], }, { selector: '#byline .author span.a-size-medium', }, { selector: '#bylineInfo .author > a', }, { selector: '#bylineInfo .contributorNameID', }, ], category: 'creator', }, { name: '插图', selector: [ { selector: '#bylineInfo', subSelector: '.author', keyWord: 'イラスト', nextSelector: [ { selector: '.contributorNameID', }, { selector: 'a', }, ], }, ], category: 'creator', }, { name: '价格', selector: [ { selector: '.swatchElement.selected .a-color-base .a-size-base', }, { selector: '.swatchElement.selected .a-color-base', }, ], }, { name: '内容简介', selector: [ { selector: '#productDescription', subSelector: 'h3', sibling: true, keyWord: ['内容紹介', '内容'], }, { selector: '#bookDesc_iframe', subSelector: '#iframeContent', isIframe: true, }, ], category: 'subject_summary', }); const erogamescapeModel = { key: 'erogamescape', description: 'erogamescape', host: ['erogamescape.org', 'erogamescape.dyndns.org'], type: SubjectTypeId.game, pageSelectors: [ { selector: '#soft-title', }, ], controlSelector: { selector: '#soft-title', }, itemList: [], }; erogamescapeModel.itemList.push({ name: '游戏名', selector: { selector: '#soft-title > span', }, category: 'subject_title', }, { name: '开发', selector: { selector: '#brand a', }, }, { name: '发行日期', selector: { selector: '#sellday a', }, category: 'date', }, { name: 'cover', selector: { selector: '#image_and_basic_infomation img', }, category: 'cover', }, { name: '官方网站', selector: [ { selector: '#links', subSelector: 'a', keyWord: 'game_OHP', }, { selector: '#bottom_inter_links_main', subSelector: 'a', keyWord: 'game_OHP', }, ], category: 'website', }, { name: '原画', selector: { selector: '#genga > td:last-child', }, }, { name: '剧本', selector: { selector: '#shinario > td:last-child', }, }, { name: '歌手', selector: { selector: '#kasyu > td:last-child', }, }); const steamdbModel = { key: 'steamdb_game', description: 'steamdb', host: ['steamdb.info'], type: SubjectTypeId.game, pageSelectors: [ { selector: '.pagehead h1', }, ], controlSelector: { selector: '.pagehead', }, itemList: [], }; const commonSelector$1 = { selector: '.scope-app .app-row table', subSelector: 'td', sibling: true, }; const dictArr = [ { name: '发行日期', keyWord: 'Release Date', }, { name: '开发', keyWord: 'Developer', }, { name: '发行', keyWord: 'Publisher', }, ]; const configArr$1 = dictArr.map((item) => { const r = { name: item.name, selector: Object.assign({ keyWord: item.keyWord }, commonSelector$1), }; if (item.name === '发行日期') { r.category = 'date'; } return r; }); const detailsTableSelector = { selector: '#info table', subSelector: 'td', sibling: true, }; const subTableSelector = { selector: 'table.web-assets', subSelector: 'td', sibling: true, }; steamdbModel.itemList.push({ name: '游戏名', selector: [ Object.assign(Object.assign({}, detailsTableSelector), { keyWord: 'name_localized', nextSelector: Object.assign(Object.assign({}, subTableSelector), { keyWord: 'japanese' }) }), { selector: '.pagehead h1', }, ], category: 'subject_title', }, { name: '中文名', selector: [ Object.assign(Object.assign({}, detailsTableSelector), { keyWord: 'name_localized', nextSelector: Object.assign(Object.assign({}, subTableSelector), { keyWord: 'schinese' }) }), Object.assign(Object.assign({}, detailsTableSelector), { keyWord: 'name_localized', nextSelector: Object.assign(Object.assign({}, subTableSelector), { keyWord: 'tchinese' }) }), ], category: 'alias', }, { name: '别名', selector: [ Object.assign(Object.assign({}, detailsTableSelector), { keyWord: 'name_localized', nextSelector: Object.assign(Object.assign({}, subTableSelector), { keyWord: 'english' }) }), { selector: '.pagehead h1', }, ], category: 'alias', }, { name: 'cover', selector: [ Object.assign(Object.assign({}, detailsTableSelector), { keyWord: 'library_assets', nextSelector: { selector: 'table.web-assets', subSelector: 'td', keyWord: 'library_capsule', sibling: true, nextSelector: { selector: 'a', }, } }), Object.assign(Object.assign({}, detailsTableSelector), { keyWord: 'Web Assets', nextSelector: { selector: 'table.web-assets', subSelector: 'td > a', keyWord: 'library_600x900', } }), ], category: 'cover', }, ...configArr$1, { name: '游戏简介', selector: [ { selector: 'head meta[name="description"]', }, { selector: '.scope-app header-description', }, ], category: 'subject_summary', }, { name: 'website', selector: { selector: '.app-links a[aria-label^="Games homepage"]', }, category: 'website', }); steamdbModel.defaultInfos = [ { name: '平台', value: 'PC', category: 'platform', }, ]; const steamModel = { key: 'steam_game', description: 'steam', host: ['store.steampowered.com'], type: SubjectTypeId.game, pageSelectors: [ { selector: '.apphub_AppName', }, ], controlSelector: { selector: '.apphub_AppName', }, itemList: [], }; steamModel.itemList.push({ name: '游戏名', selector: { selector: '.apphub_AppName', }, category: 'subject_title', }, { name: '发行日期', selector: { selector: '.release_date .date', }, category: 'date', }, { name: '开发', selector: { selector: '.glance_ctn_responsive_left .user_reviews', subSelector: '.dev_row .subtitle', keyWord: ['开发商', 'DEVELOPER'], sibling: true, }, }, { name: '发行', selector: { selector: '.glance_ctn_responsive_left .user_reviews', subSelector: '.dev_row .subtitle', keyWord: ['发行商', 'PUBLISHER'], sibling: true, }, }, { name: 'website', selector: { selector: '.responsive_apppage_details_left.game_details', subSelector: '.details_block > .linkbar', keyWord: ['访问网站', 'Visit the website'], }, category: 'website', }, { name: '游戏简介', selector: [ { selector: '.game_description_snippet', }, { selector: 'head meta[name="description"]', }, { selector: '#game_area_description', }, ], category: 'subject_summary', } // { // name: 'cover', // selector: { // selector: '#soft_table .highslide', // }, // category: 'cover', // } ); steamModel.defaultInfos = [ { name: '平台', value: 'PC', category: 'platform', }, ]; const dangdangBookModel = { key: 'dangdang_book', host: ['product.dangdang.com'], description: '当当图书', type: SubjectTypeId.book, pageSelectors: [ { selector: '#breadcrumb', subSelector: 'a', keyWord: '图书', }, ], controlSelector: { selector: '.name_info h1', }, itemList: [], }; const infoSelector = { selector: '.messbox_info', subSelector: 'span', }; const descSelector = { selector: '#detail_describe', subSelector: 'li', }; dangdangBookModel.itemList.push({ name: '名称', selector: { selector: '.name_info h1', }, category: 'subject_title', }, // { // name: 'cover', // selector: { // selector: 'img#largePic', // }, // category: 'cover', // }, { name: 'ISBN', selector: Object.assign(Object.assign({}, descSelector), { keyWord: '国际标准书号ISBN' }), category: 'ISBN', }, { name: '发售日', selector: Object.assign(Object.assign({}, infoSelector), { keyWord: '出版时间' }), category: 'date', }, { name: '作者', selector: [ Object.assign(Object.assign({}, infoSelector), { keyWord: '作者' }), ], }, { name: '出版社', selector: Object.assign(Object.assign({}, infoSelector), { keyWord: '出版社' }), }, { name: '内容简介', selector: [ { selector: '#content .descrip', }, ], category: 'subject_summary', }); const jdBookModel = { key: 'jd_book', host: ['item.jd.com'], description: '京东图书', type: SubjectTypeId.book, pageSelectors: [ { selector: '#crumb-wrap', subSelector: '.item > a', keyWord: '图书', }, ], controlSelector: { selector: '#name .sku-name', }, itemList: [], }; const descSelector$1 = { selector: '#parameter2', subSelector: 'li', }; jdBookModel.itemList.push({ name: '名称', selector: { selector: '#name .sku-name', }, category: 'subject_title', }, // { // name: 'cover', // selector: { // selector: '#preview img', // }, // category: 'cover', // }, { name: 'ISBN', selector: Object.assign(Object.assign({}, descSelector$1), { keyWord: 'ISBN' }), category: 'ISBN', }, { name: '发售日', selector: Object.assign(Object.assign({}, descSelector$1), { keyWord: '出版时间' }), category: 'date', }, { name: '作者', selector: [ { selector: '#p-author', keyWord: '著', }, ], }, { name: '出版社', selector: Object.assign(Object.assign({}, descSelector$1), { keyWord: '出版社' }), }, { name: '内容简介', selector: [ { selector: '.book-detail-item', subSelector: '.item-mt', keyWord: '内容简介', sibling: true, }, ], category: 'subject_summary', }); const doubanGameModel = { key: 'douban_game', description: 'douban game', host: ['douban.com', 'www.douban.com'], type: SubjectTypeId.game, pageSelectors: [ { selector: '#content h1', }, ], controlSelector: { selector: '#content h1', }, itemList: [], }; const gameAttr = { selector: '#content .game-attr', subSelector: 'dt', sibling: true, }; doubanGameModel.itemList.push({ name: '游戏名', selector: { selector: '#content h1', }, category: 'subject_title', }, { name: '发行日期', selector: [ Object.assign(Object.assign({}, gameAttr), { keyWord: '发行日期' }), Object.assign(Object.assign({}, gameAttr), { keyWord: '预计上市时间' }), ], category: 'date', }, // 平台特殊处理 // { // name: '平台', // selector: { // ...gameAttr, // keyWord: '平台', // }, // category: 'platform', // }, { name: '别名', selector: Object.assign(Object.assign({}, gameAttr), { keyWord: '别名' }), category: 'alias', }, { name: '游戏类型', selector: Object.assign(Object.assign({}, gameAttr), { keyWord: '类型' }), }, { name: '开发', selector: Object.assign(Object.assign({}, gameAttr), { keyWord: '开发商' }), }, { name: '发行', selector: Object.assign(Object.assign({}, gameAttr), { keyWord: '发行商' }), }, // { // name: 'website', // selector: { // selector: '.responsive_apppage_details_left.game_details', // }, // category: 'website', // }, { name: '游戏简介', selector: [ { selector: '.mod.item-desc', subSelector: 'h2', keyWord: '简介', sibling: true, }, ], category: 'subject_summary', }, { name: 'cover', selector: { selector: '#content .item-subject-info .pic > a', }, category: 'cover', }); const doubanGameEditModel = { key: 'douban_game_edit', description: 'douban game edit', host: ['douban.com', 'www.douban.com'], urlRules: [/\/game\/\d+\/edit/], type: SubjectTypeId.game, pageSelectors: [ { selector: '#content h1', }, ], controlSelector: { selector: '#content h1', }, itemList: [], }; const gameAttr$1 = { selector: '#thing-modify', subSelector: '.thing-item .desc-item .label', sibling: true, }; doubanGameEditModel.itemList.push({ name: '游戏名', selector: [ Object.assign(Object.assign({}, gameAttr$1), { keyWord: '原名' }), Object.assign(Object.assign({}, gameAttr$1), { keyWord: '中文名' }), ], category: 'subject_title', }, { name: '发行日期', selector: [ Object.assign(Object.assign({}, gameAttr$1), { keyWord: '发行日期' }), Object.assign(Object.assign({}, gameAttr$1), { keyWord: '预计上市时间' }), ], category: 'date', }, { name: '平台', selector: Object.assign(Object.assign({}, gameAttr$1), { keyWord: '平台' }), category: 'platform', }, { name: '中文名', selector: Object.assign(Object.assign({}, gameAttr$1), { keyWord: '中文名' }), category: 'alias', }, { name: '别名', selector: Object.assign(Object.assign({}, gameAttr$1), { keyWord: '别名' }), category: 'alias', }, { name: '游戏类型', selector: Object.assign(Object.assign({}, gameAttr$1), { keyWord: '类型' }), }, { name: '开发', selector: Object.assign(Object.assign({}, gameAttr$1), { keyWord: '开发商' }), }, { name: '发行', selector: Object.assign(Object.assign({}, gameAttr$1), { keyWord: '发行商' }), }, // { // name: '游戏简介', // selector: [ // { // selector: '#thing_desc_options_0', // }, // ], // category: 'subject_summary', // }, { name: 'cover', selector: Object.assign(Object.assign({}, gameAttr$1), { keyWord: '图标', nextSelector: { selector: 'img', } }), category: 'cover', }); const dlsiteGameModel = { key: 'dlsite_game', description: 'dlsite游戏', host: ['dlsite.com', 'www.dlsite.com'], type: SubjectTypeId.game, pageSelectors: [ { selector: '.floorTab-item.type-doujin.is-active', }, { selector: '.floorTab-item.type-com.is-active', }, ], controlSelector: [ { selector: '#work_name', }, ], itemList: [], }; const commonSelector$2 = { selector: '#work_outline', subSelector: 'th', sibling: true, }; const arrDict = [ { name: '发行日期', key: ['販売日', '贩卖日'], categrory: 'date', }, // { // name: '游戏类型', // key: ['ジャンル', '分类'], // }, { name: '作者', key: ['作者'], }, { name: '原画', key: ['イラスト', '插画'], }, { name: '剧本', key: ['シナリオ', '剧情'], }, { name: '声优', key: ['声優', '声优'], }, { name: '音乐', key: ['音乐', '音楽'], }, ]; const configArr$2 = arrDict.map((obj) => { const r = { name: obj.name, selector: Object.assign({ keyWord: obj.key }, commonSelector$2), }; if (obj.categrory) { r.category = obj.categrory; } return r; }); dlsiteGameModel.itemList.push({ name: '游戏名', selector: { selector: '#work_name', }, category: 'subject_title', }, { // name: '社团名', name: '开发', selector: [ { selector: '#work_maker .maker_name a', }, ], }, ...configArr$2, { name: 'cover', selector: [ { selector: '.slider_body_inner.swiper-container-horizontal>ul.slider_items>li.slider_item:first-child>img', }, ], category: 'cover', }, { name: '游戏简介', selector: [ { selector: '.work_parts_container', subSelector: '.work_parts_heading', keyWord: 'あらすじ', sibling: true, }, { selector: '#intro-title + div', }, ], category: 'subject_summary', }, { name: 'website', selector: [ { selector: '#work_name > a', }, ], category: 'website', }); dlsiteGameModel.defaultInfos = [ { name: '平台', value: 'PC', category: 'platform', }, { name: 'subject_nsfw', value: '1', category: 'checkbox', }, ]; const dmmGameModel = { key: 'dmm_game', description: 'dmm游戏', host: ['dlsoft.dmm.co.jp'], type: SubjectTypeId.game, pageSelectors: [ { selector: '.ntgnav-mainItem.is-active', subSelector: 'span', keyWord: 'ゲーム', }, ], controlSelector: [ { selector: 'h1#title', }, ], itemList: [], }; const commonSelector$3 = { selector: '.main-area-center .container02 table', subSelector: 'tr', nextSelector: { selector: '.type-right', }, }; const contentIframe = { selector: '#if_view', isIframe: true, subSelector: 'body', }; const arrDict$1 = [ { name: '发行日期', key: ['配信開始日'], categrory: 'date', }, { name: '游戏类型', key: ['ゲームジャンル'], }, { name: '原画', key: ['原画'], }, { name: '剧本', key: ['シナリオ', '剧情'], }, ]; const configArr$3 = arrDict$1.map((obj) => { const r = { name: obj.name, selector: Object.assign({ keyWord: obj.key }, commonSelector$3), }; if (obj.categrory) { r.category = obj.categrory; } return r; }); dmmGameModel.itemList.push({ name: '游戏名', selector: { selector: '#title', }, category: 'subject_title', }, { name: '开发', selector: [ { selector: '.ranking-and-brand .brand', subSelector: 'td', keyWord: 'ブランド', sibling: true, }, ], }, ...configArr$3, { name: 'cover', selector: [ Object.assign(Object.assign({}, contentIframe), { nextSelector: { selector: '#guide-head > img', } }), ], category: 'cover', }, { name: '游戏简介', selector: [ Object.assign(Object.assign({}, contentIframe), { nextSelector: { selector: '#guide-content', subSelector: '.guide-capt', keyWord: '作品紹介', sibling: true, } }), { selector: '.read-text-area .text-overflow', }, ], category: 'subject_summary', }); dmmGameModel.defaultInfos = [ { name: '平台', value: 'PC', category: 'platform', }, { name: 'subject_nsfw', value: '1', category: 'checkbox', }, ]; const dlsiteGameCharaModel = { key: 'dlsite_game_chara', siteKey: 'dlsite_game', description: 'dlsite游戏角色', host: ['dlsite.com', 'www.dlsite.com'], type: SubjectTypeId.game, itemSelector: { selector: '.work_parts_multiimage_item', }, controlSelector: [ { selector: '.work_parts.type_multiimages *:first-child', }, { selector: '#work_name', }, ], itemList: [], }; // 限定父节点 dlsiteGameCharaModel.itemList.push({ name: 'cover', selector: { selector: '.image img', }, category: 'crt_cover', }); const dmmGameCharaModel = { key: 'dmm_game_chara', siteKey: 'dmm_game', description: 'dmm 游戏角色', type: SubjectTypeId.game, itemSelector: { selector: '#if_view', isIframe: true, subSelector: 'body', nextSelector: { selector: '.guide-sect .guide-box-chr', }, }, controlSelector: [ { selector: '#title', }, ], itemList: [], }; // 限定父节点 dmmGameCharaModel.itemList.push({ name: '姓名', selector: { selector: '.guide-tx16.guide-bold.guide-lin-hgt', }, category: 'crt_name', pipes: ['p', 'ta'], }, { name: 'cover', selector: { selector: 'img', }, category: 'crt_cover', }); // 新增的 site model 需要在这里配置 const configs = { [getchuGameModel.key]: getchuGameModel, [erogamescapeModel.key]: erogamescapeModel, [amazonSubjectModel.key]: amazonSubjectModel, [steamdbModel.key]: steamdbModel, [steamModel.key]: steamModel, [dangdangBookModel.key]: dangdangBookModel, [jdBookModel.key]: jdBookModel, [doubanGameModel.key]: doubanGameModel, [doubanGameEditModel.key]: doubanGameEditModel, [dlsiteGameModel.key]: dlsiteGameModel, [dmmGameModel.key]: dmmGameModel, }; const charaModelDict = { [dlsiteGameCharaModel.key]: dlsiteGameCharaModel, [dmmGameCharaModel.key]: dmmGameCharaModel, }; function findModelByHost(host) { const keys = Object.keys(configs); const models = []; for (let i = 0; i < keys.length; i++) { const hosts = configs[keys[i]].host; if (hosts.includes(host)) { models.push(configs[keys[i]]); // return configs[keys[i]]; } } return models; } function getCharaModel(key) { const keys = Object.keys(charaModelDict); const targetKey = keys.find((k) => { var _a; return ((_a = charaModelDict[k]) === null || _a === void 0 ? void 0 : _a.siteKey) == key; }); if (!targetKey) return null; return charaModelDict[targetKey]; } // support GM_XMLHttpRequest function fetchInfo(url, type, opts = {}, TIMEOUT = 10 * 1000) { // @ts-ignore { return new Promise((resolve, reject) => { // @ts-ignore GM_xmlhttpRequest(Object.assign({ method: 'GET', timeout: TIMEOUT, url, responseType: type, onload: function (res) { resolve(res.response); }, onerror: reject }, opts)); }); } } function fetchBinary(url, opts = {}) { return fetchInfo(url, 'blob', opts); } function fetchText(url, TIMEOUT = 10 * 1000) { return fetchInfo(url, 'text', {}, TIMEOUT); } function fetchJson(url, opts = {}) { return fetchInfo(url, 'json', opts); } function genRandomStr(len) { return Array.apply(null, Array(len)) .map(function () { return (function (chars) { return chars.charAt(Math.floor(Math.random() * chars.length)); })('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'); }) .join(''); } function formatDate(time, fmt = 'yyyy-MM-dd') { const date = new Date(time); var o = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds(), 'q+': Math.floor((date.getMonth() + 3) / 3), S: date.getMilliseconds(), }; if (/(y+)/i.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); } for (var k in o) { if (new RegExp('(' + k + ')', 'i').test(fmt)) { fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); } } return fmt; } function dealDate(dataStr) { // 2019年12月19 let l = []; if (/\d{4}年\d{1,2}月(\d{1,2}日?)?/.test(dataStr)) { l = dataStr .replace('日', '') .split(/年|月/) .filter((i) => i); } else if (/\d{4}\/\d{1,2}(\/\d{1,2})?/.test(dataStr)) { l = dataStr.split('/'); } else if (/\d{4}-\d{1,2}(-\d{1,2})?/.test(dataStr)) { return dataStr; } else { return dataStr; } return l .map((i) => { if (i.length === 1) { return `0${i}`; } return i; }) .join('-'); } function isEqualDate(d1, d2) { const resultDate = new Date(d1); const originDate = new Date(d2); if (resultDate.getFullYear() === originDate.getFullYear() && resultDate.getMonth() === originDate.getMonth() && resultDate.getDate() === originDate.getDate()) { return true; } return false; } const pipeFnDict = { // t: 去除开头和结尾的空格 t: trimSpace, // ta: 去除所有空格 ta: trimAllSpace, // k: 去除关键字; k: trimKeywords, // p: 括号 p: trimParenthesis, // pn: 括号不含数字 pn: trimParenthesisN, // num: 提取数字 num: getNum, date: getDate, }; function getStr(pipe) { return (pipe.out || pipe.rawInfo).trim(); } function trim(pipe, textList) { let str = getStr(pipe); return Object.assign(Object.assign({}, pipe), { out: str.replace(new RegExp(textList.join('|'), 'g'), '') }); } function trimAllSpace(pipe) { let str = getStr(pipe); return Object.assign(Object.assign({}, pipe), { out: str.replace(/\s/g, '') }); } function trimSpace(pipe) { let str = getStr(pipe); return Object.assign(Object.assign({}, pipe), { out: str.trim() }); } function trimParenthesis(pipe) { const textList = ['\\(.*?\\)', '(.*?)']; // const textList = ['\\([^d]*?\\)', '([^d]*?)']; // 去掉多余的括号信息 return trim(pipe, textList); } // 保留括号里面的数字. 比如一些图书的 1 2 3 function trimParenthesisN(pipe) { // const textList = ['\\(.*?\\)', '(.*?)']; const textList = ['\\([^d]*?\\)', '([^d]*?)']; // 去掉多余的括号信息 return trim(pipe, textList); } function trimKeywords(pipe, keyWords) { return trim(pipe, keyWords.map((k) => `${k}\s*?(:|:)?`)); } function getNum(pipe) { let str = getStr(pipe); const m = str.match(/\d+/); return { rawInfo: pipe.rawInfo, out: m ? m[0] : '', }; } function getDate(pipe) { let dataStr = getStr(pipe); return { rawInfo: pipe.rawInfo, out: dealDate(dataStr), }; } /** * * @param str 原字符串 * @param pipes 管道 * @returns 处理后的字符串 */ function dealTextByPipe(str, pipes, argsDict = {}) { let current = { rawInfo: str }; pipes = pipes || []; for (const p of pipes) { if (p instanceof Function) { // @TODO 支持传递参数 current = p(current); } else { if (argsDict[p]) { current = pipeFnDict[p](current, ...argsDict[p]); } else { current = pipeFnDict[p](current); } } } return current.out || str; } const amazonUtils = { dealTitle(str) { str = str.trim().split('\n')[0].trim(); // str = str.split(/\s[((][^0-9))]+?[))]/)[0] // 去掉尾部括号的内容, (1) (1) 这类不处理 return str.replace(/\s[((][^0-9))]+?[))]$/g, '').trim(); // return str.replace(/(?:(\d+))(\)|)).*$/, '$1$2').trim(); }, }; const amazonJpBookTools = { filters: [ { category: 'subject_title', dealFunc: amazonUtils.dealTitle, }, ], hooks: { beforeCreate() { return __awaiter(this, void 0, void 0, function* () { const $t = document.querySelector('#title'); const bookTypeList = document.querySelectorAll('#tmmSwatches ul > li.swatchElement'); if ($t && bookTypeList && bookTypeList.length > 1) { const $div = document.createElement('div'); const $s = document.createElement('span'); $s.style.color = 'red'; $s.style.fontWeight = '600'; $s.innerHTML = '注意: '; const $txt = document.createElement('span'); $txt.innerHTML = '书籍存在多种版本,请优先选择实体书创建。(辅助创建脚本)'; $div.appendChild($s); $div.appendChild($txt); $div.style.padding = '6px 0'; $t.insertAdjacentElement('afterend', $div); } return true; }); }, afterGetWikiData(infos) { return __awaiter(this, void 0, void 0, function* () { const res = []; for (const info of infos) { let newInfo = Object.assign({}, info); if (info.name === '页数') { let val = (info.value || '').trim().replace(/ページ|页/, ''); if (val && val.length < 8 && val.indexOf('予約商品') === -1) { newInfo.value = val; } else { newInfo = null; } } if (newInfo) { res.push(Object.assign({}, newInfo)); } } return res; }); }, }, }; const dlsiteTools = { hooks: { afterGetWikiData(infos) { return __awaiter(this, void 0, void 0, function* () { const res = []; for (const info of infos) { let val = info.value; if (val && typeof val === 'string' && !/http/.test(val) && ['原画', '剧本', '音乐', '游戏类型', '声优'].includes(info.name)) { const v = info.value.split('/'); if (v && v.length > 1) { val = v.map((s) => s.trim()).join(', '); } } res.push(Object.assign(Object.assign({}, info), { value: val })); } return res; }); }, }, filters: [ { category: 'date', dealFunc(str) { if (/年/.test(str)) { return dealDate(str.replace(/日.+$/, '日')); } return str; }, }, ], }; const dlsiteCharaTools = { hooks: { afterGetWikiData(infos, model, el) { var _a; return __awaiter(this, void 0, void 0, function* () { const res = [...infos]; const txt = ((_a = el.querySelector('p')) === null || _a === void 0 ? void 0 : _a.textContent) || ''; res.push({ name: '姓名', value: txt.split('\n')[0], category: 'crt_name', }); res.push({ name: 'CV', value: (txt.split('\n').find((s) => s.includes('CV')) || '') .replace('CV:', '') .trim(), }); let idx = txt.indexOf('\n\n'); if (idx === -1) { idx = 0; } else { idx = idx + 2; } res.push({ name: '人物简介', value: txt.slice(idx), category: 'crt_summary', }); return res; }); }, }, }; /** * convert base64/URLEncoded data component to raw binary data held in a string * https://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata * @param dataURI */ function dataURItoBlob(dataURI) { var byteString; if (dataURI.split(',')[0].indexOf('base64') >= 0) byteString = atob(dataURI.split(',')[1]); else byteString = decodeURI(dataURI.split(',')[1]); // instead of unescape // separate out the mime component var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to a typed array var ia = new Uint8Array(byteString.length); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ia], { type: mimeString }); } function getImageDataByURL(url) { if (!url) return Promise.reject('invalid img url'); return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { try { const blob = yield fetchBinary(url); var reader = new FileReader(); reader.onloadend = function () { resolve(reader.result); }; reader.readAsDataURL(blob); reader.onerror = reject; } catch (e) { reject(e); } })); } /** * convert to img Element to base64 string * @param $img */ function convertImgToBase64($img) { const canvas = document.createElement('canvas'); canvas.width = $img.width; canvas.height = $img.height; const ctx = canvas.getContext('2d'); ctx.drawImage($img, 0, 0, $img.width, $img.height); const dataURL = canvas.toDataURL('image/png'); return dataURL; } function getCover($d, site) { return __awaiter(this, void 0, void 0, function* () { let url; let dataUrl = ''; if ($d.tagName.toLowerCase() === 'a') { url = $d.getAttribute('href'); } else if ($d.tagName.toLowerCase() === 'img') { url = $d.getAttribute('src'); } if (!url) return; try { // 跨域的图片不能用这种方式 // dataUrl = convertImgToBase64($d as any); dataUrl = yield getImageDataByURL(url); if (dataUrl) { return { url, dataUrl, }; } } catch (error) { return { url, dataUrl: url, }; } }); } const charaInfoDict = { 趣味: '爱好', 誕生日: '生日', '3サイズ': 'BWH', スリーサイズ: 'BWH', 身長: '身高', 血液型: '血型', }; const dmmTools = { hooks: { afterGetWikiData(infos) { var _a; return __awaiter(this, void 0, void 0, function* () { const res = []; const hasCover = infos.some((info) => info.category == 'cover'); for (const info of infos) { let val = info.value; res.push(Object.assign(Object.assign({}, info), { value: val })); } if (!hasCover) { // 使用 slider 里面的第一个图片 const slides = $qa('.image-slider.slick-slider li.slick-slide'); if (slides) { let url; let dataUrl = ''; const targetSlide = Array.from(slides).find((slide) => slide.dataset.slickIndex === '0'); url = (_a = targetSlide.querySelector('img')) === null || _a === void 0 ? void 0 : _a.getAttribute('src'); if (url) { try { dataUrl = yield getImageDataByURL(url); } catch (error) { dataUrl = url; } res.push({ name: 'cover', category: 'cover', value: { url, dataUrl, }, }); } } } return res; }); }, }, filters: [ { category: 'date', dealFunc(str) { const re = /\d{4}\/\d{1,2}(\/\d{1,2})?/; const m = str.match(re); if (m) { return dealDate(m[0]); } return str; }, }, ], }; const dmmCharaTools = { hooks: { afterGetWikiData(infos, model, $el) { return __awaiter(this, void 0, void 0, function* () { const res = [...infos]; const $nameTxt = $el.querySelector('.guide-tx16.guide-bold.guide-lin-hgt'); if ($nameTxt) { // (きのみや なのか) const nameTxt = $nameTxt.textContent; if (nameTxt.match(/((.*))/)) { res.push({ name: '纯假名', value: nameTxt.match(/((.*))/)[1], }); } const cvTxt = $nameTxt.nextSibling.textContent; if (/CV/.test(cvTxt)) { res.push({ name: 'CV', value: cvTxt.replace(/CV:/, '').replace(/\s/g, ''), }); } } const boxArr = Array.from($el.querySelectorAll('.box')); for (const $box of boxArr) { const txtArr = $box.textContent .trim() .split(/:|:/) .map((s) => s.trim()); if (charaInfoDict[txtArr[0]]) { res.push({ name: charaInfoDict[txtArr[0]], value: txtArr[1], }); } else { res.push({ name: txtArr[0], value: txtArr[1], }); } } const $summary = $nameTxt.closest('div').cloneNode(true); $summary.firstElementChild.remove(); res.push({ name: '人物简介', value: $summary.textContent, category: 'crt_summary', }); return res; }); }, }, }; const doubanTools = { hooks: { beforeCreate() { return __awaiter(this, void 0, void 0, function* () { const href = window.location.href; if (/\/game\//.test(href) && !/\/game\/\d+\/edit/.test(href)) { return { payload: { auxSite: document.querySelector('.th-modify > a').href, auxPrefs: { originNames: ['平台', '发行日期'], targetNames: 'all', }, }, }; } }); }, afterGetWikiData(infos) { return __awaiter(this, void 0, void 0, function* () { const res = []; for (const info of infos) { if (['平台', '别名'].includes(info.name)) { const pArr = info.value.split('/').map((i) => { return Object.assign(Object.assign({}, info), { value: i.trim() }); }); res.push(...pArr); } else if (info.category === 'cover') { res.push(Object.assign({}, info)); } else { let val = info.value; if (val && typeof val === 'string') { const v = info.value.split('/'); if (v && v.length > 1) { val = v.map((s) => s.trim()).join(', '); } } if (info.name === '游戏类型' && val) { val = val.replace('游戏, ', '').trim(); } res.push(Object.assign(Object.assign({}, info), { value: val })); } } // 特殊处理平台 const $plateform = findElement({ selector: '#content .game-attr', subSelector: 'dt', sibling: true, keyWord: '平台', }); if ($plateform) { const aList = $plateform.querySelectorAll('a') || []; for (const $a of aList) { res.push({ name: '平台', value: getText($a).replace(/\/.*/, '').trim(), category: 'platform', }); } } return res; }); }, }, filters: [], }; const doubanGameEditTools = { hooks: { beforeCreate() { return __awaiter(this, void 0, void 0, function* () { const href = window.location.href; return /\/game\/\d+\/edit/.test(href); }); }, afterGetWikiData(infos) { return __awaiter(this, void 0, void 0, function* () { const res = []; for (const info of infos) { const arr = Object.assign({}, info); if (['平台', '别名'].includes(info.name)) { const plateformDict = { ARC: 'Arcade', NES: 'FC', 红白机: 'FC', 街机: 'Arcade', }; const pArr = info.value.split(',').map((i) => { let v = i.trim(); if (plateformDict[v]) { v = plateformDict[v]; } return Object.assign(Object.assign({}, info), { value: v }); }); res.push(...pArr); } else if (arr.category === 'cover' && arr.value && arr.value.url) { try { const url = arr.value.url.replace('/spic/', '/lpic/'); const dataUrl = yield getImageDataByURL(url); const coverItem = Object.assign(Object.assign({}, arr), { value: { dataUrl, url, } }); res.push(coverItem); } catch (error) { console.error(error); } } else if (arr.name === '游戏类型') { arr.value = arr.value.replace(/,(?!\s)/g, ', '); res.push(arr); } else if (arr.name === '开发') { arr.value = arr.value.replace(/,(?!\s)/g, ', '); res.push(arr); } else { res.push(arr); } } // 描述 const inputList = document.querySelectorAll('input[name="target"][type="hidden"]'); inputList.forEach(($input) => { const val = $input.value; if (val === 'description') { const $target = $input .closest('form') .querySelector('.desc-form-item #thing_desc_options_0'); if ($target) { res.push({ name: '游戏简介', value: $target.value, category: 'subject_summary', }); } } }); return res; }); }, }, filters: [], }; const getchuTools = { dealTitle(str) { str = str.trim().split('\n')[0]; str = str .split('+')[0] .replace(/(このタイトルの関連商品)/, '') .trim(); return str.replace(/\s[^ ]*?(スペシャルプライス版|限定版|通常版|廉価版|復刻版|初回.*?版|描き下ろし).*?$|<.*>$/g, ''); }, getExtraCharaInfo(txt) { const re = /[^\s]+?[::]/g; const matchedArr = txt.match(re); if (!matchedArr) return []; const infoArr = txt.split(re); const res = []; matchedArr.forEach((item, idx) => { const val = (infoArr[idx + 1] || '').trim(); if (val) { res.push({ name: item.replace(/:|:/, ''), value: val, }); } }); return res; }, getCharacterInfo($t) { const charaData = []; const $name = $t.closest('dt').querySelector('h2'); let name; if ($name.querySelector('charalist')) { const $charalist = $name.querySelector('charalist'); name = getText($charalist); } else { if ($name.classList.contains('chara-name') && $name.querySelector('br')) { name = $name .querySelector('br') .nextSibling.textContent.split(/(|\(|\sCV|新建角色/)[0]; } else { name = getText($name).split(/(|\(|\sCV|新建角色/)[0]; } } charaData.push({ name: '姓名', value: name.replace(/\s/g, ''), category: 'crt_name', }); charaData.push({ name: '日文名', value: name, }); const nameTxt = getText($name); if (nameTxt.match(/((.*))/)) { charaData.push({ name: '纯假名', value: nameTxt.match(/((.*))/)[1], }); } const cvMatch = nameTxt.match(/(?<=CV[::]).+/); if (cvMatch) { charaData.push({ name: 'CV', value: cvMatch[0].replace(/\s/g, ''), }); } const $img = $t.closest('tr').querySelector('td > img'); if ($img) { charaData.push({ name: 'cover', value: convertImgToBase64($img), category: 'crt_cover', }); } // 处理杂项 参考 id=1074002 id=735329 id=1080370 // id=1080431 // id=840936 // dd tag const $dd = $t.closest('dt').nextElementSibling; const $clonedDd = $dd.cloneNode(true); Array.prototype.forEach.call($clonedDd.querySelectorAll('span[style^="font-weight"]'), (node) => { const t = getText(node).trim(); t.split(/\n/g).forEach((el) => { const extraInfo = getchuTools.getExtraCharaInfo(el); if (extraInfo.length) { charaData.push(...extraInfo); } else { const c = el.match(/B.*W.*H\d+/); if (c) { charaData.push({ name: 'BWH', value: c[0], }); } } }); node.remove(); }); charaData.push({ name: '人物简介', value: getText($clonedDd).trim(), category: 'crt_summary', }); const dict = { 誕生日: '生日', '3サイズ': 'BWH', スリーサイズ: 'BWH', 身長: '身高', 血液型: '血型', }; charaData.forEach((item) => { if (dict[item.name]) { item.name = dict[item.name]; } }); return charaData; }, }; const getchuSiteTools = { hooks: { beforeCreate() { return __awaiter(this, void 0, void 0, function* () { const $t = document.querySelector('#soft-title'); if (!$t) return false; const rawTitle = $t.textContent.trim(); if (/[同人グッズ|同人誌|同人音楽]/.test(rawTitle)) return false; return true; }); }, }, filters: [ { category: 'subject_title', dealFunc: getchuTools.dealTitle, }, ], }; const getchuCharaTools = { hooks: { afterGetWikiData(infos, model, $el) { return __awaiter(this, void 0, void 0, function* () { const res = [...infos]; const $chara = $el.querySelector('h2.chara-name'); if ($chara) { res.push(...getchuTools.getCharacterInfo($chara)); } return res; }); }, }, }; function getSteamdbURL(href) { var _a; href = href || (location === null || location === void 0 ? void 0 : location.href); const id = (_a = href.match(/store\.steampowered\.com\/app\/(\d+)\/?/)) === null || _a === void 0 ? void 0 : _a[1]; if (id) { return `https://steamdb.info/app/${id}/info/`; } return ''; } function getSteamURL(href) { var _a; href = href || (location === null || location === void 0 ? void 0 : location.href); const id = (_a = href.match(/steamdb\.info\/app\/(\d+)\/?/)) === null || _a === void 0 ? void 0 : _a[1]; if (id) { return `https://store.steampowered.com/app/${id}/_/`; } return ''; } const steamTools = { hooks: { beforeCreate() { return __awaiter(this, void 0, void 0, function* () { return { payload: { disableDate: true, auxSite: getSteamdbURL(window.location.href), }, }; }); }, }, filters: [ { category: 'website', dealFunc(str) { // https://steamcommunity.com/linkfilter/?url=https://www.koeitecmoamerica.com/ryza/ const arr = str.split('?url='); return arr[1] || ''; }, }, { category: 'date', dealFunc(str) { if (/年/.test(str)) { return dealDate(str); } return formatDate(str); }, }, ], }; const steamdbTools = { hooks: { beforeCreate() { return __awaiter(this, void 0, void 0, function* () { return { payload: { disableDate: true, auxSite: getSteamURL(window.location.href), }, }; }); }, }, filters: [ { category: 'date', dealFunc(str) { const arr = str.split('–'); if (!arr[0]) return ''; return formatDate(arr[0].trim()); }, }, ], }; function trimParenthesis$1(str) { const textList = ['\\([^d]*?\\)', '([^d]*?)']; // 去掉多余的括号信息 return str.replace(new RegExp(textList.join('|'), 'g'), '').trim(); } function identity(x) { return x; } const noOps = () => Promise.resolve(true); function getHooks(siteConfig, timing) { var _a; const hooks = ((_a = sitesFuncDict[siteConfig.key]) === null || _a === void 0 ? void 0 : _a.hooks) || {}; return hooks[timing] || noOps; } function getCharaHooks(config, timing) { var _a; const hooks = ((_a = charaFuncDict[config.key]) === null || _a === void 0 ? void 0 : _a.hooks) || {}; return hooks[timing] || noOps; } function dealFuncByCategory(key, category) { var _a; let fn; if ((_a = sitesFuncDict[key]) === null || _a === void 0 ? void 0 : _a.filters) { const obj = sitesFuncDict[key].filters.find((x) => x.category === category); fn = obj && obj.dealFunc; } if (fn) { return fn; } else { return (str = '') => identity(str.trim()); } } const sitesFuncDict = { amazon_jp_book: amazonJpBookTools, dangdang_book: { filters: [ { category: 'date', dealFunc(str) { return dealDate(str.replace(/出版时间[::]/, '').trim()); }, }, { category: 'subject_title', dealFunc(str) { return trimParenthesis$1(str); }, }, ], }, jd_book: { filters: [ { category: 'subject_title', dealFunc(str) { return trimParenthesis$1(str); }, }, ], }, getchu_game: getchuSiteTools, steam_game: steamTools, steamdb_game: steamdbTools, douban_game: doubanTools, douban_game_edit: doubanGameEditTools, dlsite_game: dlsiteTools, dmm_game: dmmTools, }; // 存储新建角色的钩子函数和 filters const charaFuncDict = { dlsite_game_chara: dlsiteCharaTools, dmm_game_chara: dmmCharaTools, getchu_chara: getchuCharaTools, }; /** * 处理单项 wiki 信息 * @param str * @param category * @param keyWords */ function dealItemText(str, category = '', keyWords = []) { if (['subject_summary', 'subject_title'].indexOf(category) !== -1) { return str; } const textList = ['\\(.*?\\)', '(.*?)']; // 去掉多余的括号信息 // const keyStr = keyWords.sort((a, b) => b.length - a.length).join('|') // `(${keyStr})(${separators.join('|')})?` return str .replace(new RegExp(textList.join('|'), 'g'), '') .replace(new RegExp(keyWords.map((k) => `${k}\s*?(:|:)?`).join('|'), 'g'), '') .replace(/[^\d:]+?(:|:)/, '') .trim(); } function getWikiItem(infoConfig, site) { return __awaiter(this, void 0, void 0, function* () { if (!infoConfig) return; const sl = infoConfig.selector; let $d; let targetSelector; if (sl instanceof Array) { let i = 0; targetSelector = sl[i]; while (!($d = findElement(targetSelector)) && i < sl.length) { targetSelector = sl[++i]; } } else { targetSelector = sl; $d = findElement(targetSelector); } if (!$d) return; let keyWords; if (targetSelector.keyWord instanceof Array) { keyWords = targetSelector.keyWord; } else { keyWords = [targetSelector.keyWord]; } let val; let txt = getText($d); const pipeArgsDict = { k: [keyWords], }; switch (infoConfig.category) { case 'cover': case 'crt_cover': val = yield getCover($d); break; case 'subject_summary': // 优先使用 innerText const innerTxt = getInnerText($d); if (innerTxt) { txt = innerTxt; } case 'alias': case 'subject_title': // 有管道优先使用管道处理数据. 兼容之前使用写法 if (infoConfig.pipes) { val = dealTextByPipe(txt, infoConfig.pipes, pipeArgsDict); } else { val = dealFuncByCategory(site, infoConfig.category)(txt); } break; case 'website': val = dealFuncByCategory(site, 'website')($d.getAttribute('href')); break; case 'date': // 有管道优先使用管道处理数据. 兼容之前使用写法 if (infoConfig.pipes) { val = dealTextByPipe(txt, infoConfig.pipes, pipeArgsDict); } else { // 日期预处理,不能删除 val = dealItemText(txt, infoConfig.category, keyWords); val = dealFuncByCategory(site, infoConfig.category)(val); } break; default: // 有管道优先使用管道处理数据. 兼容之前使用写法 if (infoConfig.pipes) { val = dealTextByPipe(txt, infoConfig.pipes, pipeArgsDict); } else { val = dealItemText(txt, infoConfig.category, keyWords); } } // 信息后处理 if (infoConfig.category === 'creator') { val = val.replace(/\s/g, ''); } if (val) { return { name: infoConfig.name, value: val, category: infoConfig.category, }; } }); } function getWikiData(siteConfig, el) { return __awaiter(this, void 0, void 0, function* () { if (el) { window._parsedEl = el; } else { window._parsedEl = null; } const r = yield Promise.all(siteConfig.itemList.map((item) => getWikiItem(item, siteConfig.key))); delete window._parsedEl; const defaultInfos = siteConfig.defaultInfos || []; let rawInfo = r.filter((i) => i); const hookRes = yield getHooks(siteConfig, 'afterGetWikiData')(rawInfo, siteConfig); if (Array.isArray(hookRes) && hookRes.length) { rawInfo = hookRes; } return [...rawInfo, ...defaultInfos]; }); } function getCharaData(model, el) { return __awaiter(this, void 0, void 0, function* () { if (el) { window._parsedEl = el; } else { window._parsedEl = null; } const r = yield Promise.all(model.itemList.map((item) => getWikiItem(item, model.key))); delete window._parsedEl; const defaultInfos = model.defaultInfos || []; let rawInfo = r.filter((i) => i); const hookRes = yield getCharaHooks(model, 'afterGetWikiData')(rawInfo, model, el); if (Array.isArray(hookRes) && hookRes.length) { rawInfo = hookRes; } return [...rawInfo, ...defaultInfos]; }); } /** * 过滤搜索结果: 通过名称以及日期 * @param items * @param subjectInfo * @param opts */ function filterResults(items, subjectInfo, opts = {}, isSearch = true) { var _a; if (!items) return; // 只有一个结果时直接返回, 不再比较日期 if (items.length === 1 && isSearch) { const result = items[0]; return result; // if (isEqualDate(result.releaseDate, subjectInfo.releaseDate)) { // } } let results = new Fuse(items, Object.assign({}, opts)).search(subjectInfo.name); if (!results.length) return; // 有参考的发布时间 if (subjectInfo.releaseDate) { for (const item of results) { const result = item.item; if (result.releaseDate) { if (isEqualDate(result.releaseDate, subjectInfo.releaseDate)) { return result; } } } } // 比较名称 const nameRe = new RegExp(subjectInfo.name.trim()); for (const item of results) { const result = item.item; if (nameRe.test(result.name) || nameRe.test(result.greyName)) { return result; } } return (_a = results[0]) === null || _a === void 0 ? void 0 : _a.item; } function getQueryInfo(items) { let info = {}; items.forEach((item) => { if (item.category === 'subject_title') { info.name = item.value; } if (item.category === 'date') { info.releaseDate = item.value; } if (item.category === 'ASIN') { info.asin = item.value; } if (item.category === 'ISBN') { info.isbn = item.value; } }); return info; } /** * 插入控制的按钮 * @param $t 父节点 * @param cb 返回 Promise 的回调 */ function insertControlBtn($t, cb) { if (!$t) return; const $div = document.createElement('div'); const $s = document.createElement('span'); $s.classList.add('e-wiki-new-subject'); $s.innerHTML = '新建'; const $search = $s.cloneNode(); $search.innerHTML = '新建并查重'; $div.appendChild($s); $div.appendChild($search); $t.insertAdjacentElement('afterend', $div); $s.addEventListener('click', (e) => __awaiter(this, void 0, void 0, function* () { yield cb(e); })); $search.addEventListener('click', (e) => __awaiter(this, void 0, void 0, function* () { if ($search.innerHTML !== '新建并查重') return; $search.innerHTML = '查重中...'; try { yield cb(e, true); $search.innerHTML = '新建并查重'; } catch (e) { if (e === 'notmatched') { $search.innerHTML = '未查到条目'; } console.error(e); } })); } /** * 插入新建角色控制的按钮 * @param $t 父节点 * @param cb 返回 Promise 的回调 */ function insertControlBtnChara($t, cb) { if (!$t) return; const $div = document.createElement('div'); const $s = document.createElement('a'); $s.classList.add('e-wiki-new-character'); // $s.setAttribute('target', '_blank') $s.innerHTML = '添加新虚拟角色'; $div.appendChild($s); $t.insertAdjacentElement('afterend', $div); $s.addEventListener('click', (e) => __awaiter(this, void 0, void 0, function* () { yield cb(e); })); } function addCharaUI($t, names, cb) { if (!$t) return; // @TODO 增加全部 // const btn = `添加新虚拟角色`; const $div = htmlToElement(`