├── .gitignore ├── LICENSE ├── README.md ├── columnArticleList.js ├── config.js ├── downloadAudio.js ├── downloadComment.js ├── generaterPdf.js ├── image ├── geektime_VScode_filelist.png ├── geektime_file_content.png └── geektime_run.png ├── package-lock.json ├── package.json ├── template └── article.ejs └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | geektime_*/** 4 | config.js 5 | .nyc_output 6 | coverage 7 | quest.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 YibuMe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 极客时间专栏转换为PDF 2 | 3 | >说明:该项目仅仅只能用户个人学习使用,不能在商业中使用,若极客时间官方要求该代码仓库删除,请联系我进行删除 4 | 5 | ## 使用方法 6 | 7 | ### 配置信息 8 | 9 | 在配置文件[config.js](./config,js)中修改配置所需要的信息 10 | 11 | ```js 12 | /** 13 | * 需要转换为 pdf 的配置信息 14 | */ 15 | module.exports = { 16 | url: 'https://time.geekbang.org/serv/v1/article', // 该配置项不需要改动 17 | commentUrl: 'https://time.geekbang.org/serv/v1/comments', // 该配置项不需要改动 18 | columnBaseUrl: 'https://time.geekbang.org/column/article/', // 该配置项不需要改动 19 | columnName: '玩转VScode', // 专栏名称 20 | firstArticalId: 18053, //专栏第一篇文章的ID 21 | articalIds: [201700,202772,204472,205784], //指定下载的articalId, 优先级更高, 配置后firstArticalId配置将失效 22 | isdownloadVideo: false, // 是否下载音频 23 | isComment: false, // 是否导出评论 24 | cookie: 'cookie' 25 | }; 26 | ``` 27 | 28 | * 上面的配置项**前三项是不需要修改**的, 只需要修改后面的专栏信息 29 | 30 | * 会自动生成一个`geektime_{{columnName}}` 的文件夹来保存导出的所有`pdf`文件, `columnName` 为上面配置的 31 | 32 | * `firstArticalId` 这个参数最好配置专栏第一篇文章的 `ID` ,这个可以获取专栏的所有的文章,若不是第一篇文章的`ID` 则获取的是该文章以及之后的文章 33 | 34 | * `articalIds` 这个参数配置为需要获取的文章的所有的 `ID` 35 | 36 | * `cookie` 你在网页版登录后返回的`cookie`信息 37 | 38 | ### 运行 39 | 40 | 1. `git clone git@github.com:jjeejj/geektime2pdf.git` 在本地克隆下来 41 | 2. 然后执行 `npm i` 安装依赖 42 | 3. 运行主程序 `node columnArticleList.js` 等待一段时间,生成 `PDF` 完成 43 | 44 | > 这里可以先设置 `firstArticalId` 参数,获取整个专栏的内容;若中间有错误,不用管它,等运行完毕后,再设置 `articalIds` 参数,参数的值为上面获取失败的文章 `ID`,再次运行下载 45 | 46 | ![](./image/geektime_run.png) 47 | 48 | ## 导出结果 49 | 50 | ![](./image/geektime_VScode_filelist.png) 51 | 52 | ![](./image/geektime_file_content.png) 53 | 54 | ## 问题汇总 55 | 56 | * `puppeteer` 下载失败问题,参考 [stackoverflow](https://stackoverflow.com/questions/53997175/puppeteer-error-chromium-revision-is-not-downloaded) -------------------------------------------------------------------------------- /columnArticleList.js: -------------------------------------------------------------------------------- 1 | // 获取专栏文章列表 2 | const config = require('./config.js'); 3 | const superagent = require('superagent'); 4 | const utils = require('./utils'); 5 | const path = require('path'); 6 | const generaterPdf = require('./generaterPdf.js'); 7 | const downloadAudio = require('./downloadAudio.js'); 8 | const downloadComment = require('./downloadComment.js'); 9 | 10 | /** 11 | * 执行方法 12 | */ 13 | (async function getColumnArticleList (firstArticalId){ 14 | await utils.createDir('geektime_' + config.columnName); 15 | console.log('专栏文章链接开始获取'); 16 | let columnArticleUrlList = []; 17 | // 下载类型, 1: 指定文章ID进行下载, 0: 通过 firstArticalId进行下载 18 | let type = 0, nextId, neighborRight; 19 | //指定id下载 20 | let assignIndex = 1; 21 | if (config.articalIds && config.articalIds.length > 0) { 22 | type = 1; 23 | firstArticalId = config.articalIds[0]; 24 | console.log('通过articalIds配置进行文章获取', config.articalIds.length); 25 | } else { 26 | console.log('通过firstArticalId配置进行文章获取', firstArticalId); 27 | }; 28 | let articalId = firstArticalId; 29 | 30 | async function getNextColumnArticleUrl (){ 31 | try { 32 | let res = await superagent.post(config.url) 33 | .set({ 34 | 'Content-Type': 'application/json', 35 | 'Cookie': config.cookie, 36 | 'Referer': config.columnBaseUrl + articalId, 37 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36' 38 | }).send({ 39 | 'id': articalId, 40 | 'include_neighbors': true 41 | }); 42 | if (res.body && res.body.error && res.body.error.code){ 43 | console.log('error msg', res.body.error.msg); 44 | throw new Error(res.body.error.msg); 45 | }; 46 | console.log(res.body.data.article_title); 47 | let columnArticle = res.body.data; 48 | 49 | let articleInfo = { 50 | articleTitle: columnArticle.article_title, // 文章标题 51 | articalUrl: config.columnBaseUrl + articalId, // 文章地址 52 | articleContent: columnArticle.article_content, // 文章内容 53 | articleCover: columnArticle.article_cover, // 文章背景图 54 | authorName: columnArticle.author_name, // 文章作者 55 | articleCtime: utils.formatDate(columnArticle.article_ctime), // 文章创建时间 unix 时间戳 单位为 s 56 | articleNeighbors: columnArticle.neighbors, // 上下篇文章信息 57 | audioDownloadUrl: columnArticle.audio_download_url, 58 | audioTitle: columnArticle.audio_title 59 | }; 60 | columnArticleUrlList.push(articleInfo); 61 | articleInfo.commentsTotal = 0; 62 | articleInfo.commentsArr = []; 63 | // 是否导出评论 64 | if (config.isComment) { 65 | let {commentsTotal, commentsArr} = await downloadComment( 66 | config.columnBaseUrl + articalId, 67 | articalId); 68 | articleInfo.commentsTotal = commentsTotal; 69 | articleInfo.commentsArr = commentsArr; 70 | }; 71 | // 替换非法文件名 72 | let useArticleTtle = columnArticle.article_title.replace(/[\/:*?"<>|]/g, '-'); 73 | //生成PDF 74 | await generaterPdf(articleInfo, 75 | useArticleTtle + '.pdf', 76 | path.resolve(__dirname, 'geektime_' + config.columnName) 77 | ); 78 | // 是否下载音频 79 | if (config.isdownloadVideo && columnArticle.audio_download_url) { 80 | await downloadAudio( 81 | columnArticle.audio_download_url, 82 | useArticleTtle + '.mp3', 83 | path.resolve(__dirname, 'geektime_' + config.columnName) 84 | ); 85 | }; 86 | 87 | if(type == 1) { 88 | nextId = config.articalIds.length > assignIndex ? config.articalIds[assignIndex] : undefined; 89 | assignIndex++; 90 | } else { 91 | neighborRight = columnArticle.neighbors.right; 92 | nextId = (neighborRight && neighborRight.id) ? neighborRight.id : undefined; 93 | }; 94 | // 判断是否还有下一篇文章 95 | if (nextId){ 96 | articalId = nextId; 97 | await utils.sleep(1.5); 98 | await getNextColumnArticleUrl(); 99 | }; 100 | } catch(err){ 101 | console.log(`访问 地址 ${config.columnBaseUrl + articalId} err`, err.message); 102 | }; 103 | }; 104 | await getNextColumnArticleUrl(); 105 | console.log('专栏文章链接获取完成'); 106 | utils.writeToFile(`geektime_${config.columnName}`, JSON.stringify(columnArticleUrlList,null,4)); 107 | return columnArticleUrlList; 108 | })(config.firstArticalId); -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 需要转换为 pdf 的配置信息 3 | */ 4 | module.exports = { 5 | url: 'https://time.geekbang.org/serv/v1/article', 6 | commentUrl: 'https://time.geekbang.org/serv/v1/comments', 7 | columnBaseUrl: 'https://time.geekbang.org/column/article/', 8 | columnName: '分布式协议与算法实战', 9 | firstArticalId: 201700, //专栏第一篇文章的ID 10 | articalIds: [201700,202772,204472,205784], //指定下载的articalId, 优先级更高, 配置后firstArticalId配置将失效 11 | isdownloadVideo: true, // 是否下载音频 12 | isComment: true, // 是否导出评论 13 | cookie: 'cookie' 14 | }; -------------------------------------------------------------------------------- /downloadAudio.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const superagent = require('superagent'); 3 | const fs = require('fs'); 4 | /** 5 | * 下载对应的音频文件 6 | * @param {String} url 音频文件的地址 7 | * @param {String} fileName 文件名称 8 | * @param {String} fileDir 保存文件夹地址 9 | */ 10 | const downloadAudio = async (url, fileName, fileDir = __dirname) => { 11 | console.log('开始下载 音频文件: ', fileName); 12 | if (!url) throw '请传入一个音频地址'; 13 | if (path.extname(fileName) !== '.mp3'){ // 判断传的文件后缀是否是 mp3 14 | fileName = fileName + '.mp3'; 15 | }; 16 | let filePath = path.resolve(fileDir, fileName); 17 | let writeStream = fs.createWriteStream(filePath); 18 | superagent.get(url).pipe(writeStream); 19 | console.log('结束下载 音频文件: ', fileName); 20 | }; 21 | 22 | // downloadAudio( 23 | // 'https://static001.geekbang.org/resource/audio/e7/b1/e7ffca8ca5b09224969b3237723a0bb1.mp3', 24 | // '305 | 学会几个系统调用:咱们公司能接哪些类型的项目?.mp3', 25 | // '/Users/jiang/Project/geektime2pdf/geektime_趣谈Linux操作系统') 26 | 27 | module.exports = downloadAudio; -------------------------------------------------------------------------------- /downloadComment.js: -------------------------------------------------------------------------------- 1 | // 获取每篇文章下面所有的评论 2 | const config = require('./config.js'); 3 | const superagent = require('superagent'); 4 | const utils = require('./utils'); 5 | 6 | /** 7 | * 获取每篇文章下面所有的评论 8 | * @param {String} 文章的链接地址 9 | * @param {Number} 文章的ID 10 | */ 11 | async function downloadComments (url, articleId, prev = 0) { 12 | console.log('开始获取 ', url, '评论'); 13 | let commentsArr = []; 14 | let commentsTotal = 0; 15 | async function run (prev) { 16 | try { 17 | let res = await superagent.post(config.commentUrl) 18 | .set({ 19 | 'Content-Type': 'application/json', 20 | 'Cookie': config.cookie, 21 | 'Referer': url, 22 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36' 23 | }).send({ 24 | aid: articleId, 25 | prev: prev 26 | }); 27 | if (res.body && res.body.error && res.body.error.code){ 28 | console.log('error msg', res.body.error.msg); 29 | throw new Error(res.body.error.msg); 30 | }; 31 | let resData = res.body.data 32 | commentsTotal = resData.page.count; 33 | let nextPage = resData.page.more; 34 | commentsArr.push(...resData.list); 35 | if (nextPage) { 36 | prev = resData.list[resData.list.length -1].score; 37 | await utils.sleep(1); 38 | await run(prev); 39 | }; 40 | }catch (err){ 41 | console.log(`获取 评论 ${url} err`, err.message); 42 | }; 43 | }; 44 | await run(prev); 45 | // console.log('commentsArr', commentsArr); 46 | // console.log('commentsTotal', commentsTotal); 47 | console.log('结束获取 ', url, '评论 总评论数为', commentsTotal); 48 | return {commentsArr, commentsTotal}; 49 | }; 50 | 51 | // downloadComments('https://time.geekbang.org/column/article/82337',82337); 52 | 53 | module.exports = downloadComments; -------------------------------------------------------------------------------- /generaterPdf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成 PDF 的工具方法 3 | */ 4 | const puppeteer = require('puppeteer'); 5 | // const devices = require('puppeteer/DeviceDescriptors'); 6 | const path = require('path'); 7 | // const iPhone = devices['iPhone 6']; 8 | const utils = require('./utils'); 9 | const config = require('./config.js'); 10 | 11 | /** 12 | * 生成 pdf 13 | * @param {Object} data 生产 pdf 需要的文章数据 14 | * @param {String} filename 生成 pdf 的名称 15 | * @param {dirname} dirname 生成 pdf 存放的文件夹 名称 16 | * @param {Oject} options ejs 渲染模版的配置项 17 | */ 18 | async function generaterPdf (data, filename = 'example.pdf', dirname = __dirname, options = {}) { 19 | console.log('generater pdf start'); 20 | // console.log('data', JSON.stringify(data)); 21 | let browser, page; 22 | try { 23 | browser = await puppeteer.launch({ 24 | // headless: false 25 | }); 26 | page = await browser.newPage(); 27 | 28 | await page.setContent(utils.renderEjsArticle2Html(data, options)); 29 | 30 | await utils.sleep(1); 31 | await page.pdf({path: path.resolve(dirname, filename)}); 32 | console.log('generater pdf success'); 33 | // 关闭浏览器资源 34 | await page.close(); 35 | await browser.close(); 36 | }catch (err){ 37 | console.log('generater pdf err', err); 38 | page ? await page.close(): ''; 39 | browser ? await browser.close(): ''; 40 | }; 41 | }; 42 | 43 | /** 44 | generaterPdf({ 45 | "articleTitle": "08 | 玩转鼠标操作", 46 | "articalUrl": "https://time.geekbang.org/column/article/40650", 47 | "articleCtime": "2018-10-2", 48 | "articleCover": "https://static001.geekbang.org/resource/image/d5/c7/d5b9907b8e3e860cd16f5297d82336c7.jpg", 49 | "authorName": "吕鹏", 50 | "articleNeighbors": { 51 | "left": { 52 | "article_title": "37 | 插件开发(六):VS Code插件维护和发布要点", 53 | "id": 71299 54 | }, 55 | "right": [] 56 | }, 57 | "articleContent": "

之前我一直说VS Code 非常注重编码中的键盘体验。换句话来说就是,VS Code 非常关心你能不能够使用键盘完成绝大部分操作,从而无需将手从键盘上移动到别的输入设备上。但是这并不意味着 VS Code 只关心键盘的使用体验,毕竟大部分用户都是非常熟悉和喜欢使用鼠标的,更不要说 Mac 上让人无法割舍的触控板体验了。

\n

那今天我们就一起来聊一聊,如何在 VS Code 中通过鼠标或者触控设备来完成代码阅读和编辑操作。在今天的内容中,我们使用鼠标来代表各种类似鼠标的触控设备。

\n

文本选择

\n

首先我们来看一下如何使用鼠标快速地选择文本。最简单的方式,也是我们每个人最熟悉的方式,就是按住鼠标左键,然后拖动鼠标,直到选中所有我们想要选择的文字为止,再松开鼠标即可。

\n

我们在前面的章节学习过,如何通过键盘快捷键快速选中单词行和全文,这在很大程度上提高了我们的编码效率。那是不是说鼠标用户要完成类似的操作,就只能“一点、二拖、三松手”呢?当然不是,VS Code 其实给鼠标也配备了类似的快捷键。

\n

我们继续使用一段 JavaScript 代码来举例,如下:

\n
function foo() {\n bar("Hello World");\n}\n\nfoo()\n\nfunction bar() {\n \n}\n
\n

这段代码还是很简单的,你可以把他复制到你的编辑器中和我一起练习。

\n

在VS Code中,你单击鼠标左键就可以把光标移动到相应的位置。而双击鼠标左键,则会将当前光标下的单词选中。连续三次按下鼠标左键,则会选中当前这一行代码。最后是连续四次按下鼠标左键,则会选中整个文档。

\n

\"\"

\n
通过鼠标左键完成选中操作
\n

到这里你可能会问,如果我想要使用鼠标,选中其中的多行代码该怎么办?VS Code也考虑到了这个情况,在编辑器的最左边,显示的是每一行的行号。如果你单击行号,就能够直接选中这一行。如果你在某个行号上按下鼠标,然后上下移动,则能够选中多行代码。

\n

\"\"

\n
拖动行号栏,选中指定代码行
\n

文本编辑

\n

在 VS Code中,我们除了能够使用鼠标来选择文本以外,还能够使用鼠标对文本进行一定程度的修改,我们把它称为拖放功能(drag and drop)

\n

比如在今天的示例代码中,我们选中 bar 这个函数,然后将鼠标移到这段选中的代码之上,按下鼠标左键不松开。这时你可以看到,鼠标指针已经从一条竖线,变成了一个箭头。这时候我们移动鼠标的话,就可以把这段文本拖拽到我们想要的位置。

\n

在移动的过程当中,我们能够在编辑器中看到一个由虚线构成的光标,当我们松开鼠标左键的时候,这段文本就会被移动到这个虚拟的光标所在的位置。

\n

\"\"

\n
通过鼠标左键移动代码位置
\n

在上面的动图里,我们把 bar这个函数,从文档的末尾移动到了第四行。这个功能就相当于使用键盘进行的“剪切+粘贴”。

\n

那么能不能使用鼠标进行“复制+粘贴”呢?别担心,VS Code 肯定也会考虑到这个情况的,所以答案是:必须能。

\n

如果我们在拖拽这段文本的同时,按下 Option 键(Windows 上是 Ctrl 键),鼠标指针上会多一个加号,这时候我们再移动鼠标或虚拟光标至我们想要的位置,然后当我们松开鼠标左键的时候,这段文本将会被复制粘贴到虚拟光标所在的位置,也就是我们既定的目标位置。

\n

你看,在移动鼠标的过程中,多按了个 Option 键(Windows 上是 Ctrl 键),操作结果就由原来的“剪切+粘贴”变为“复制+粘贴”了。

\n

\"\"

\n
鼠标左键拖拽+Option键,复制粘贴代码块
\n

多光标

\n

在前面第6篇文章中,我们已经学习了如何使用鼠标添加多光标。不得不承认,在鼠标的帮助下,多光标的创建显得格外便捷。我们只需按下 Option 键,然后在需要创建新光标的地方,按下鼠标左键即可。简言之,就是按住 Option 键,然后哪里需要点哪里。

\n

不过,VS Code 中还有一个更加便捷的鼠标创建多光标的方式。当然,这首先要求你的鼠标拥有中键。你只需按下鼠标中键,然后对着一段文档拖出一个框,在这个框中的代码就都被选中了,而且每一行被选中的代码,都拥有一个独立的光标。

\n

\"\"

\n
利用鼠标中键添加多光标
\n

在第6篇文章中,我们已经尝试了用多种方法去创建光标然后修改代码,现在我们又为鼠标用户多提供了一种更为便捷的操作方式。尝试掌握它们吧,我相信这些便捷操作肯定能为你的高效编程之路尽一份力的。

\n

悬停提示窗口

\n

相信你在 VS Code 的编辑器里使用鼠标的过程中,早就发现了,当你的鼠标移动到某些文本上之后,稍待片刻就能看到一个悬停提示窗口。这个窗口里会显示跟鼠标下文本相关的信息。

\n

比如,在我们的示例代码中,当我们把鼠标移动到第五行 foo 上后,悬停提示窗口里展示了 foo的类型信息,它告诉我们 foo是一个函数,不需要任何的参数,返回值是 void

\n

\"\"

\n
了解函数的类型信息
\n

如果我们把鼠标移动到 foo 上面时,按下 Cmd 键(Windows 上是 Ctrl),则能够在悬停提示窗口里直接看到 foo的实现。

\n

\"\"

\n
按下Cmd键,辅助以鼠标,查看函数实现
\n

我们能看到这样的信息,是因为这个功能也被包含在了 VS Code 的语言接口之中。VS Code 会告诉语言服务,当前鼠标所在位置的信息,语言服务会根据当前的项目情况和代码提供有用的信息。

\n

在 JavaScript 或者 Java 这样的编程语言中,当我们把鼠标移动到某个变量上时,我们能够看到这个变量的定义信息。而在 CSS 中,当我们把鼠标移动到一个 CSS 规则上时,我们能看到的则是一段能够让这个 CSS 规则生效的 HTML 的样例代码。

\n

\"\"

\n
了解CSS对应的HTML代码样例
\n

当然,除了语言服务,任何 VS Code 上的插件都能够控制悬浮窗口里的内容。

\n

代码跳转和链接

\n

除了能够使用鼠标进行代码选择、编辑、预览之外,我们还可以借助鼠标来完成跳转操作。不知道你还记得我们之前讲的文件、代码跳转相关的快捷键吗?如果不记得,一定要回去再复习哦。如果记得,今天在这里我再教你如何使用鼠标来完成跳转操作。

\n

我们还是把鼠标移动到示例代码的第五行 foo 上,然后按下 Cmd 键,这时候 foo下面出现了一个下划线。然后当我们按下鼠标左键,就跳转到了 foo函数的定义处。

\n

\"\"

\n
通过Cmd键和鼠标左键,跳转到函数定义处
\n

当我们在编写 Markdown 这样的非编程语言的文档时,也可以通过 Cmd + 鼠标左键(Windows 上是 Ctrl + 鼠标左键)来打开超级链接。

\n

\"\"

\n
通过Cmd键和鼠标左键,打开超级链接
\n

小结

\n

以上就是 VS Code 编辑器中常用的鼠标操作,相对比较简单,当然这并不是全部。之后的章节中,在介绍 VS Code 的其他 UI 组件时,我也还会介绍相应的鼠标快捷操作。

\n

这一节的内容你还是需要多加练习,然后希望它们能成为你的肌肉记忆。这样,不管你使用任何编辑器,在有鼠标操作的时候,你都可以试试上面的小技巧是否生效。

\n

最后,欢迎在评论区给我留言,你可以分享对于鼠标操作,你自己珍藏的小技能。当然,有疑问同样可以写到留言区,我会第一时间给你反馈。

\n
\n

\"\"

\n", 58 | "audioDownloadUrl": "https://res001.geekbang.org/resource/audio/a3/d6/a31321bec73bafeef366f50ae1ac71d6.mp3", 59 | "audioTitle": "吕鹏8_01_01" 60 | }, '08 | 玩转鼠标操作.pdf', path.resolve(__dirname, config.columnName)); 61 | */ 62 | 63 | module.exports = generaterPdf; -------------------------------------------------------------------------------- /image/geektime_VScode_filelist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjeejj/geektime2pdf/53433e3f0212845f4be085379e516553de283c72/image/geektime_VScode_filelist.png -------------------------------------------------------------------------------- /image/geektime_file_content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjeejj/geektime2pdf/53433e3f0212845f4be085379e516553de283c72/image/geektime_file_content.png -------------------------------------------------------------------------------- /image/geektime_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jjeejj/geektime2pdf/53433e3f0212845f4be085379e516553de283c72/image/geektime_run.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geektime2pdf", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async-limiter": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 10 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 11 | }, 12 | "asynckit": { 13 | "version": "0.4.0", 14 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 15 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 16 | }, 17 | "balanced-match": { 18 | "version": "1.0.0", 19 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 20 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 21 | }, 22 | "brace-expansion": { 23 | "version": "1.1.11", 24 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 25 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 26 | "requires": { 27 | "balanced-match": "^1.0.0", 28 | "concat-map": "0.0.1" 29 | } 30 | }, 31 | "buffer-from": { 32 | "version": "1.1.1", 33 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 34 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 35 | }, 36 | "combined-stream": { 37 | "version": "1.0.7", 38 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", 39 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", 40 | "requires": { 41 | "delayed-stream": "~1.0.0" 42 | } 43 | }, 44 | "component-emitter": { 45 | "version": "1.2.1", 46 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 47 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 48 | }, 49 | "concat-map": { 50 | "version": "0.0.1", 51 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 52 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 53 | }, 54 | "concat-stream": { 55 | "version": "1.6.2", 56 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 57 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 58 | "requires": { 59 | "buffer-from": "^1.0.0", 60 | "inherits": "^2.0.3", 61 | "readable-stream": "^2.2.2", 62 | "typedarray": "^0.0.6" 63 | } 64 | }, 65 | "cookiejar": { 66 | "version": "2.1.2", 67 | "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", 68 | "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" 69 | }, 70 | "core-util-is": { 71 | "version": "1.0.2", 72 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 73 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 74 | }, 75 | "debug": { 76 | "version": "4.1.1", 77 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 78 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 79 | "requires": { 80 | "ms": "^2.1.1" 81 | } 82 | }, 83 | "delayed-stream": { 84 | "version": "1.0.0", 85 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 86 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 87 | }, 88 | "ejs": { 89 | "version": "2.6.1", 90 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", 91 | "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" 92 | }, 93 | "es6-promise": { 94 | "version": "4.2.6", 95 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", 96 | "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" 97 | }, 98 | "es6-promisify": { 99 | "version": "5.0.0", 100 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 101 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 102 | "requires": { 103 | "es6-promise": "^4.0.3" 104 | } 105 | }, 106 | "extract-zip": { 107 | "version": "1.6.7", 108 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", 109 | "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", 110 | "requires": { 111 | "concat-stream": "1.6.2", 112 | "debug": "2.6.9", 113 | "mkdirp": "0.5.1", 114 | "yauzl": "2.4.1" 115 | }, 116 | "dependencies": { 117 | "debug": { 118 | "version": "2.6.9", 119 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 120 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 121 | "requires": { 122 | "ms": "2.0.0" 123 | } 124 | }, 125 | "ms": { 126 | "version": "2.0.0", 127 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 128 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 129 | } 130 | } 131 | }, 132 | "fd-slicer": { 133 | "version": "1.0.1", 134 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", 135 | "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", 136 | "requires": { 137 | "pend": "~1.2.0" 138 | } 139 | }, 140 | "form-data": { 141 | "version": "2.3.3", 142 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 143 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 144 | "requires": { 145 | "asynckit": "^0.4.0", 146 | "combined-stream": "^1.0.6", 147 | "mime-types": "^2.1.12" 148 | } 149 | }, 150 | "formidable": { 151 | "version": "1.2.1", 152 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", 153 | "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" 154 | }, 155 | "fs.realpath": { 156 | "version": "1.0.0", 157 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 158 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 159 | }, 160 | "glob": { 161 | "version": "7.1.3", 162 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 163 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 164 | "requires": { 165 | "fs.realpath": "^1.0.0", 166 | "inflight": "^1.0.4", 167 | "inherits": "2", 168 | "minimatch": "^3.0.4", 169 | "once": "^1.3.0", 170 | "path-is-absolute": "^1.0.0" 171 | } 172 | }, 173 | "https-proxy-agent": { 174 | "version": "2.2.4", 175 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", 176 | "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", 177 | "requires": { 178 | "agent-base": "^4.3.0", 179 | "debug": "^3.1.0" 180 | }, 181 | "dependencies": { 182 | "agent-base": { 183 | "version": "4.3.0", 184 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", 185 | "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", 186 | "requires": { 187 | "es6-promisify": "^5.0.0" 188 | } 189 | }, 190 | "debug": { 191 | "version": "3.2.6", 192 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 193 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 194 | "requires": { 195 | "ms": "^2.1.1" 196 | } 197 | } 198 | } 199 | }, 200 | "inflight": { 201 | "version": "1.0.6", 202 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 203 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 204 | "requires": { 205 | "once": "^1.3.0", 206 | "wrappy": "1" 207 | } 208 | }, 209 | "inherits": { 210 | "version": "2.0.3", 211 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 212 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 213 | }, 214 | "isarray": { 215 | "version": "1.0.0", 216 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 217 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 218 | }, 219 | "methods": { 220 | "version": "1.1.2", 221 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 222 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 223 | }, 224 | "mime": { 225 | "version": "2.4.0", 226 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", 227 | "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" 228 | }, 229 | "mime-db": { 230 | "version": "1.38.0", 231 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", 232 | "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" 233 | }, 234 | "mime-types": { 235 | "version": "2.1.22", 236 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", 237 | "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", 238 | "requires": { 239 | "mime-db": "~1.38.0" 240 | } 241 | }, 242 | "minimatch": { 243 | "version": "3.0.4", 244 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 245 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 246 | "requires": { 247 | "brace-expansion": "^1.1.7" 248 | } 249 | }, 250 | "minimist": { 251 | "version": "0.0.8", 252 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 253 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 254 | }, 255 | "mkdirp": { 256 | "version": "0.5.1", 257 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 258 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 259 | "requires": { 260 | "minimist": "0.0.8" 261 | } 262 | }, 263 | "ms": { 264 | "version": "2.1.1", 265 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 266 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 267 | }, 268 | "once": { 269 | "version": "1.4.0", 270 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 271 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 272 | "requires": { 273 | "wrappy": "1" 274 | } 275 | }, 276 | "path-is-absolute": { 277 | "version": "1.0.1", 278 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 279 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 280 | }, 281 | "pend": { 282 | "version": "1.2.0", 283 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 284 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" 285 | }, 286 | "process-nextick-args": { 287 | "version": "2.0.0", 288 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 289 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 290 | }, 291 | "progress": { 292 | "version": "2.0.3", 293 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 294 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 295 | }, 296 | "proxy-from-env": { 297 | "version": "1.0.0", 298 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", 299 | "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" 300 | }, 301 | "puppeteer": { 302 | "version": "1.14.0", 303 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.14.0.tgz", 304 | "integrity": "sha512-SayS2wUX/8LF8Yo2Rkpc5nkAu4Jg3qu+OLTDSOZtisVQMB2Z5vjlY2TdPi/5CgZKiZroYIiyUN3sRX63El9iaw==", 305 | "requires": { 306 | "debug": "^4.1.0", 307 | "extract-zip": "^1.6.6", 308 | "https-proxy-agent": "^2.2.1", 309 | "mime": "^2.0.3", 310 | "progress": "^2.0.1", 311 | "proxy-from-env": "^1.0.0", 312 | "rimraf": "^2.6.1", 313 | "ws": "^6.1.0" 314 | } 315 | }, 316 | "qs": { 317 | "version": "6.7.0", 318 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 319 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 320 | }, 321 | "readable-stream": { 322 | "version": "2.3.6", 323 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 324 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 325 | "requires": { 326 | "core-util-is": "~1.0.0", 327 | "inherits": "~2.0.3", 328 | "isarray": "~1.0.0", 329 | "process-nextick-args": "~2.0.0", 330 | "safe-buffer": "~5.1.1", 331 | "string_decoder": "~1.1.1", 332 | "util-deprecate": "~1.0.1" 333 | } 334 | }, 335 | "rimraf": { 336 | "version": "2.6.3", 337 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", 338 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", 339 | "requires": { 340 | "glob": "^7.1.3" 341 | } 342 | }, 343 | "safe-buffer": { 344 | "version": "5.1.2", 345 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 346 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 347 | }, 348 | "semver": { 349 | "version": "6.0.0", 350 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", 351 | "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" 352 | }, 353 | "string_decoder": { 354 | "version": "1.1.1", 355 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 356 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 357 | "requires": { 358 | "safe-buffer": "~5.1.0" 359 | } 360 | }, 361 | "superagent": { 362 | "version": "5.0.2", 363 | "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.0.2.tgz", 364 | "integrity": "sha512-CqeqvwByDJuLwhcO6NOSuPatyQOIZX/TlvD5GJnXg5tzBTth2xQGZGdAZdo/kX+BtzvwJFX2IGGczTZgEIT7Wg==", 365 | "requires": { 366 | "component-emitter": "^1.2.1", 367 | "cookiejar": "^2.1.2", 368 | "debug": "^4.1.1", 369 | "form-data": "^2.3.3", 370 | "formidable": "^1.2.1", 371 | "methods": "^1.1.2", 372 | "mime": "^2.4.0", 373 | "qs": "^6.7.0", 374 | "readable-stream": "^3.2.0", 375 | "semver": "^6.0.0" 376 | }, 377 | "dependencies": { 378 | "readable-stream": { 379 | "version": "3.3.0", 380 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", 381 | "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", 382 | "requires": { 383 | "inherits": "^2.0.3", 384 | "string_decoder": "^1.1.1", 385 | "util-deprecate": "^1.0.1" 386 | } 387 | } 388 | } 389 | }, 390 | "typedarray": { 391 | "version": "0.0.6", 392 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 393 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 394 | }, 395 | "util-deprecate": { 396 | "version": "1.0.2", 397 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 398 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 399 | }, 400 | "wrappy": { 401 | "version": "1.0.2", 402 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 403 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 404 | }, 405 | "ws": { 406 | "version": "6.2.1", 407 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", 408 | "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", 409 | "requires": { 410 | "async-limiter": "~1.0.0" 411 | } 412 | }, 413 | "yauzl": { 414 | "version": "2.4.1", 415 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", 416 | "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", 417 | "requires": { 418 | "fd-slicer": "~1.0.1" 419 | } 420 | } 421 | } 422 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geektime2pdf", 3 | "version": "1.0.0", 4 | "description": "极客时间专栏文章 转为 PDF", 5 | "main": "columnArticleList.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "jjeejj", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ejs": "^2.6.1", 14 | "puppeteer": "^1.14.0", 15 | "superagent": "^5.0.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /template/article.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 12 | 14 | 16 | 18 | 19 | 21 | 22 | 极客时间 | <%= articleTitle %> 23 | 2606 | 2607 | 2608 | 2609 | 3016 |
3017 |
3018 |
3019 |
3020 |
3021 |
3022 |
3023 |

3024 | <%= articleTitle %> 3025 |

3026 |
<%= articleCtime %> <%= authorName %> 3027 |
3028 |
3029 | 3030 |
3035 |
3036 |
3037 |
3038 | <%- articleContent %> 3039 |
3040 |
3041 |
© 版权归极客邦科技所有,未经许可不得传播售卖。 3042 | 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。 3043 |
3044 |
3045 |
3046 | <% if (articleNeighbors.left && articleNeighbors.left.id) { %> 3047 |
3048 |
上一篇
3049 |
<%= articleNeighbors.left.article_title %>
3050 |
3051 | <% } %> 3052 | <% if (articleNeighbors.right && articleNeighbors.right.id) { %> 3053 |
3054 |
下一篇
3055 |
<%= articleNeighbors.right.article_title %>
3056 |
3057 | <% } %> 3058 |
3059 |
3060 |

精选留言<%- commentsTotal %>

3061 |
    3062 | <%for(let i=0;i 3063 |
  • 3064 | 3065 |
    3066 |
    3067 |
    3068 |
    3069 | <%- commentsArr[i].user_name %> 3070 |
    3071 |
    <%- commentsArr[i].comment_ctime %>
    3072 |
    3073 |
    3074 |
    <%- commentsArr[i].comment_content %>
    3075 | <% if(commentsArr[i].replies) { %> 3076 | <% for(let j=0;j 3077 |
    3078 |

    <%- commentsArr[i].replies[j].user_name %> <%- commentsArr[i].replies[j].content %>

    3079 |
    3080 | <% } %> 3081 | <% } %> 3082 |
    3083 |
  • 3084 | <%}%> 3085 |
3086 |
3087 |
3088 |
3089 |
3090 | 3091 | 3092 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具方法 3 | */ 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const ejs = require('ejs'); 7 | /** 8 | * 暂停几秒 9 | */ 10 | async function sleep(time) { 11 | return new Promise((resolve) => { 12 | setTimeout(resolve, time * 1000); 13 | }); 14 | }; 15 | /** 16 | * 创建单文件夹 17 | * 如果传入的参数是相对路径,在当前目录创建 18 | * 如果是绝对路径,就直接创建 19 | */ 20 | function createDir (dir){ 21 | return new Promise((resolve) => { 22 | if(!dir) throw '请传入一个文件夹名称'; 23 | if(!path.isAbsolute(dir)) { 24 | dir = path.resolve(__dirname, dir); 25 | }; 26 | fs.stat(dir, (err, stat) => { 27 | if (err){ // 不存在进行创建 28 | fs.mkdirSync(dir); 29 | }else{ 30 | console.log('该文件夹已经存在',dir); 31 | }; 32 | resolve(); 33 | }); 34 | }); 35 | }; 36 | 37 | /** 38 | * 向指定的文件夹写入文件 39 | * @param {string} dir 需要写入的文件夹名称, 相对于项目目录的路径 40 | */ 41 | function writeToFile (dir, content){ 42 | fs.writeFileSync(path.resolve(__dirname, dir, './articleInfoList.json'), content); 43 | }; 44 | 45 | /** 46 | * 传进来的是unix 时间戳 单位 为 s 47 | * 返回对应的年月日 48 | * @param {Number} unixTime 49 | * @returns YYYY-MM-dd 50 | */ 51 | function formatDate (unixTime) { 52 | let date = new Date(unixTime * 1000); 53 | return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate(); 54 | }; 55 | 56 | // ejs 文章模版 57 | let articleEjsTempate = fs.readFileSync('./template/article.ejs', 'utf8'); 58 | 59 | // console.log(articleEjsTempate); 60 | /** 61 | * 渲染 文章 ejs 模版 并返回 html 62 | * @param {Object} 渲染 ejs 需要的数据 63 | * @param {options} 渲染配置项 64 | * @returns {String} html 页面字符串 65 | */ 66 | function renderEjsArticle2Html(data, options) { 67 | try { 68 | let html = ejs.render(articleEjsTempate, data, options); 69 | return html; 70 | } catch(err){ 71 | throw err 72 | }; 73 | }; 74 | 75 | module.exports = { 76 | sleep, 77 | createDir, 78 | writeToFile, 79 | formatDate, 80 | renderEjsArticle2Html 81 | }; --------------------------------------------------------------------------------