├── .gitignore ├── demo ├── 01.gif ├── 02.jpg ├── 03.gif └── novel_sources.json ├── src ├── constants.ts ├── parser │ ├── index.ts │ ├── rule.ts │ ├── general.ts │ └── parser.ts ├── type.ts ├── parser.ts ├── cache.ts ├── db.ts ├── index.ts ├── api.ts ├── utils.ts ├── main.ts └── reader.ts ├── SOURCE.md ├── test ├── ruleParser.js └── parser.js ├── package.json ├── README.md ├── Algorithm.md ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /demo/01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zt8989/novel-reader/HEAD/demo/01.gif -------------------------------------------------------------------------------- /demo/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zt8989/novel-reader/HEAD/demo/02.jpg -------------------------------------------------------------------------------- /demo/03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zt8989/novel-reader/HEAD/demo/03.gif -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const scriptStartTag = "" 2 | export const scriptEndTag = "" 3 | export const newLineSplit = "\n" -------------------------------------------------------------------------------- /src/parser/index.ts: -------------------------------------------------------------------------------- 1 | export type ParserReturnType = { 2 | index: { next?: string, prev?: string} 3 | content: string 4 | title: string 5 | } 6 | 7 | export interface IParser { 8 | parseNovel: (url: string) => Promise 9 | } -------------------------------------------------------------------------------- /src/type.ts: -------------------------------------------------------------------------------- 1 | export type BookType = { 2 | name: string 3 | lastUrl: string 4 | } 5 | 6 | export type DataStoreDocumentType = { 7 | _id: string 8 | createdAt?: Date 9 | updatedAt?: Date 10 | } 11 | 12 | export type ConfigType = { 13 | lastUrl?: string 14 | lastLine?: number 15 | line?: number, 16 | baseUrl?: string, 17 | token?: string 18 | } -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import { GeneralParser } from './parser/general'; 2 | import { readSourcesSync } from './utils'; 3 | import { RuleParser } from './parser/rule'; 4 | import { IParser } from './parser/index'; 5 | 6 | export function parseNovel(url: string) { 7 | const sources = readSourcesSync() 8 | const source = sources.find(source => url.startsWith(source.bookSourceUrl)) 9 | let parser: IParser 10 | if (source) { 11 | parser = new RuleParser(source) 12 | } else { 13 | parser = new GeneralParser() 14 | } 15 | return parser.parseNovel(url) 16 | } 17 | -------------------------------------------------------------------------------- /src/cache.ts: -------------------------------------------------------------------------------- 1 | import { ParserReturnType } from "./parser/index"; 2 | 3 | export default class CacheManager { 4 | private cache: { url: string, value: Promise }[] = [] 5 | 6 | setItem(url: string, value: Promise){ 7 | this.cache.push({ url, value }) 8 | } 9 | 10 | getItem(url: string) { 11 | return this.cache.find(x => x.url === url)?.value || null 12 | } 13 | 14 | removeItem(url: string) { 15 | const index = this.cache.findIndex(x => x.url === url) 16 | if(index !== -1){ 17 | this.cache.splice(index, 1) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /demo/novel_sources.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "bookSourceUrl": "https://www.biquwu.cc/", 3 | "ruleBookContent": "next($('#content').text())", 4 | "rulePrevPage": "next($('a:contains(上一章)').attr('href'))", 5 | "ruleNextPage": "next($('a:contains(下一章)').attr('href'))", 6 | "ruleBookTitle": "next($('.content > h1').text())" 7 | }, 8 | { 9 | "bookSourceUrl": "http://m.suixkan.com/", 10 | "ruleBookContent": "class.con.0@textNodes&&class.con.1@textNodes&&class.con.1@textNodes", 11 | "rulePrevPage": "class.prevUrl@text", 12 | "ruleNextPage": "class.nextUrl@text", 13 | "ruleBookTitle": "class.section@tag.h2@text##\\(.*\\)" 14 | }] -------------------------------------------------------------------------------- /SOURCE.md: -------------------------------------------------------------------------------- 1 | # 源规则 2 | 3 | 目前支持两种采集源规则,一种简易规则,另一种js规则 4 | 5 | ## 源文件 6 | 7 | 将`novel_sources.json`文件放在home目录下的.nvrd目录, 8 | `novel_sources.json`是一个json数组 9 | 可以在`demo/novel_sources.json`下查看 10 | 11 | ### 规则字段说明 12 | 13 | * `bookSourceUrl` 书源地址 14 | * `ruleBookContent` 文本内容规则 15 | * `rulePrevPage` 上一章规则 16 | * `ruleNextPage` 下一章规则 17 | * `ruleBookTitle` 标题规则 18 | 19 | ## 简易规则 20 | 21 | 具体可以查看 22 | [阅读源规则](https://alanskycn.gitee.io/teachme/Rule/source.html) 23 | 24 | 只支持JSOUP之Default语法规则 25 | 26 | ## js规则 27 | 28 | 如果规则已``开头``结尾,则为js规则 29 | 30 | js接受三个参数`$`,`fetch`,`next`,返回结果必须调用`next` 31 | 32 | * `$` `cheerio`实例 33 | * `fetch` `node-fetch`实例 34 | * `next` 回调,进入下一步 35 | 36 | > 例如 37 | ```javascript 38 | next($('#content').text()) 39 | ``` -------------------------------------------------------------------------------- /test/ruleParser.js: -------------------------------------------------------------------------------- 1 | const parser = require('../dist/parser'); 2 | const { RuleParser } = require('../dist/parser/rule'); 3 | const utils = require('../dist/utils'); 4 | 5 | let urls = [ 6 | 'https://h.630book.com/book_155359/49485601.html', 7 | ] 8 | 9 | ;(async () => { 10 | for (let url of urls) { 11 | const p = parser.getParser(url) 12 | console.assert(p instanceof RuleParser, "rule 查找失败") 13 | let res = await p.parseNovel(url) 14 | let content = res.content 15 | const limit = 40 16 | const lines = utils.wordWrap(content, limit) 17 | console.assert(lines.length > 10, "解析失败") 18 | for(let line of lines) { 19 | console.assert(line.length <= limit, "error") 20 | console.log(line.length, line) 21 | } 22 | console.info(res) 23 | } 24 | })() 25 | 26 | -------------------------------------------------------------------------------- /src/db.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import Datastore from 'nedb-promises' 3 | import os from "os" 4 | 5 | function dbFactory(fileName: string){ 6 | return Datastore.create({ 7 | filename: join(os.homedir(), `/.nvrd/${fileName}.db`), 8 | timestampData: true, 9 | autoload: true 10 | }) 11 | } 12 | 13 | let novelsIns: Datastore | null = null 14 | 15 | let sourcesIns: Datastore | null = null 16 | 17 | let usersIns: Datastore | null = null 18 | 19 | export default { 20 | sources: () => { 21 | if (sourcesIns === null) { 22 | sourcesIns = dbFactory('sources') 23 | } 24 | return sourcesIns 25 | }, 26 | books: () => { 27 | if (novelsIns === null) { 28 | novelsIns = dbFactory('books') 29 | } 30 | return novelsIns 31 | }, 32 | users: () => { 33 | if (usersIns === null) { 34 | usersIns = dbFactory('users') 35 | } 36 | return usersIns 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /test/parser.js: -------------------------------------------------------------------------------- 1 | const parser = require('../dist/parser') 2 | const utils = require('../dist/utils'); 3 | 4 | let urls = [ 5 | // 'https://www.daxuetian.com/xs/2/2511.html', 6 | // 'http://www.yunxs.com/xintengjiejie/252578.html', 7 | // 'http://www.xbiquge.la/26/26874/13244872.html', 8 | // 'https://www.oldtimescc.cc/go/37136/19989330.html', 9 | // 'https://www.sikushu8.com/5/5867/919735.html', 10 | // 'https://www.2kzw.com/42/42542/38451420.html', 11 | // 'https://www.hgq26.com/107/107229/64932812.html', 12 | // 'http://m.suixkan.com/r/218571/218572.html', 13 | 'https://h.630book.com/book_155359/49485601.html' 14 | ] 15 | 16 | ;(async () => { 17 | for (let url of urls) { 18 | let res = await parser.getParser(url).parseNovel(url) 19 | let content = res.content 20 | const limit = 40 21 | const lines = utils.wordWrap(content, limit) 22 | console.assert(lines.length > 10, "解析失败") 23 | for(let line of lines) { 24 | console.assert(line.length <= limit, "error") 25 | console.log(line.length, line) 26 | } 27 | console.info(res.index) 28 | } 29 | })() 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@zt8989/novel-reader", 3 | "version": "1.1.3", 4 | "description": "terminal novel reader", 5 | "main": "dist/main.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "watch": "tsc -w" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:zt8989/novel-reader.git" 13 | }, 14 | "keywords": [ 15 | "terminal", 16 | "typescript", 17 | "novel reader" 18 | ], 19 | "author": "zhouteng", 20 | "license": "MIT", 21 | "bin": { 22 | "nvrd": "dist/index.js" 23 | }, 24 | "files": [ 25 | "dist" 26 | ], 27 | "dependencies": { 28 | "@types/cheerio": "^0.22.23", 29 | "@types/inquirer": "^7.3.1", 30 | "@types/mkdirp": "^1.0.1", 31 | "@types/node": "^10.12.10", 32 | "@types/node-fetch": "^2.5.7", 33 | "chalk": "^4.1.0", 34 | "cheerio": "^1.0.0-rc.3", 35 | "cli-cursor": "^3.1.0", 36 | "commander": "^2.19.0", 37 | "debug": "^4.1.0", 38 | "eol": "^0.9.1", 39 | "gbk.js": "^0.3.0", 40 | "inquirer": "^7.3.3", 41 | "mkdirp": "^1.0.4", 42 | "nedb-promises": "^4.1.0", 43 | "node-fetch": "^2.6.1", 44 | "rxjs": "^6.6.3", 45 | "typescript": "^4.1.3", 46 | "update-notifier": "^5.0.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Novel Reader 2 | 3 | 一款基于命令行跨平台文本小说阅读工具,996 与 10107 程序员摸鱼划水必备神器。 4 | 5 | ![demo1](./demo/01.gif) 6 | 7 | > 添加书籍 8 | 9 | ![demo2](./demo/02.jpg) 10 | 11 | > 实时更换书籍 12 | 13 | ![demo2](./demo/03.gif) 14 | 15 | ## 功能亮点 16 | 17 | - 使用 nodejs 开发 18 | - 软件运行于命令行,对 Vimer 友好,支持 Vim 方式的 Key Binding 进行翻页和跳转。 19 | - 支持 Boss Key,方便紧急情况下对界面隐藏和伪装。 20 | 21 | ## 安装步骤 22 | 23 | 其实,你和摸鱼之间,只有两步的距离: 24 | 25 | ```bash 26 | npm i -g @zt8989/novel-reader 27 | # -n 3表示显示3行,默认为1 28 | nvrd read <章节地址> -n 3 29 | 30 | nvrd book -l # 列出所有书籍 31 | nvrd book -a <书名> <章节地址> #添加书籍 32 | nvrd book -s <书名> <章节地址> #修改书籍地址 33 | nvrd book --remove <书名> #删除书籍 34 | nvrd book -r [书名] -n 3 # 如果不填写书名或者查找不到会出现下拉列表, -n 3表示显示3行,默认为1 35 | ``` 36 | 37 | ## 支持平台 38 | 39 | - Mac OS 40 | - Linux 41 | - Windows 42 | 43 | ## 快捷键说明 44 | 45 | - `ctrl+c` 退出程序 46 | - `j`, `ctrl+n`, ``显示下一行内容 47 | - `k` 或者 `ctrl+p`, ``显示上一行内容 48 | - `b` Boss Key,隐藏当前内容并显示伪装 Shell 提示符 49 | - `r` 重新获取当前页面 50 | - `l` 展示书源列表 51 | - `n` 下一章节 52 | - `m` 上一章节 53 | 54 | ## 提示 55 | 56 | - 只测试过笔趣阁相关网址,采集规则不一定适用其他网站 57 | 58 | ## 书源规则 59 | 60 | [书源规则](./SOURCE.md) 61 | 62 | ## 小说内容采集算法解释 63 | 64 | [小说解析算法解释](./Algorithm.md) 65 | 66 | ## Issue 与 PR 67 | 68 | 欢迎提交 issue 与 merge request。 69 | 70 | ## 协议 71 | 72 | 本开源软件基于[MIT](#)。 73 | 74 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import program from 'commander' 4 | import { readAction, bookAction, sourceAction, loginAction } from './main' 5 | const updateNotifier = require('update-notifier'); 6 | 7 | const pkg = require('../package.json'); 8 | 9 | updateNotifier({ pkg }).notify(); 10 | 11 | program 12 | .version(pkg.version) 13 | 14 | program.command("read ") 15 | .description("read from url") 16 | .option("-n [line]", "set the line number") 17 | .action(readAction) 18 | 19 | program.command("book") 20 | .description("book manage") 21 | .option("-l, --list", "list books") 22 | .option("-r, --read [name]", "list books") 23 | .option("-a, --add ", "add a book") 24 | .option("--remove ", "remove a book") 25 | .option("-s, --set-url ") 26 | .option("-n [line]", "set the line number") 27 | .option("-u, --up", "将数据上传到服务器") 28 | .option("-d, --down", "将数据下载到本地") 29 | .action(bookAction) 30 | 31 | program.command("source") 32 | .description("source manage") 33 | .option("-u, --up", "将数据上传到服务器") 34 | .option("-d, --down", "将数据下载到本地") 35 | .action(sourceAction) 36 | 37 | program.command("login") 38 | .description("login") 39 | .option("-a, --api", "set api url") 40 | .action(loginAction) 41 | 42 | program.parse(process.argv) -------------------------------------------------------------------------------- /src/parser/rule.ts: -------------------------------------------------------------------------------- 1 | import cheerio from 'cheerio'; 2 | import { SourceType } from '../utils'; 3 | import { parseRule } from './parser' 4 | import { GeneralParser } from './general'; 5 | 6 | export class RuleParser extends GeneralParser { 7 | private source: SourceType 8 | 9 | constructor(source: SourceType) { 10 | super() 11 | this.source = source 12 | } 13 | 14 | /** 15 | * 解析内容 16 | * @param doc 17 | */ 18 | protected async parseContent(doc: string) { 19 | if(this.source.ruleBookContent) { 20 | const $ = cheerio.load(doc, { decodeEntities: false }) 21 | return parseRule(this.source.ruleBookContent, $('body'), $) as string 22 | } else { 23 | return super.parseContent(doc) 24 | } 25 | } 26 | 27 | protected async parsePrevPage($: cheerio.Root) { 28 | if(this.source.rulePrevPage) { 29 | return parseRule(this.source.rulePrevPage, $('body'), $) as string 30 | } else { 31 | return super.parsePrevPage($) 32 | } 33 | } 34 | 35 | protected async parseNextPage($: cheerio.Root){ 36 | if(this.source.ruleNextPage) { 37 | return parseRule(this.source.ruleNextPage, $('body'), $) as string 38 | } else { 39 | return super.parseNextPage($) 40 | } 41 | } 42 | 43 | protected async parseTitle(doc: string) { 44 | if(this.source.ruleBookTitle) { 45 | const $ = cheerio.load(doc, { decodeEntities: false }); 46 | return parseRule(this.source.ruleBookTitle, $('body'), $) as string 47 | } else { 48 | return super.parseTitle(doc) 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /Algorithm.md: -------------------------------------------------------------------------------- 1 | ## 小说解析算法解释 2 | 3 | ```typescript 4 | protected async parseContent(doc: string) { 5 | const $ = cheerio.load(doc, { decodeEntities: false }); 6 | 7 | function parseChildren(parent: cheerio.Cheerio, size: number, slope: number, variance: number): string { 8 | const children = parent.children() 9 | if(children.length === 1) { 10 | return parseChildren(children, size, slope, variance) 11 | } 12 | let maxChildren: { element: cheerio.Cheerio; size: number; slope: number; }; 13 | const sizeList: number[] = []; 14 | children.each((index, element) => { 15 | const wrapperElement = $(element); 16 | const length = wrapperElement.text().length; 17 | sizeList.push(length); 18 | const tempSlop = (size - length) / size; 19 | if (!maxChildren) { 20 | maxChildren = { 21 | element: wrapperElement, 22 | size: length, 23 | slope: tempSlop 24 | }; 25 | } else if (tempSlop < (maxChildren.slope)) { // 保存最小斜率 26 | maxChildren = { 27 | element: wrapperElement, 28 | size: length, 29 | slope: tempSlop 30 | }; 31 | } 32 | }); 33 | // debug(sizeList) 34 | let avg = sizeList.reduce((sum, x) => sum + x, 0) / sizeList.length; 35 | // 计算方差 36 | let tempVariance = Math.sqrt(sizeList.reduce((sum, x) => sum + (x - avg) ** 2, 0) / sizeList.length); 37 | // debug(avg, tempVariance) 38 | // @ts-ignore 39 | if (maxChildren) { 40 | // console.log('---', maxChildren.element.html(), maxChildren.element[0].name) 41 | debug(maxChildren.element.text(), maxChildren.slope, slope, tempVariance, variance) 42 | if (tempVariance > 100) { 43 | return parseChildren(maxChildren.element, maxChildren.size, maxChildren.slope, tempVariance); 44 | } else { 45 | return handleTextNodes(parent) 46 | } 47 | } else { 48 | return ""; 49 | } 50 | } 51 | 52 | return parseChildren($('body'), $('body').text().length, 1, $('body').text().length); 53 | } 54 | 55 | ``` 56 | 57 | 父元素的文字长度为`size`。 58 | 遍历每个子元素,取得文字长度`length`。 59 | 如果`(size - length) / size`值最小, 既斜率最小, 则当前子元素文字最多。 60 | 计算方差`tempVariance`, 如果方差值>`100`(经验值),则表示所有子元素的差异很大,取最大的子元素重新计算。 61 | 否则表示这个父元素所有的子元素差异很小(如果是小说的话,每个段落不会差很多),将父元素作为正文返回。 62 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import fetch, { RequestInit } from 'node-fetch' 2 | import { BookType } from './type' 3 | import { readConfig, SourceType } from './utils' 4 | 5 | const request = async (url: string, options: RequestInit, auth = true) => { 6 | const config = await readConfig() 7 | if (!config.baseUrl || !config.baseUrl.startsWith("http")) { 8 | throw new Error("请使用nvrd login -a 登录") 9 | } 10 | if (auth && !config.token) { 11 | throw new Error("请使用nvrd login登录") 12 | } 13 | if(auth) { 14 | if (!options.headers) { 15 | options.headers = {} 16 | } 17 | // @ts-ignore 18 | options.headers['Authorization'] = 'Bearer ' + config.token 19 | } 20 | // console.log(options) 21 | // console.log(options.method || "get", url, options) 22 | 23 | const baseUrl = config.baseUrl 24 | const res = await fetch(baseUrl + url, options).then(res => res.json()) 25 | if(res.code === 200) { 26 | return res.data 27 | } else { 28 | const error = new Error(res.message) 29 | // @ts-ignore 30 | error.code = res.code 31 | throw error 32 | } 33 | } 34 | 35 | export const login = (data: { username: string, password: string }) => { 36 | return request('/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)}, false) 37 | } 38 | 39 | export const checkLogin = () => { 40 | return request('/status', {}) 41 | } 42 | 43 | export const getBooks = (): Promise => { 44 | return request('/books/list', {}) 45 | } 46 | 47 | export const setBooks = (data: BookType[]) => { 48 | return request('/books/list', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) 49 | } 50 | 51 | export const syncBooks = (data: BookType[]) => { 52 | return request('/books/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) 53 | } 54 | 55 | export const setBook = (data: BookType) => { 56 | return request('/books', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) 57 | } 58 | 59 | export const removeBook = (data: { name: string }) => { 60 | return request('/books', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) 61 | } 62 | 63 | export const getSources = (): Promise => { 64 | return request('/sources', {} ) 65 | } 66 | 67 | export const setSources = (data: SourceType[]) => { 68 | return request('/sources', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)}) 69 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import os from "os" 2 | import path from "path" 3 | import fs from 'fs' 4 | import { promisify } from 'util' 5 | import { newLineSplit } from "./constants" 6 | import mkdirp from 'mkdirp' 7 | import { ConfigType } from "./type" 8 | 9 | const writeFile = promisify(fs.writeFile) 10 | const readFile = promisify(fs.readFile) 11 | 12 | const nvrdDir = path.resolve(os.homedir(), ".nvrd") 13 | 14 | const historyFile = path.resolve(nvrdDir, ".novel_history") 15 | 16 | const sourcesFile = path.resolve(nvrdDir, "novel_sources.json") 17 | 18 | export type SourceType = { 19 | bookSourceUrl: string, 20 | ruleBookContent: string, 21 | ruleNextPage: string 22 | rulePrevPage: string 23 | ruleBookTitle: string 24 | } 25 | 26 | export async function readConfig(): Promise { 27 | if (!fs.existsSync(historyFile)) { 28 | await mkdirp(nvrdDir) 29 | await writeConfig({}) 30 | return {} 31 | } else { 32 | try { 33 | const json = await readFile(historyFile, { encoding: 'utf-8'}) 34 | return JSON.parse(json) 35 | } catch (e){ 36 | return {} 37 | } 38 | } 39 | } 40 | 41 | export async function writeConfig(config: ConfigType) { 42 | return await writeFile(historyFile, JSON.stringify(config), { encoding: 'utf-8'}) 43 | } 44 | 45 | export function writeConfigSync(config: ConfigType) { 46 | return fs.writeFileSync(historyFile, JSON.stringify(config), { encoding: 'utf-8'}) 47 | } 48 | 49 | export function readSourcesSync(): SourceType[] { 50 | if (!fs.existsSync(sourcesFile)) { 51 | return [] 52 | } else { 53 | try { 54 | const json = fs.readFileSync(sourcesFile, { encoding: 'utf-8'}) 55 | return JSON.parse(json) 56 | } catch (e){ 57 | return [] 58 | } 59 | } 60 | } 61 | 62 | export async function readSources(): Promise { 63 | if (!fs.existsSync(sourcesFile)) { 64 | return [] 65 | } else { 66 | try { 67 | const json = await readFile(sourcesFile, { encoding: 'utf-8'}) 68 | return JSON.parse(json) 69 | } catch (e){ 70 | return [] 71 | } 72 | } 73 | } 74 | 75 | export async function writeSources(sources: SourceType[]) { 76 | return await writeFile(sourcesFile, JSON.stringify(sources), { encoding: 'utf-8'}) 77 | } 78 | 79 | export function wordWrap(str: string, maxWidth: number) { 80 | const lines = str.split(newLineSplit) 81 | const newLines: string[] = [] 82 | for (let line of lines) { 83 | if(line.length <= maxWidth) { 84 | if(line.trim()){ 85 | newLines.push(line) 86 | } 87 | } else { 88 | do { 89 | newLines.push(line.slice(0, maxWidth)) 90 | line = line.slice(maxWidth) 91 | } while(line.length > 0) 92 | } 93 | } 94 | 95 | // console.log(newLines) 96 | 97 | return newLines 98 | } -------------------------------------------------------------------------------- /src/parser/general.ts: -------------------------------------------------------------------------------- 1 | import { IParser } from './index'; 2 | import fetch from 'node-fetch'; 3 | import cheerio from 'cheerio'; 4 | import { handleTextNodes } from './parser'; 5 | const gbk = require('gbk.js'); 6 | const debug = require('debug')('parser') 7 | 8 | export class GeneralParser implements IParser { 9 | 10 | protected async fetchUrl(url: string) { 11 | const headers = { 12 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36' 13 | }; 14 | 15 | return fetch(url, { 16 | headers 17 | }) 18 | .then(res => res.buffer()) 19 | .then(res => { 20 | if (res.includes('gbk') || res.includes('gb2312')) { 21 | return gbk.decode(res); 22 | } 23 | return res.toString('utf-8'); 24 | }); 25 | } 26 | /** 27 | * 解析内容 28 | * @param doc 29 | */ 30 | protected async parseContent(doc: string) { 31 | const $ = cheerio.load(doc, { decodeEntities: false }); 32 | 33 | function parseChildren(parent: cheerio.Cheerio, size: number, slope: number, variance: number): string { 34 | const children = parent.children() 35 | if(children.length === 1) { 36 | return parseChildren(children, size, slope, variance) 37 | } 38 | let maxChildren: { element: cheerio.Cheerio; size: number; slope: number; }; 39 | const sizeList: number[] = []; 40 | children.each((index, element) => { 41 | const wrapperElement = $(element); 42 | const length = wrapperElement.text().length; 43 | sizeList.push(length); 44 | const tempSlop = (size - length) / size; 45 | if (!maxChildren) { 46 | maxChildren = { 47 | element: wrapperElement, 48 | size: length, 49 | slope: tempSlop 50 | }; 51 | } else if (tempSlop < (maxChildren.slope)) { // 保存最小斜率 52 | maxChildren = { 53 | element: wrapperElement, 54 | size: length, 55 | slope: tempSlop 56 | }; 57 | } 58 | }); 59 | // debug(sizeList) 60 | let avg = sizeList.reduce((sum, x) => sum + x, 0) / sizeList.length; 61 | // 计算方差 62 | let tempVariance = Math.sqrt(sizeList.reduce((sum, x) => sum + (x - avg) ** 2, 0) / sizeList.length); 63 | // debug(avg, tempVariance) 64 | // @ts-ignore 65 | if (maxChildren) { 66 | // console.log('---', maxChildren.element.html(), maxChildren.element[0].name) 67 | debug(maxChildren.element.text(), maxChildren.slope, slope, tempVariance, variance) 68 | if (tempVariance > 100) { 69 | return parseChildren(maxChildren.element, maxChildren.size, maxChildren.slope, tempVariance); 70 | } else { 71 | return handleTextNodes(parent) 72 | } 73 | } else { 74 | return ""; 75 | } 76 | } 77 | 78 | return parseChildren($('body'), $('body').text().length, 1, $('body').text().length); 79 | } 80 | 81 | protected async parseIndexChapter(doc: string) { 82 | const $ = cheerio.load(doc, { decodeEntities: false }); 83 | 84 | const nextHref = await this.parseNextPage($) 85 | const prevHref = await this.parsePrevPage($) 86 | return { next: nextHref, prev: prevHref }; 87 | } 88 | 89 | protected async parsePrevPage($: cheerio.Root) { 90 | const prevHref = $('body').find('a:contains("上一章")').attr("href") || $('body').find('a:contains("上一页")').attr("href"); 91 | return prevHref 92 | } 93 | 94 | protected async parseNextPage($: cheerio.Root){ 95 | const nextHref = $('body').find('a:contains("下一章")').attr("href") || $('body').find('a:contains("下一页")').attr("href"); 96 | return nextHref 97 | } 98 | 99 | protected async parseTitle(doc: string) { 100 | const $ = cheerio.load(doc, { decodeEntities: false }); 101 | 102 | return $('title').text(); 103 | } 104 | 105 | parseNovel = async (url: string) => { 106 | // console.log('fetching...', url) 107 | const doc = await this.fetchUrl(url); 108 | const content = await this.parseContent(doc); 109 | const index = await this.parseIndexChapter(doc); 110 | const title = await this.parseTitle(doc); 111 | return { index, content, title }; 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/parser/parser.ts: -------------------------------------------------------------------------------- 1 | import { scriptEndTag, scriptStartTag, newLineSplit } from "../constants" 2 | import cheerio from 'cheerio' 3 | import fetch from 'node-fetch' 4 | 5 | 6 | function parseRule (rule: string, $: cheerio.Cheerio, root: cheerio.Root): cheerio.Cheerio | string | Promise { 7 | if (!rule) return '' 8 | 9 | if (rule.startsWith(scriptStartTag) && rule.endsWith(scriptEndTag)) { 10 | return parseScript(rule.slice(scriptStartTag.length, rule.length - scriptEndTag.length), root) 11 | } 12 | 13 | const index = rule.indexOf("##") 14 | const parseRules = index === - 1 ? rule : rule.slice(0, index) 15 | const regexRule = index === - 1 ? "": rule.slice(index + 2) 16 | 17 | if(parseRules.includes("||") && parseRules.includes("&&")) { 18 | throw new Error("不允许同时存在&&和||规则") 19 | } 20 | 21 | if (rule.includes("&&")) { 22 | const parseRuleListAnd = parseRules.split("&&") 23 | const temp: string[] = [] 24 | for (let parseRule of parseRuleListAnd) { 25 | const rules = parseRule.split('@') 26 | let ret: ReturnType = $ 27 | rules.forEach((r) => { 28 | if(ret && typeof ret !== "string") { 29 | ret = simpleParser(r, ret) 30 | } 31 | }) 32 | if (regexRule) { 33 | const regexRules = regexRule.split('|') 34 | regexRules.forEach(r => { 35 | if(typeof ret === 'string'){ 36 | ret = parseRegex(ret, r) 37 | } 38 | }) 39 | } 40 | if(ret && ret.length > 0) { 41 | temp.push(ret as any as string) 42 | } 43 | } 44 | return temp.join(newLineSplit) 45 | } else { 46 | const parseRuleListOr = parseRules.split("||") 47 | for (let parseRule of parseRuleListOr) { 48 | const rules = parseRule.split('@') 49 | let ret: ReturnType = $ 50 | rules.forEach((r) => { 51 | if(ret && typeof ret !== "string") { 52 | ret = simpleParser(r, ret) 53 | } 54 | }) 55 | if (regexRule) { 56 | const regexRules = regexRule.split('|') 57 | regexRules.forEach(r => { 58 | if(typeof ret === 'string'){ 59 | ret = parseRegex(ret, r) 60 | } 61 | }) 62 | } 63 | if(ret && ret.length > 0) { 64 | return ret as any as cheerio.Cheerio | string 65 | } 66 | } 67 | } 68 | 69 | return '' 70 | } 71 | 72 | function parseScript(script: string, $: cheerio.Root): Promise { 73 | const func = new Function("$", "fetch", "next", script) 74 | return new Promise((resovle) => func($, fetch, resovle)) 75 | } 76 | 77 | function parseRegex (text: string, regex: string) { 78 | const reg = new RegExp(regex, "g") 79 | return text.replace(reg, '') 80 | } 81 | 82 | function selectorParse (rule: string[], $: cheerio.Cheerio, prefix = '') { 83 | const [tag, indexs] = rule[1].split('!') 84 | let indexList: number[] = [] 85 | if(indexs) { 86 | indexList = indexs.split(":").map(x => Number(x)); 87 | } 88 | 89 | $ = $.find(prefix + tag) 90 | if (indexs) { 91 | indexList = indexList.map(val => { 92 | if(val >= 0) { 93 | return val 94 | } else { 95 | return $.length + val 96 | } 97 | }) 98 | $ = $.filter(i => { 99 | return !indexList.includes(i) 100 | }) 101 | } else if (rule[2]) { 102 | $ = $.eq(Number(rule[2])) 103 | } 104 | return $ 105 | } 106 | 107 | function simpleParser (rule: string, $: cheerio.Cheerio) { 108 | const rules = rule.split('.') 109 | let ret: cheerio.Cheerio | string | undefined | null 110 | switch (rules[0]) { 111 | case 'class': 112 | ret = selectorParse(rules, $, '.') 113 | break 114 | case 'id': 115 | ret = selectorParse(rules, $, '#') 116 | break 117 | case 'tag': 118 | ret = selectorParse(rules, $, '') 119 | break 120 | } 121 | if('cheerio' in $) { 122 | switch (rules[0]) { 123 | case 'class': 124 | case 'id': 125 | case 'tag': 126 | break 127 | case 'text': 128 | ret = $.text() 129 | break 130 | case 'href': 131 | case 'src': 132 | ret = $.attr(rules[0]) 133 | break 134 | case 'html': 135 | ret = $.html() 136 | break 137 | case 'textNodes': 138 | { 139 | ret = handleTextNodes($) 140 | } 141 | break 142 | default: 143 | throw new Error('unkonw rule: ' + rule) 144 | } 145 | } 146 | return ret 147 | } 148 | 149 | function handleTextNodes(parent: cheerio.Cheerio){ 150 | const temp: string[] = []; 151 | parent.contents().each((index, element) => { 152 | if (element.type === 'text') { 153 | const content = String.prototype.trim.apply(element.data); 154 | content && (temp.push(' ' + content)); 155 | } else if(element.type === 'tag' && ["p"].includes(element.name)) { 156 | const content = String.prototype.trim.apply(cheerio(element).text()); 157 | content && (temp.push(' ' + content)); 158 | } 159 | }); 160 | return temp.join(newLineSplit); 161 | } 162 | 163 | export { 164 | handleTextNodes, 165 | parseRule 166 | } 167 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | "types": [], /* Type declaration files to be included in compilation. */ 45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | "skipLibCheck": true 59 | }, 60 | "include": [ 61 | "src/*" 62 | ], 63 | "exclude": [ 64 | "node_modules", 65 | "./node_modules", 66 | "./node_modules/*", 67 | "./node_modules/@types/node/index.d.ts", 68 | ] 69 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Reader from "./reader" 2 | import inquirer from "inquirer"; 3 | import { readConfig, readSources, writeConfig, writeSources } from "./utils"; 4 | import db from './db' 5 | import { BookType, ConfigType } from "./type"; 6 | import { checkLogin, getBooks, getSources, login, removeBook, setBooks, setSources, syncBooks } from "./api"; 7 | 8 | 9 | 10 | export async function readAction(...rest: any[]) { 11 | let argv = rest[rest.length - 1] 12 | const url = rest[0] || "" 13 | if(url.startsWith("http")) { 14 | let line = 1 15 | if(argv.N > 1) { 16 | line = Number(argv.N) 17 | } 18 | 19 | return await read(url, line) 20 | } else { 21 | console.error("请输入正确的网址") 22 | } 23 | } 24 | 25 | async function read(url: string, line: number = 1, skipConfig = false) { 26 | let config = await readConfig() 27 | 28 | const questions: any[] = [] 29 | 30 | // if (config.lastUrl) { 31 | // questions.push({ 32 | // type: "confirm", 33 | // name: "continue", 34 | // message: "是否继续上次的阅读?" 35 | // }) 36 | // } 37 | 38 | questions.push({ 39 | type: "novel", 40 | name: "read novel", 41 | url, 42 | config: config, 43 | line: line ? line : config.line ? config.line : 1 44 | }) 45 | 46 | inquirer.registerPrompt("novel", Reader) 47 | return inquirer.prompt(questions) 48 | } 49 | 50 | async function readFromBook(config: ConfigType, book: BookType, line: number = 1) { 51 | const questions: any[] = [] 52 | 53 | // if (config.lastUrl) { 54 | // questions.push({ 55 | // type: "confirm", 56 | // name: "continue", 57 | // message: "是否继续上次的阅读?" 58 | // }) 59 | // } 60 | 61 | const { lastUrl, ...rest } = config 62 | questions.push({ 63 | type: "novel", 64 | name: "read novel", 65 | url: book ? book.lastUrl : "", 66 | config: rest, 67 | book: book, 68 | line: line ? line : config.line ? config.line : 1 69 | }) 70 | 71 | inquirer.registerPrompt("novel", Reader) 72 | return inquirer.prompt(questions) 73 | } 74 | 75 | export async function bookAction(...rest: any[]){ 76 | let argv = rest[rest.length - 1] 77 | if(argv.list){ 78 | let list = await db.books().find({}, { name: 1, lastUrl: 1, updatedAt: 1 }) 79 | let newList = list.map(x => { 80 | const { _id, ...rest } = x 81 | return rest 82 | }) 83 | console.table(newList) 84 | return 85 | } 86 | 87 | if(argv.add || argv['setUrl']) { 88 | const name = argv.add || argv['setUrl'] 89 | if(rest.length !== 2) { 90 | console.error("请输入正确的参数") 91 | return 92 | } 93 | const url: string = rest[0] || "" 94 | if(!url.startsWith("http")){ 95 | console.error("请输入正确的网址") 96 | return 97 | } 98 | let result = await db.books().findOne({ name: name }) 99 | if (result) { 100 | await db.books().update({ _id: result._id }, { $set: { lastUrl: url } }) 101 | } else { 102 | await db.books().insert({ name: name, lastUrl: url }) 103 | } 104 | console.log(argv.add ? "添加成功":"更新成功") 105 | return 106 | } 107 | 108 | if(argv.read) { 109 | let line = 1 110 | if(argv.N > 1) { 111 | line = Number(argv.N) 112 | } 113 | let config = await readConfig() 114 | let books = await db.books().find({}) 115 | if (config.token) { 116 | await checkLogin().catch(e => { 117 | if(e.code === 401) { 118 | delete config.token 119 | e.message = "登录已失效" 120 | } 121 | throw e 122 | }) 123 | let data = await syncBooks(books as any) as (BookType & { _id: string, deleted: boolean, updatedAt: string })[] 124 | 125 | // 过滤出所有已删除的 126 | let deleteds = data.filter(x => x.deleted) 127 | 128 | // 过滤出所有未删除的 129 | data = data.filter(x => !x.deleted) 130 | 131 | // 需要添加的 132 | const inserts = data.filter(x => !books.some(b => b.name === x.name)) 133 | 134 | // 需要更新的 135 | const updates = data.filter(x => { 136 | const book = books.find(b => b.name === x.name) 137 | return book && (new Date(x.updatedAt).getTime()) > (book.updatedAt?.getTime() ?? 0) 138 | }) 139 | 140 | // const deletes = books.filter(b => !data.some(x => x.name === b.name)) 141 | 142 | for (let book of inserts) { 143 | const { name, lastUrl } = book 144 | await db.books().insert({ name, lastUrl }) 145 | } 146 | 147 | 148 | for (let book of updates) { 149 | const { _id, name, updatedAt, ...rest } = book 150 | await db.books().update({ name }, { $set: rest }) 151 | } 152 | 153 | for(let book of deleteds) { 154 | await db.books().remove({ name: book.name }, {}) 155 | } 156 | 157 | } 158 | 159 | const result = books.find(x => x.name == argv.read) as BookType 160 | 161 | await readFromBook(config, result, line) 162 | return 163 | } 164 | 165 | if(argv.remove) { 166 | await db.books().remove({ name: argv.remove }, {}) 167 | let config = await readConfig() 168 | if (config.token) { 169 | await checkLogin().catch(e => { 170 | if(e.code === 401) { 171 | delete config.token 172 | e.message = "登录已失效" 173 | } 174 | throw e 175 | }) 176 | await removeBook({ name: argv.remove }) 177 | } 178 | console.error("删除成功") 179 | return 180 | } 181 | 182 | if(argv.up) { 183 | const books = await db.books().find({}) as BookType[] 184 | await setBooks(books) 185 | console.log("上传成功") 186 | return 187 | } 188 | 189 | if(argv.down) { 190 | const books = await getBooks() 191 | await db.books().remove({}, { multi: true }) 192 | for(let book of books) { 193 | await db.books().insert(book) 194 | } 195 | console.log("下载成功") 196 | return 197 | } 198 | 199 | console.error("请输入正确的参数") 200 | } 201 | 202 | export async function sourceAction(...rest: any[]){ 203 | let argv = rest[rest.length - 1] 204 | 205 | if(argv.up) { 206 | const sources = await readSources() 207 | await setSources(sources) 208 | console.log("上传成功") 209 | return 210 | } 211 | 212 | if(argv.down) { 213 | const sources = await getSources() 214 | await writeSources(sources) 215 | console.log("下载成功") 216 | return 217 | } 218 | 219 | } 220 | 221 | export async function configAction(...rest: any[]) { 222 | let argv = rest[rest.length - 1] 223 | if(argv.api && rest[0] && rest[0].startsWith('http')) { 224 | const config = await readConfig() 225 | config.baseUrl = rest[0] 226 | await writeConfig(config) 227 | console.log("保存api成功", rest[0]) 228 | return 229 | } 230 | 231 | if(argv.up) { 232 | const books = await db.books().find({}) as BookType[] 233 | await setBooks(books) 234 | const sources = await readSources() 235 | const res = await setSources(sources) 236 | console.log("上传成功", res) 237 | return 238 | } 239 | 240 | if(argv.down) { 241 | const books = await getBooks() 242 | await db.books().remove({}, { multi: true }) 243 | for(let book of books) { 244 | await db.books().insert(book) 245 | } 246 | const sources = await getSources() 247 | await writeSources(sources) 248 | console.log("下载成功") 249 | return 250 | } 251 | 252 | } 253 | 254 | export async function loginAction(...rest: any[]) { 255 | let argv = rest[rest.length - 1] 256 | const config = await readConfig() 257 | 258 | if(argv.api && rest[0] && rest[0].startsWith('http')) { 259 | config.baseUrl = rest[0] 260 | await writeConfig(config) 261 | } 262 | 263 | if(!config.baseUrl) { 264 | console.error("请使用nvrd login -a 登录") 265 | return 266 | } 267 | 268 | inquirer.prompt([ 269 | { type: 'input', 270 | name: 'username', 271 | validate: val => !!String.prototype.trim.apply(val) 272 | }, 273 | { type: 'password', 274 | name: 'password', 275 | validate: val => !!String.prototype.trim.apply(val) 276 | }, 277 | ]).then(async (answers: { username: string, password: string }) => { 278 | const token = await login(answers) 279 | const config = await readConfig() 280 | config.token = token 281 | await writeConfig(config) 282 | console.log("登录成功") 283 | }).catch(e => { 284 | console.error(e.message ? e.message : e) 285 | }) 286 | } -------------------------------------------------------------------------------- /src/reader.ts: -------------------------------------------------------------------------------- 1 | import { ReadLine } from "readline"; 2 | // import eol from 'eol' 3 | import Base from "inquirer/lib/prompts/base"; 4 | import inquirer from "inquirer"; 5 | import observe from "inquirer/lib/utils/events"; 6 | import cliCursor from 'cli-cursor' 7 | import chalk from 'chalk' 8 | import { filter, share } from 'rxjs/operators' 9 | import { parseNovel } from "./parser"; 10 | import { writeConfigSync, wordWrap } from "./utils"; 11 | import ConfirmPrompt from "inquirer/lib/prompts/confirm"; 12 | import { newLineSplit } from "./constants"; 13 | import { BookType, ConfigType, DataStoreDocumentType } from "./type"; 14 | import db from "./db"; 15 | import ListPrompt from "inquirer/lib/prompts/list"; 16 | import { setBook } from "./api"; 17 | import { ParserReturnType } from "./parser/index"; 18 | import CacheManager from "./cache"; 19 | 20 | const HISTORY_STACK = 5 21 | export default class Reader extends Base{ 22 | /** 阅读滚动行数 */ 23 | private count = 0 24 | /** 显示行数 */ 25 | private line = 1 26 | private lineNumber = 40 27 | private lines: string[] = [] 28 | private url: string = "" 29 | private title: string = "" 30 | private loading = true 31 | private boss = false 32 | private config: ConfigType 33 | // @ts-ignore 34 | private firstRun = true 35 | private book: BookType & DataStoreDocumentType 36 | private cacheManageer: CacheManager; 37 | 38 | private list: (string | undefined)[] = new Array(HISTORY_STACK) 39 | private currentPos = -1 40 | 41 | setNext(url?: string){ 42 | this.list[(this.currentPos + 1) % HISTORY_STACK] = url 43 | } 44 | 45 | next(){ 46 | this.currentPos+=1 47 | return this.list[this.currentPos % HISTORY_STACK] 48 | } 49 | 50 | prev(){ 51 | if(this.currentPos > 0){ 52 | this.currentPos -= 1 53 | return this.list[this.currentPos % HISTORY_STACK] 54 | } 55 | } 56 | 57 | constructor(question: any, readLine: ReadLine, answers: inquirer.Answers) { 58 | super(question, readLine, answers) 59 | 60 | this.url = question.url 61 | this.line = question.line 62 | this.config = question.config 63 | // console.log(this.config) 64 | this.book = question.book 65 | 66 | this.cacheManageer = new CacheManager() 67 | 68 | // if (answers.continue === true && this.config.lastUrl) { 69 | // this.url = this.config.lastUrl 70 | // } 71 | } 72 | 73 | getConfirmPrompt() { 74 | const prompt = new ConfirmPrompt({ 75 | type: "confirm", 76 | name: "continue", 77 | message: "是否继续上次的阅读?" 78 | }, this.rl, {}); 79 | 80 | (prompt as any).onEnd = function onEnd(this: any, input: string) { 81 | this.status = 'answered'; 82 | 83 | var output = this.opt.filter(input); 84 | // this.render(output); 85 | 86 | this.done(output); 87 | }; 88 | 89 | (prompt as any).screen = this.screen 90 | 91 | return prompt 92 | } 93 | 94 | async getListPrompt() { 95 | const list = await db.books().find({}) 96 | const prompt = new ListPrompt({ 97 | type: "list", 98 | name: "chooseBook", 99 | message: "选择你要阅读的书籍?", 100 | choices: list as any 101 | }, this.rl, {}); 102 | 103 | (prompt as any).onSubmit = function onSubmit(this: any, value: any) { 104 | this.status = 'answered'; 105 | this.done(value); 106 | }; 107 | 108 | (prompt as any).screen = this.screen 109 | 110 | return prompt 111 | } 112 | 113 | /** 114 | * Start the Inquiry session 115 | * @param {Function} cb Callback when prompt is done 116 | * @return {this} 117 | */ 118 | async _run(cb: Function) { 119 | 120 | var events = observe(this.rl); 121 | 122 | events.normalizedUpKey 123 | .forEach(this.onUpKey.bind(this)); 124 | events.normalizedDownKey 125 | .forEach(this.onDownKey.bind(this)); 126 | 127 | events.keypress.pipe( 128 | filter( 129 | ({ key }) => 130 | key.name === 'n' 131 | ), 132 | share() 133 | ).forEach(this.onPageNext.bind(this)) 134 | 135 | events.keypress.pipe( 136 | filter( 137 | ({ key }) => 138 | key.name === 'm' 139 | ), 140 | share() 141 | ).forEach(this.onPageUp.bind(this)) 142 | 143 | events.keypress.pipe( 144 | filter(({ key }) => key && key.name === 'b'), 145 | share() 146 | ).forEach(this.onBossKey.bind(this)) 147 | 148 | events.keypress.pipe( 149 | filter(({ key }) => key && key.name === 'r'), 150 | share() 151 | ).forEach(this.onRefresh.bind(this)) 152 | 153 | events.keypress.pipe( 154 | filter(({ key }) => key && key.name === 'l'), 155 | share() 156 | ).forEach(this.onListBook.bind(this)) 157 | 158 | const next = (cont: Boolean = false) => { 159 | if(cont && this.config.lastUrl) { 160 | this.url = this.config.lastUrl 161 | } 162 | // Init the prompt 163 | cliCursor.hide(); 164 | this.setNext(this.url) 165 | this._read(this.next()!) 166 | } 167 | if (this.firstRun && this.config.lastUrl) { 168 | this.getConfirmPrompt().run().then((res: boolean) => { 169 | // Init the prompt 170 | next(res) 171 | }) 172 | } else if (this.book) { 173 | next() 174 | } else { 175 | this.onListBook() 176 | } 177 | this.firstRun = false 178 | return this; 179 | } 180 | 181 | onListBook(){ 182 | this.getListPrompt().then(res => res.run()).then(res => { 183 | return db.books().findOne({ name: res }) 184 | }).then((res: any) => { 185 | if(res) { 186 | this.book = res as any 187 | return this._read(res.lastUrl) 188 | } else { 189 | console.error("未找到对应的书") 190 | process.exit() 191 | } 192 | }) 193 | } 194 | 195 | onPageUp(){ 196 | const prev = this.prev() 197 | if(prev){ 198 | if(prev.startsWith("http")) { 199 | this._read(prev) 200 | } else { 201 | const urlObj = new URL(prev, this.url) 202 | this._read(urlObj.href) 203 | } 204 | } else { 205 | this.title = '没有上一页了' 206 | this.render() 207 | } 208 | } 209 | 210 | onUpKey() { 211 | if(this.loading) return 212 | if(this.isHead()) { 213 | this.onPageUp() 214 | } else { 215 | if(this.count < this.line) { 216 | this.count = 0 217 | } else { 218 | this.count -= this.line 219 | } 220 | this.render() 221 | } 222 | } 223 | 224 | getAbsoluteUrl(url: string) { 225 | if(url.startsWith("http")) { 226 | return url 227 | } else { 228 | const urlObj = new URL(url, this.url) 229 | return urlObj.href 230 | } 231 | } 232 | 233 | onPageNext(){ 234 | const next = this.next() 235 | if(next){ 236 | this._read(this.getAbsoluteUrl(next)) 237 | } else { 238 | this.title = '没有下一页了' 239 | this.render() 240 | } 241 | } 242 | 243 | onDownKey() { 244 | if(this.loading) return 245 | if(!this.isEnd()) { 246 | this.count += this.line 247 | this.render() 248 | } else { 249 | this.onPageNext() 250 | } 251 | } 252 | 253 | onBossKey() { 254 | this.boss = !this.boss 255 | if(this.boss) { 256 | cliCursor.show() 257 | } else { 258 | cliCursor.hide() 259 | } 260 | this.render() 261 | } 262 | 263 | onRefresh(){ 264 | if(!this.loading) { 265 | this._read(this.url) 266 | } 267 | } 268 | 269 | 270 | 271 | private parseNovel(url: string): Promise{ 272 | let item = this.cacheManageer.getItem(url) 273 | if(item) { 274 | return item 275 | } else { 276 | let value = parseNovel(url) 277 | this.cacheManageer.setItem(url, value) 278 | return value 279 | } 280 | } 281 | 282 | private async _read(url: string){ 283 | this.url = url 284 | this.loading = true 285 | this.render(); 286 | const res = await this.parseNovel(url) 287 | if(res.index.next) { 288 | this.parseNovel(this.getAbsoluteUrl(res.index.next)) 289 | } 290 | if (this.book) { 291 | // console.log("save books") 292 | db.books().update({ _id: this.book._id }, { $set: { lastUrl: url }}) 293 | this.config.token && setBook({ name: this.book.name, lastUrl: url }).catch(e => { 294 | if(e.code === 401) { 295 | delete this.config.token 296 | } else { 297 | throw e 298 | } 299 | }) 300 | } 301 | this.lines = wordWrap(res.content, this.lineNumber) 302 | // if (this.firstRun && (this.config.lastLine || 0) < this.lines.length) { 303 | // this.count = this.config.lastLine || 0 304 | // } else { 305 | this.count = 0 306 | // } 307 | this.setNext(res.index.next) 308 | this.title = res.title 309 | this.loading = false 310 | this.render() 311 | } 312 | 313 | render() { 314 | if(this.boss) { 315 | this.screen.render("shell>", "") 316 | } else { 317 | const currentLine = this.getCurrentLine() 318 | const lines = this.getRenderLines(currentLine) 319 | const progress = Math.round(this.lines.length ? ((currentLine) / this.lines.length * 100) : 100) 320 | const content = lines.length > 0 ? lines.join(newLineSplit) : this.loading ? '' : "没有内容" 321 | const title = this.loading ? '加载中...' : chalk.gray(this.title) 322 | this.screen.render(content, 323 | progress 324 | + '%\t' 325 | + currentLine 326 | + '/' 327 | + this.lines.length 328 | + '\t' 329 | + title) 330 | } 331 | } 332 | 333 | getRenderLines(currentLine: number){ 334 | const lines = new Array(this.line).fill(" ") 335 | this.lines.slice(this.count, currentLine).forEach((value, index) => { 336 | lines[index] = value 337 | }) 338 | return lines 339 | } 340 | 341 | getCurrentLine(){ 342 | let currentLine = this.count + this.line 343 | if(currentLine > this.lines.length) { 344 | currentLine = this.lines.length 345 | } 346 | return currentLine 347 | } 348 | 349 | close(){ 350 | super.close() 351 | writeConfigSync({ ...this.config, lastUrl: this.url, lastLine: this.count }) 352 | } 353 | 354 | isEnd() { 355 | return (this.count + this.line) >= this.lines.length 356 | } 357 | 358 | isHead() { 359 | return this.count === 0 360 | } 361 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@sindresorhus/is@^0.14.0": 6 | version "0.14.0" 7 | resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" 8 | integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== 9 | 10 | "@szmarczak/http-timer@^1.1.2": 11 | version "1.1.2" 12 | resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" 13 | integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== 14 | dependencies: 15 | defer-to-connect "^1.0.1" 16 | 17 | "@types/cheerio@^0.22.23": 18 | version "0.22.23" 19 | resolved "https://registry.npm.taobao.org/@types/cheerio/download/@types/cheerio-0.22.23.tgz?cache=0&sync_timestamp=1607401292950&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fcheerio%2Fdownload%2F%40types%2Fcheerio-0.22.23.tgz#74bcfee9c5ee53f619711dca953a89fe5cfa4eb4" 20 | integrity sha1-dLz+6cXuU/YZcR3KlTqJ/lz6TrQ= 21 | dependencies: 22 | "@types/node" "*" 23 | 24 | "@types/inquirer@^7.3.1": 25 | version "7.3.1" 26 | resolved "https://registry.npm.taobao.org/@types/inquirer/download/@types/inquirer-7.3.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Finquirer%2Fdownload%2F%40types%2Finquirer-7.3.1.tgz#1f231224e7df11ccfaf4cf9acbcc3b935fea292d" 27 | integrity sha1-HyMSJOffEcz69M+ay8w7k1/qKS0= 28 | dependencies: 29 | "@types/through" "*" 30 | rxjs "^6.4.0" 31 | 32 | "@types/mkdirp@^1.0.1": 33 | version "1.0.1" 34 | resolved "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" 35 | integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q== 36 | dependencies: 37 | "@types/node" "*" 38 | 39 | "@types/node-fetch@^2.5.7": 40 | version "2.5.7" 41 | resolved "https://registry.npm.taobao.org/@types/node-fetch/download/@types/node-fetch-2.5.7.tgz?cache=0&sync_timestamp=1605054865534&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode-fetch%2Fdownload%2F%40types%2Fnode-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" 42 | integrity sha1-IKKv/6iCqwTUTKeGRJonb59rvzw= 43 | dependencies: 44 | "@types/node" "*" 45 | form-data "^3.0.0" 46 | 47 | "@types/node@*": 48 | version "14.14.14" 49 | resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-14.14.14.tgz?cache=0&sync_timestamp=1608047873667&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae" 50 | integrity sha1-9/1fPMhSEwERn2ORDw+5ZcfXYa4= 51 | 52 | "@types/node@^10.12.10": 53 | version "10.17.49" 54 | resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-10.17.49.tgz?cache=0&sync_timestamp=1608047873667&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-10.17.49.tgz#ecf0b67bab4b84d0ec9b0709db4aac3824a51c4a" 55 | integrity sha1-7PC2e6tLhNDsmwcJ20qsOCSlHEo= 56 | 57 | "@types/through@*": 58 | version "0.0.30" 59 | resolved "https://registry.npm.taobao.org/@types/through/download/@types/through-0.0.30.tgz?cache=0&sync_timestamp=1605057449889&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fthrough%2Fdownload%2F%40types%2Fthrough-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" 60 | integrity sha1-4OQs536Je9aurW9upirrE1uKOJU= 61 | dependencies: 62 | "@types/node" "*" 63 | 64 | ansi-align@^3.0.0: 65 | version "3.0.0" 66 | resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" 67 | integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== 68 | dependencies: 69 | string-width "^3.0.0" 70 | 71 | ansi-escapes@^4.2.1: 72 | version "4.3.1" 73 | resolved "https://registry.npm.taobao.org/ansi-escapes/download/ansi-escapes-4.3.1.tgz?cache=0&sync_timestamp=1600349127942&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-escapes%2Fdownload%2Fansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" 74 | integrity sha1-pcR8xDGB8fOP/XB2g3cA05VSKmE= 75 | dependencies: 76 | type-fest "^0.11.0" 77 | 78 | ansi-regex@^4.1.0: 79 | version "4.1.0" 80 | resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 81 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 82 | 83 | ansi-regex@^5.0.0: 84 | version "5.0.0" 85 | resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 86 | integrity sha1-OIU59VF5vzkznIGvMKZU1p+Hy3U= 87 | 88 | ansi-styles@^4.1.0: 89 | version "4.3.0" 90 | resolved "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1606792371412&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 91 | integrity sha1-7dgDYornHATIWuegkG7a00tkiTc= 92 | dependencies: 93 | color-convert "^2.0.1" 94 | 95 | async@0.2.10: 96 | version "0.2.10" 97 | resolved "https://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" 98 | integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= 99 | 100 | asynckit@^0.4.0: 101 | version "0.4.0" 102 | resolved "https://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 103 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 104 | 105 | binary-search-tree@0.2.5: 106 | version "0.2.5" 107 | resolved "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz#7dbb3b210fdca082450dad2334c304af39bdc784" 108 | integrity sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q= 109 | dependencies: 110 | underscore "~1.4.4" 111 | 112 | boolbase@~1.0.0: 113 | version "1.0.0" 114 | resolved "https://registry.npm.taobao.org/boolbase/download/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" 115 | integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= 116 | 117 | boxen@^4.2.0: 118 | version "4.2.0" 119 | resolved "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" 120 | integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== 121 | dependencies: 122 | ansi-align "^3.0.0" 123 | camelcase "^5.3.1" 124 | chalk "^3.0.0" 125 | cli-boxes "^2.2.0" 126 | string-width "^4.1.0" 127 | term-size "^2.1.0" 128 | type-fest "^0.8.1" 129 | widest-line "^3.1.0" 130 | 131 | cacheable-request@^6.0.0: 132 | version "6.1.0" 133 | resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" 134 | integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== 135 | dependencies: 136 | clone-response "^1.0.2" 137 | get-stream "^5.1.0" 138 | http-cache-semantics "^4.0.0" 139 | keyv "^3.0.0" 140 | lowercase-keys "^2.0.0" 141 | normalize-url "^4.1.0" 142 | responselike "^1.0.2" 143 | 144 | camelcase@^5.3.1: 145 | version "5.3.1" 146 | resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 147 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 148 | 149 | chalk@^3.0.0: 150 | version "3.0.0" 151 | resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" 152 | integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== 153 | dependencies: 154 | ansi-styles "^4.1.0" 155 | supports-color "^7.1.0" 156 | 157 | chalk@^4.1.0: 158 | version "4.1.0" 159 | resolved "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1592843133653&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" 160 | integrity sha1-ThSHCmGNni7dl92DRf2dncMVZGo= 161 | dependencies: 162 | ansi-styles "^4.1.0" 163 | supports-color "^7.1.0" 164 | 165 | chardet@^0.7.0: 166 | version "0.7.0" 167 | resolved "https://registry.npm.taobao.org/chardet/download/chardet-0.7.0.tgz?cache=0&sync_timestamp=1601032529880&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchardet%2Fdownload%2Fchardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" 168 | integrity sha1-kAlISfCTfy7twkJdDSip5fDLrZ4= 169 | 170 | cheerio@^1.0.0-rc.3: 171 | version "1.0.0-rc.3" 172 | resolved "https://registry.npm.taobao.org/cheerio/download/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" 173 | integrity sha1-CUY21CWy6cD065GkbAVjDJoai/Y= 174 | dependencies: 175 | css-select "~1.2.0" 176 | dom-serializer "~0.1.1" 177 | entities "~1.1.1" 178 | htmlparser2 "^3.9.1" 179 | lodash "^4.15.0" 180 | parse5 "^3.0.1" 181 | 182 | ci-info@^2.0.0: 183 | version "2.0.0" 184 | resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" 185 | integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== 186 | 187 | cli-boxes@^2.2.0: 188 | version "2.2.1" 189 | resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" 190 | integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== 191 | 192 | cli-cursor@^3.1.0: 193 | version "3.1.0" 194 | resolved "https://registry.npm.taobao.org/cli-cursor/download/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" 195 | integrity sha1-JkMFp65JDR0Dvwybp8kl0XU68wc= 196 | dependencies: 197 | restore-cursor "^3.1.0" 198 | 199 | cli-width@^3.0.0: 200 | version "3.0.0" 201 | resolved "https://registry.npm.taobao.org/cli-width/download/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" 202 | integrity sha1-ovSEN6LKqaIkNueUvwceyeYc7fY= 203 | 204 | clone-response@^1.0.2: 205 | version "1.0.2" 206 | resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" 207 | integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= 208 | dependencies: 209 | mimic-response "^1.0.0" 210 | 211 | color-convert@^2.0.1: 212 | version "2.0.1" 213 | resolved "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 214 | integrity sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM= 215 | dependencies: 216 | color-name "~1.1.4" 217 | 218 | color-name@~1.1.4: 219 | version "1.1.4" 220 | resolved "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 221 | integrity sha1-wqCah6y95pVD3m9j+jmVyCbFNqI= 222 | 223 | combined-stream@^1.0.8: 224 | version "1.0.8" 225 | resolved "https://registry.npm.taobao.org/combined-stream/download/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 226 | integrity sha1-w9RaizT9cwYxoRCoolIGgrMdWn8= 227 | dependencies: 228 | delayed-stream "~1.0.0" 229 | 230 | commander@^2.19.0: 231 | version "2.20.3" 232 | resolved "https://registry.npm.taobao.org/commander/download/commander-2.20.3.tgz?cache=0&sync_timestamp=1607931342826&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 233 | integrity sha1-/UhehMA+tIgcIHIrpIA16FMa6zM= 234 | 235 | configstore@^5.0.1: 236 | version "5.0.1" 237 | resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" 238 | integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== 239 | dependencies: 240 | dot-prop "^5.2.0" 241 | graceful-fs "^4.1.2" 242 | make-dir "^3.0.0" 243 | unique-string "^2.0.0" 244 | write-file-atomic "^3.0.0" 245 | xdg-basedir "^4.0.0" 246 | 247 | crypto-random-string@^2.0.0: 248 | version "2.0.0" 249 | resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" 250 | integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== 251 | 252 | css-select@~1.2.0: 253 | version "1.2.0" 254 | resolved "https://registry.npm.taobao.org/css-select/download/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" 255 | integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= 256 | dependencies: 257 | boolbase "~1.0.0" 258 | css-what "2.1" 259 | domutils "1.5.1" 260 | nth-check "~1.0.1" 261 | 262 | css-what@2.1: 263 | version "2.1.3" 264 | resolved "https://registry.npm.taobao.org/css-what/download/css-what-2.1.3.tgz?cache=0&sync_timestamp=1602570934118&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcss-what%2Fdownload%2Fcss-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" 265 | integrity sha1-ptdgRXM2X+dGhsPzEcVlE9iChfI= 266 | 267 | debug@^4.1.0: 268 | version "4.3.1" 269 | resolved "https://registry.npm.taobao.org/debug/download/debug-4.3.1.tgz?cache=0&sync_timestamp=1607566537361&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" 270 | integrity sha1-8NIpxQXgxtjEmsVT0bE9wYP2su4= 271 | dependencies: 272 | ms "2.1.2" 273 | 274 | decompress-response@^3.3.0: 275 | version "3.3.0" 276 | resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" 277 | integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= 278 | dependencies: 279 | mimic-response "^1.0.0" 280 | 281 | deep-extend@^0.6.0: 282 | version "0.6.0" 283 | resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 284 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 285 | 286 | defer-to-connect@^1.0.1: 287 | version "1.1.3" 288 | resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" 289 | integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== 290 | 291 | delayed-stream@~1.0.0: 292 | version "1.0.0" 293 | resolved "https://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 294 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 295 | 296 | dom-serializer@0: 297 | version "0.2.2" 298 | resolved "https://registry.npm.taobao.org/dom-serializer/download/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" 299 | integrity sha1-GvuB9TNxcXXUeGVd68XjMtn5u1E= 300 | dependencies: 301 | domelementtype "^2.0.1" 302 | entities "^2.0.0" 303 | 304 | dom-serializer@~0.1.1: 305 | version "0.1.1" 306 | resolved "https://registry.npm.taobao.org/dom-serializer/download/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" 307 | integrity sha1-HsQFnihLq+027sKUHUqXChic58A= 308 | dependencies: 309 | domelementtype "^1.3.0" 310 | entities "^1.1.1" 311 | 312 | domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: 313 | version "1.3.1" 314 | resolved "https://registry.npm.taobao.org/domelementtype/download/domelementtype-1.3.1.tgz?cache=0&sync_timestamp=1606866123758&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomelementtype%2Fdownload%2Fdomelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" 315 | integrity sha1-0EjESzew0Qp/Kj1f7j9DM9eQSB8= 316 | 317 | domelementtype@^2.0.1: 318 | version "2.1.0" 319 | resolved "https://registry.npm.taobao.org/domelementtype/download/domelementtype-2.1.0.tgz?cache=0&sync_timestamp=1606866123758&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomelementtype%2Fdownload%2Fdomelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" 320 | integrity sha1-qFHAgKbRw9lDRK7RUdmfZp7fWF4= 321 | 322 | domhandler@^2.3.0: 323 | version "2.4.2" 324 | resolved "https://registry.npm.taobao.org/domhandler/download/domhandler-2.4.2.tgz?cache=0&sync_timestamp=1606872288592&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomhandler%2Fdownload%2Fdomhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" 325 | integrity sha1-iAUJfpM9ZehVRvcm1g9euItE+AM= 326 | dependencies: 327 | domelementtype "1" 328 | 329 | domutils@1.5.1: 330 | version "1.5.1" 331 | resolved "https://registry.npm.taobao.org/domutils/download/domutils-1.5.1.tgz?cache=0&sync_timestamp=1607393088815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomutils%2Fdownload%2Fdomutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" 332 | integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= 333 | dependencies: 334 | dom-serializer "0" 335 | domelementtype "1" 336 | 337 | domutils@^1.5.1: 338 | version "1.7.0" 339 | resolved "https://registry.npm.taobao.org/domutils/download/domutils-1.7.0.tgz?cache=0&sync_timestamp=1607393088815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdomutils%2Fdownload%2Fdomutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" 340 | integrity sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo= 341 | dependencies: 342 | dom-serializer "0" 343 | domelementtype "1" 344 | 345 | dot-prop@^5.2.0: 346 | version "5.3.0" 347 | resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" 348 | integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== 349 | dependencies: 350 | is-obj "^2.0.0" 351 | 352 | duplexer3@^0.1.4: 353 | version "0.1.4" 354 | resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 355 | integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= 356 | 357 | emoji-regex@^7.0.1: 358 | version "7.0.3" 359 | resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 360 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 361 | 362 | emoji-regex@^8.0.0: 363 | version "8.0.0" 364 | resolved "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 365 | integrity sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc= 366 | 367 | end-of-stream@^1.1.0: 368 | version "1.4.4" 369 | resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 370 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 371 | dependencies: 372 | once "^1.4.0" 373 | 374 | entities@^1.1.1, entities@~1.1.1: 375 | version "1.1.2" 376 | resolved "https://registry.npm.taobao.org/entities/download/entities-1.1.2.tgz?cache=0&sync_timestamp=1602897079266&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fentities%2Fdownload%2Fentities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" 377 | integrity sha1-vfpzUplmTfr9NFKe1PhSKidf6lY= 378 | 379 | entities@^2.0.0: 380 | version "2.1.0" 381 | resolved "https://registry.npm.taobao.org/entities/download/entities-2.1.0.tgz?cache=0&sync_timestamp=1602897079266&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fentities%2Fdownload%2Fentities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" 382 | integrity sha1-mS0xKc999ocLlsV4WMJJoSD4uLU= 383 | 384 | eol@^0.9.1: 385 | version "0.9.1" 386 | resolved "https://registry.npm.taobao.org/eol/download/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" 387 | integrity sha1-9wGRL1BAdL41xhF6XEreSc1Ues0= 388 | 389 | escape-goat@^2.0.0: 390 | version "2.1.1" 391 | resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" 392 | integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== 393 | 394 | escape-string-regexp@^1.0.5: 395 | version "1.0.5" 396 | resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 397 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 398 | 399 | external-editor@^3.0.3: 400 | version "3.1.0" 401 | resolved "https://registry.npm.taobao.org/external-editor/download/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" 402 | integrity sha1-ywP3QL764D6k0oPK7SdBqD8zVJU= 403 | dependencies: 404 | chardet "^0.7.0" 405 | iconv-lite "^0.4.24" 406 | tmp "^0.0.33" 407 | 408 | figures@^3.0.0: 409 | version "3.2.0" 410 | resolved "https://registry.npm.taobao.org/figures/download/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" 411 | integrity sha1-YlwYvSk8YE3EqN2y/r8MiDQXRq8= 412 | dependencies: 413 | escape-string-regexp "^1.0.5" 414 | 415 | form-data@^3.0.0: 416 | version "3.0.0" 417 | resolved "https://registry.npm.taobao.org/form-data/download/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" 418 | integrity sha1-MbfjnIXxNVtxOe4MZHzw3n+DxoI= 419 | dependencies: 420 | asynckit "^0.4.0" 421 | combined-stream "^1.0.8" 422 | mime-types "^2.1.12" 423 | 424 | gbk.js@^0.3.0: 425 | version "0.3.0" 426 | resolved "https://registry.npm.taobao.org/gbk.js/download/gbk.js-0.3.0.tgz#1b62a0a3f137081c213905bfaee1b6abdac861bd" 427 | integrity sha1-G2Kgo/E3CBwhOQW/ruG2q9rIYb0= 428 | 429 | get-stream@^4.1.0: 430 | version "4.1.0" 431 | resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" 432 | integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== 433 | dependencies: 434 | pump "^3.0.0" 435 | 436 | get-stream@^5.1.0: 437 | version "5.2.0" 438 | resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 439 | integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== 440 | dependencies: 441 | pump "^3.0.0" 442 | 443 | global-dirs@^2.0.1: 444 | version "2.1.0" 445 | resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" 446 | integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== 447 | dependencies: 448 | ini "1.3.7" 449 | 450 | got@^9.6.0: 451 | version "9.6.0" 452 | resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" 453 | integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== 454 | dependencies: 455 | "@sindresorhus/is" "^0.14.0" 456 | "@szmarczak/http-timer" "^1.1.2" 457 | cacheable-request "^6.0.0" 458 | decompress-response "^3.3.0" 459 | duplexer3 "^0.1.4" 460 | get-stream "^4.1.0" 461 | lowercase-keys "^1.0.1" 462 | mimic-response "^1.0.1" 463 | p-cancelable "^1.0.0" 464 | to-readable-stream "^1.0.0" 465 | url-parse-lax "^3.0.0" 466 | 467 | graceful-fs@^4.1.2: 468 | version "4.2.4" 469 | resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 470 | integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== 471 | 472 | has-flag@^4.0.0: 473 | version "4.0.0" 474 | resolved "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz?cache=0&sync_timestamp=1577797756584&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-flag%2Fdownload%2Fhas-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 475 | integrity sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s= 476 | 477 | has-yarn@^2.1.0: 478 | version "2.1.0" 479 | resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" 480 | integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== 481 | 482 | htmlparser2@^3.9.1: 483 | version "3.10.1" 484 | resolved "https://registry.npm.taobao.org/htmlparser2/download/htmlparser2-3.10.1.tgz?cache=0&sync_timestamp=1607394271903&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtmlparser2%2Fdownload%2Fhtmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" 485 | integrity sha1-vWedw/WYl7ajS7EHSchVu1OpOS8= 486 | dependencies: 487 | domelementtype "^1.3.1" 488 | domhandler "^2.3.0" 489 | domutils "^1.5.1" 490 | entities "^1.1.1" 491 | inherits "^2.0.1" 492 | readable-stream "^3.1.1" 493 | 494 | http-cache-semantics@^4.0.0: 495 | version "4.1.0" 496 | resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" 497 | integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== 498 | 499 | iconv-lite@^0.4.24: 500 | version "0.4.24" 501 | resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 502 | integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs= 503 | dependencies: 504 | safer-buffer ">= 2.1.2 < 3" 505 | 506 | immediate@~3.0.5: 507 | version "3.0.6" 508 | resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" 509 | integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= 510 | 511 | import-lazy@^2.1.0: 512 | version "2.1.0" 513 | resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" 514 | integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= 515 | 516 | imurmurhash@^0.1.4: 517 | version "0.1.4" 518 | resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 519 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 520 | 521 | inherits@^2.0.1, inherits@^2.0.3: 522 | version "2.0.4" 523 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 524 | integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= 525 | 526 | ini@1.3.7: 527 | version "1.3.7" 528 | resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" 529 | integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== 530 | 531 | ini@~1.3.0: 532 | version "1.3.8" 533 | resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" 534 | integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== 535 | 536 | inquirer@^7.3.3: 537 | version "7.3.3" 538 | resolved "https://registry.npm.taobao.org/inquirer/download/inquirer-7.3.3.tgz?cache=0&sync_timestamp=1595475980671&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finquirer%2Fdownload%2Finquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" 539 | integrity sha1-BNF2sq8Er8FXqD/XwQDpjuCq0AM= 540 | dependencies: 541 | ansi-escapes "^4.2.1" 542 | chalk "^4.1.0" 543 | cli-cursor "^3.1.0" 544 | cli-width "^3.0.0" 545 | external-editor "^3.0.3" 546 | figures "^3.0.0" 547 | lodash "^4.17.19" 548 | mute-stream "0.0.8" 549 | run-async "^2.4.0" 550 | rxjs "^6.6.0" 551 | string-width "^4.1.0" 552 | strip-ansi "^6.0.0" 553 | through "^2.3.6" 554 | 555 | is-ci@^2.0.0: 556 | version "2.0.0" 557 | resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" 558 | integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== 559 | dependencies: 560 | ci-info "^2.0.0" 561 | 562 | is-fullwidth-code-point@^2.0.0: 563 | version "2.0.0" 564 | resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 565 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 566 | 567 | is-fullwidth-code-point@^3.0.0: 568 | version "3.0.0" 569 | resolved "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 570 | integrity sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0= 571 | 572 | is-installed-globally@^0.3.2: 573 | version "0.3.2" 574 | resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" 575 | integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== 576 | dependencies: 577 | global-dirs "^2.0.1" 578 | is-path-inside "^3.0.1" 579 | 580 | is-npm@^5.0.0: 581 | version "5.0.0" 582 | resolved "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" 583 | integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== 584 | 585 | is-obj@^2.0.0: 586 | version "2.0.0" 587 | resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" 588 | integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== 589 | 590 | is-path-inside@^3.0.1: 591 | version "3.0.2" 592 | resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" 593 | integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== 594 | 595 | is-typedarray@^1.0.0: 596 | version "1.0.0" 597 | resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 598 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= 599 | 600 | is-yarn-global@^0.3.0: 601 | version "0.3.0" 602 | resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" 603 | integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== 604 | 605 | json-buffer@3.0.0: 606 | version "3.0.0" 607 | resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" 608 | integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= 609 | 610 | keyv@^3.0.0: 611 | version "3.1.0" 612 | resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" 613 | integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== 614 | dependencies: 615 | json-buffer "3.0.0" 616 | 617 | latest-version@^5.1.0: 618 | version "5.1.0" 619 | resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" 620 | integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== 621 | dependencies: 622 | package-json "^6.3.0" 623 | 624 | lie@3.1.1: 625 | version "3.1.1" 626 | resolved "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" 627 | integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= 628 | dependencies: 629 | immediate "~3.0.5" 630 | 631 | localforage@^1.3.0: 632 | version "1.9.0" 633 | resolved "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1" 634 | integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g== 635 | dependencies: 636 | lie "3.1.1" 637 | 638 | lodash@^4.15.0, lodash@^4.17.19: 639 | version "4.17.20" 640 | resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.20.tgz?cache=0&sync_timestamp=1597336082988&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" 641 | integrity sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI= 642 | 643 | lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: 644 | version "1.0.1" 645 | resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" 646 | integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== 647 | 648 | lowercase-keys@^2.0.0: 649 | version "2.0.0" 650 | resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" 651 | integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== 652 | 653 | lru-cache@^6.0.0: 654 | version "6.0.0" 655 | resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 656 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 657 | dependencies: 658 | yallist "^4.0.0" 659 | 660 | make-dir@^3.0.0: 661 | version "3.1.0" 662 | resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" 663 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== 664 | dependencies: 665 | semver "^6.0.0" 666 | 667 | mime-db@1.44.0: 668 | version "1.44.0" 669 | resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.44.0.tgz?cache=0&sync_timestamp=1600831117178&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-db%2Fdownload%2Fmime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 670 | integrity sha1-+hHF6wrKEzS0Izy01S8QxaYnL5I= 671 | 672 | mime-types@^2.1.12: 673 | version "2.1.27" 674 | resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 675 | integrity sha1-R5SfmOJ56lMRn1ci4PNOUpvsAJ8= 676 | dependencies: 677 | mime-db "1.44.0" 678 | 679 | mimic-fn@^2.1.0: 680 | version "2.1.0" 681 | resolved "https://registry.npm.taobao.org/mimic-fn/download/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 682 | integrity sha1-ftLCzMyvhNP/y3pptXcR/CCDQBs= 683 | 684 | mimic-response@^1.0.0, mimic-response@^1.0.1: 685 | version "1.0.1" 686 | resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 687 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== 688 | 689 | minimist@^1.2.0, minimist@^1.2.5: 690 | version "1.2.5" 691 | resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 692 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 693 | 694 | mkdirp@^1.0.4: 695 | version "1.0.4" 696 | resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" 697 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 698 | 699 | mkdirp@~0.5.1: 700 | version "0.5.5" 701 | resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 702 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 703 | dependencies: 704 | minimist "^1.2.5" 705 | 706 | ms@2.1.2: 707 | version "2.1.2" 708 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&sync_timestamp=1607433899126&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 709 | integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= 710 | 711 | mute-stream@0.0.8: 712 | version "0.0.8" 713 | resolved "https://registry.npm.taobao.org/mute-stream/download/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" 714 | integrity sha1-FjDEKyJR/4HiooPelqVJfqkuXg0= 715 | 716 | nedb-promises@^4.1.0: 717 | version "4.1.0" 718 | resolved "https://registry.npmjs.org/nedb-promises/-/nedb-promises-4.1.0.tgz#b0700aae6c72b7c1ba696da618cc140a1520c078" 719 | integrity sha512-nTdx7jX/Vu24L05Cy0ee7CL3L4SEHCb1jlLlegPl0VlE8jsUXgnSyNOjq3FEc3cdUSDK05X7hSzb3+a07PigmQ== 720 | dependencies: 721 | nedb "^1.8.0" 722 | 723 | nedb@^1.8.0: 724 | version "1.8.0" 725 | resolved "https://registry.npmjs.org/nedb/-/nedb-1.8.0.tgz#0e3502cd82c004d5355a43c9e55577bd7bd91d88" 726 | integrity sha1-DjUCzYLABNU1WkPJ5VV3vXvZHYg= 727 | dependencies: 728 | async "0.2.10" 729 | binary-search-tree "0.2.5" 730 | localforage "^1.3.0" 731 | mkdirp "~0.5.1" 732 | underscore "~1.4.4" 733 | 734 | node-fetch@^2.6.1: 735 | version "2.6.1" 736 | resolved "https://registry.npm.taobao.org/node-fetch/download/node-fetch-2.6.1.tgz?cache=0&sync_timestamp=1599309206591&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-fetch%2Fdownload%2Fnode-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" 737 | integrity sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI= 738 | 739 | normalize-url@^4.1.0: 740 | version "4.5.0" 741 | resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" 742 | integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== 743 | 744 | nth-check@~1.0.1: 745 | version "1.0.2" 746 | resolved "https://registry.npm.taobao.org/nth-check/download/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" 747 | integrity sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw= 748 | dependencies: 749 | boolbase "~1.0.0" 750 | 751 | once@^1.3.1, once@^1.4.0: 752 | version "1.4.0" 753 | resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 754 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 755 | dependencies: 756 | wrappy "1" 757 | 758 | onetime@^5.1.0: 759 | version "5.1.2" 760 | resolved "https://registry.npm.taobao.org/onetime/download/onetime-5.1.2.tgz?cache=0&sync_timestamp=1597003951681&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fonetime%2Fdownload%2Fonetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" 761 | integrity sha1-0Oluu1awdHbfHdnEgG5SN5hcpF4= 762 | dependencies: 763 | mimic-fn "^2.1.0" 764 | 765 | os-tmpdir@~1.0.2: 766 | version "1.0.2" 767 | resolved "https://registry.npm.taobao.org/os-tmpdir/download/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 768 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 769 | 770 | p-cancelable@^1.0.0: 771 | version "1.1.0" 772 | resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" 773 | integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== 774 | 775 | package-json@^6.3.0: 776 | version "6.5.0" 777 | resolved "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" 778 | integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== 779 | dependencies: 780 | got "^9.6.0" 781 | registry-auth-token "^4.0.0" 782 | registry-url "^5.0.0" 783 | semver "^6.2.0" 784 | 785 | parse5@^3.0.1: 786 | version "3.0.3" 787 | resolved "https://registry.npm.taobao.org/parse5/download/parse5-3.0.3.tgz?cache=0&sync_timestamp=1595850971402&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fparse5%2Fdownload%2Fparse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" 788 | integrity sha1-BC95L/3TaFFVHPTp4Gazh0q0W1w= 789 | dependencies: 790 | "@types/node" "*" 791 | 792 | prepend-http@^2.0.0: 793 | version "2.0.0" 794 | resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" 795 | integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= 796 | 797 | pump@^3.0.0: 798 | version "3.0.0" 799 | resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 800 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 801 | dependencies: 802 | end-of-stream "^1.1.0" 803 | once "^1.3.1" 804 | 805 | pupa@^2.1.1: 806 | version "2.1.1" 807 | resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" 808 | integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== 809 | dependencies: 810 | escape-goat "^2.0.0" 811 | 812 | rc@^1.2.8: 813 | version "1.2.8" 814 | resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 815 | integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== 816 | dependencies: 817 | deep-extend "^0.6.0" 818 | ini "~1.3.0" 819 | minimist "^1.2.0" 820 | strip-json-comments "~2.0.1" 821 | 822 | readable-stream@^3.1.1: 823 | version "3.6.0" 824 | resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 825 | integrity sha1-M3u9o63AcGvT4CRCaihtS0sskZg= 826 | dependencies: 827 | inherits "^2.0.3" 828 | string_decoder "^1.1.1" 829 | util-deprecate "^1.0.1" 830 | 831 | registry-auth-token@^4.0.0: 832 | version "4.2.1" 833 | resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" 834 | integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== 835 | dependencies: 836 | rc "^1.2.8" 837 | 838 | registry-url@^5.0.0: 839 | version "5.1.0" 840 | resolved "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" 841 | integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== 842 | dependencies: 843 | rc "^1.2.8" 844 | 845 | responselike@^1.0.2: 846 | version "1.0.2" 847 | resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" 848 | integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= 849 | dependencies: 850 | lowercase-keys "^1.0.0" 851 | 852 | restore-cursor@^3.1.0: 853 | version "3.1.0" 854 | resolved "https://registry.npm.taobao.org/restore-cursor/download/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" 855 | integrity sha1-OfZ8VLOnpYzqUjbZXPADQjljH34= 856 | dependencies: 857 | onetime "^5.1.0" 858 | signal-exit "^3.0.2" 859 | 860 | run-async@^2.4.0: 861 | version "2.4.1" 862 | resolved "https://registry.npm.taobao.org/run-async/download/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" 863 | integrity sha1-hEDsz5nqPnC9QJ1JqriOEMGJpFU= 864 | 865 | rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: 866 | version "6.6.3" 867 | resolved "https://registry.npm.taobao.org/rxjs/download/rxjs-6.6.3.tgz?cache=0&sync_timestamp=1607305607879&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frxjs%2Fdownload%2Frxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" 868 | integrity sha1-jKhGNcTaqQDA05Z6buesYCce5VI= 869 | dependencies: 870 | tslib "^1.9.0" 871 | 872 | safe-buffer@~5.2.0: 873 | version "5.2.1" 874 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 875 | integrity sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY= 876 | 877 | "safer-buffer@>= 2.1.2 < 3": 878 | version "2.1.2" 879 | resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 880 | integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= 881 | 882 | semver-diff@^3.1.1: 883 | version "3.1.1" 884 | resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" 885 | integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== 886 | dependencies: 887 | semver "^6.3.0" 888 | 889 | semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: 890 | version "6.3.0" 891 | resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 892 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 893 | 894 | semver@^7.3.2: 895 | version "7.3.4" 896 | resolved "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" 897 | integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== 898 | dependencies: 899 | lru-cache "^6.0.0" 900 | 901 | signal-exit@^3.0.2: 902 | version "3.0.3" 903 | resolved "https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.3.tgz?cache=0&sync_timestamp=1592843131591&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsignal-exit%2Fdownload%2Fsignal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" 904 | integrity sha1-oUEMLt2PB3sItOJTyOrPyvBXRhw= 905 | 906 | string-width@^3.0.0: 907 | version "3.1.0" 908 | resolved "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 909 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 910 | dependencies: 911 | emoji-regex "^7.0.1" 912 | is-fullwidth-code-point "^2.0.0" 913 | strip-ansi "^5.1.0" 914 | 915 | string-width@^4.0.0, string-width@^4.1.0: 916 | version "4.2.0" 917 | resolved "https://registry.npm.taobao.org/string-width/download/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" 918 | integrity sha1-lSGCxGzHssMT0VluYjmSvRY7crU= 919 | dependencies: 920 | emoji-regex "^8.0.0" 921 | is-fullwidth-code-point "^3.0.0" 922 | strip-ansi "^6.0.0" 923 | 924 | string_decoder@^1.1.1: 925 | version "1.3.0" 926 | resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 927 | integrity sha1-QvEUWUpGzxqOMLCoT1bHjD7awh4= 928 | dependencies: 929 | safe-buffer "~5.2.0" 930 | 931 | strip-ansi@^5.1.0: 932 | version "5.2.0" 933 | resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 934 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 935 | dependencies: 936 | ansi-regex "^4.1.0" 937 | 938 | strip-ansi@^6.0.0: 939 | version "6.0.0" 940 | resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 941 | integrity sha1-CxVx3XZpzNTz4G4U7x7tJiJa5TI= 942 | dependencies: 943 | ansi-regex "^5.0.0" 944 | 945 | strip-json-comments@~2.0.1: 946 | version "2.0.1" 947 | resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 948 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 949 | 950 | supports-color@^7.1.0: 951 | version "7.2.0" 952 | resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1608033349725&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 953 | integrity sha1-G33NyzK4E4gBs+R4umpRyqiWSNo= 954 | dependencies: 955 | has-flag "^4.0.0" 956 | 957 | term-size@^2.1.0: 958 | version "2.2.1" 959 | resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" 960 | integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== 961 | 962 | through@^2.3.6: 963 | version "2.3.8" 964 | resolved "https://registry.npm.taobao.org/through/download/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 965 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 966 | 967 | tmp@^0.0.33: 968 | version "0.0.33" 969 | resolved "https://registry.npm.taobao.org/tmp/download/tmp-0.0.33.tgz?cache=0&sync_timestamp=1592843137359&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftmp%2Fdownload%2Ftmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 970 | integrity sha1-bTQzWIl2jSGyvNoKonfO07G/rfk= 971 | dependencies: 972 | os-tmpdir "~1.0.2" 973 | 974 | to-readable-stream@^1.0.0: 975 | version "1.0.0" 976 | resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" 977 | integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== 978 | 979 | tslib@^1.9.0: 980 | version "1.14.1" 981 | resolved "https://registry.npm.taobao.org/tslib/download/tslib-1.14.1.tgz?cache=0&sync_timestamp=1602286854330&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 982 | integrity sha1-zy04vcNKE0vK8QkcQfZhni9nLQA= 983 | 984 | type-fest@^0.11.0: 985 | version "0.11.0" 986 | resolved "https://registry.npm.taobao.org/type-fest/download/type-fest-0.11.0.tgz?cache=0&sync_timestamp=1606468804579&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftype-fest%2Fdownload%2Ftype-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" 987 | integrity sha1-l6vwhyMQ/tiKXEZrJWgVdhReM/E= 988 | 989 | type-fest@^0.8.1: 990 | version "0.8.1" 991 | resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" 992 | integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== 993 | 994 | typedarray-to-buffer@^3.1.5: 995 | version "3.1.5" 996 | resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" 997 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== 998 | dependencies: 999 | is-typedarray "^1.0.0" 1000 | 1001 | typescript@^4.1.3: 1002 | version "4.1.3" 1003 | resolved "https://registry.npm.taobao.org/typescript/download/typescript-4.1.3.tgz?cache=0&sync_timestamp=1607912478239&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" 1004 | integrity sha1-UZ1YK9lMugz4k0x9joRn5HP1O7c= 1005 | 1006 | underscore@~1.4.4: 1007 | version "1.4.4" 1008 | resolved "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" 1009 | integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ= 1010 | 1011 | unique-string@^2.0.0: 1012 | version "2.0.0" 1013 | resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" 1014 | integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== 1015 | dependencies: 1016 | crypto-random-string "^2.0.0" 1017 | 1018 | update-notifier@^5.0.1: 1019 | version "5.0.1" 1020 | resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-5.0.1.tgz#1f92d45fb1f70b9e33880a72dd262bc12d22c20d" 1021 | integrity sha512-BuVpRdlwxeIOvmc32AGYvO1KVdPlsmqSh8KDDBxS6kDE5VR7R8OMP1d8MdhaVBvxl4H3551k9akXr0Y1iIB2Wg== 1022 | dependencies: 1023 | boxen "^4.2.0" 1024 | chalk "^4.1.0" 1025 | configstore "^5.0.1" 1026 | has-yarn "^2.1.0" 1027 | import-lazy "^2.1.0" 1028 | is-ci "^2.0.0" 1029 | is-installed-globally "^0.3.2" 1030 | is-npm "^5.0.0" 1031 | is-yarn-global "^0.3.0" 1032 | latest-version "^5.1.0" 1033 | pupa "^2.1.1" 1034 | semver "^7.3.2" 1035 | semver-diff "^3.1.1" 1036 | xdg-basedir "^4.0.0" 1037 | 1038 | url-parse-lax@^3.0.0: 1039 | version "3.0.0" 1040 | resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" 1041 | integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= 1042 | dependencies: 1043 | prepend-http "^2.0.0" 1044 | 1045 | util-deprecate@^1.0.1: 1046 | version "1.0.2" 1047 | resolved "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1048 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 1049 | 1050 | widest-line@^3.1.0: 1051 | version "3.1.0" 1052 | resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" 1053 | integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== 1054 | dependencies: 1055 | string-width "^4.0.0" 1056 | 1057 | wrappy@1: 1058 | version "1.0.2" 1059 | resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1060 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 1061 | 1062 | write-file-atomic@^3.0.0: 1063 | version "3.0.3" 1064 | resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" 1065 | integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== 1066 | dependencies: 1067 | imurmurhash "^0.1.4" 1068 | is-typedarray "^1.0.0" 1069 | signal-exit "^3.0.2" 1070 | typedarray-to-buffer "^3.1.5" 1071 | 1072 | xdg-basedir@^4.0.0: 1073 | version "4.0.0" 1074 | resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" 1075 | integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== 1076 | 1077 | yallist@^4.0.0: 1078 | version "4.0.0" 1079 | resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1080 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1081 | --------------------------------------------------------------------------------