yuque-dl 1.0.24 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/index-87c3a8dd.js +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/es/cli.js +1 -1
- package/dist/es/index-0527d511.js +1 -0
- package/dist/es/index.js +1 -1
- package/package.json +2 -2
- package/types/api.d.ts +8 -1
- package/types/cli.d.ts +2 -1
- package/types/constant.d.ts +5 -1
- package/types/index.d.ts +13 -2
- package/.eslintrc.cjs +0 -21
- package/.prettierrc.js +0 -7
- package/GET_TOEKN.md +0 -18
- package/dist/cjs/index-852c3568.js +0 -1
- package/dist/es/index-16978a9a.js +0 -1
- package/eslint.log +0 -0
- package/rollup.config.ts +0 -26
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
语雀知识库下载为本地markdown
|
|
4
4
|
|
|
5
|
-

|
|
5
|
+

|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -27,7 +27,8 @@ $ yuque-dl --help
|
|
|
27
27
|
Options:
|
|
28
28
|
-d, --distDir <dir> 下载的目录 eg: -d download (default: download)
|
|
29
29
|
-i, --ignoreImg 忽略图片不下载 (default: false)
|
|
30
|
-
-
|
|
30
|
+
-k, --key <key> 指定语雀的 cookie key,默认是 "_yuque_session"
|
|
31
|
+
-t, --token <token> 语雀的cookie key 对应的值
|
|
31
32
|
-h, --help Display this message
|
|
32
33
|
-v, --version Display version number
|
|
33
34
|
```
|
|
@@ -41,7 +42,7 @@ yuque-dl "https://www.yuque.com/yuque/thyzgp"
|
|
|
41
42
|
|
|
42
43
|
## Example
|
|
43
44
|
|
|
44
|
-

|
|
45
|
+

|
|
45
46
|
|
|
46
47
|
## 私有知识库
|
|
47
48
|
|
|
@@ -51,7 +52,15 @@ yuque-dl "https://www.yuque.com/yuque/thyzgp"
|
|
|
51
52
|
yuque-dl "https://www.yuque.com/yuque/thyzgp" -t "abcd..."
|
|
52
53
|
```
|
|
53
54
|
|
|
54
|
-
[token的获取请看](./GET_TOEKN.md)
|
|
55
|
+
[token的获取请看](./docs/GET_TOEKN.md)
|
|
56
|
+
|
|
57
|
+
## 企业私有服务
|
|
58
|
+
|
|
59
|
+
企业服务有自己的域名(黄色语雀logo),非`yuque.com`结尾, 如`https://yuque.antfin.com/r/zone`
|
|
60
|
+
|
|
61
|
+
这种情况 token的key不唯一, 不一定是为`_yuque_session` 需用户使用 `-v` 指定 token的key,`-t` 指定 token的值。
|
|
62
|
+
|
|
63
|
+
至于`key`具体是什么只能靠用户自己在 `浏览器Devtools-> Application -> Cookies` 里找了🤔
|
|
55
64
|
|
|
56
65
|
## Feature
|
|
57
66
|
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("node:fs"),r=require("cac"),o=require("./index-
|
|
1
|
+
"use strict";var e=require("node:fs"),r=require("cac"),o=require("./index-87c3a8dd.js");require("node:fs/promises"),require("pull-md-img"),require("ora"),require("cli-progress"),require("log4js"),require("axios"),require("rand-user-agent");var n="undefined"!=typeof document?document.currentScript:null;const i=r.cac("yuque-dl"),{version:t}=JSON.parse(e.readFileSync(new URL("../../package.json","undefined"==typeof document?require("url").pathToFileURL(__filename).href:n&&n.src||new URL("cli.js",document.baseURI).href)).toString());i.command("<url>","语雀知识库url").option("-d, --distDir <dir>","下载的目录 eg: -d download",{default:"download"}).option("-i, --ignoreImg","忽略图片不下载",{default:!1}).option("-k, --key <key>",'语雀的cookie key, 默认是 "_yuque_session", 在某些企业版本中 key 不一样').option("-t, --token <token>","语雀的cookie key 对应的值").action((async(e,r)=>{try{await o.main(e,r)}catch(e){o.logger.error(e.message||"unknown exception")}})),i.help(),i.version(t);try{i.parse()}catch(e){o.logger.error(e.message||"unknown exception"),process.exit(1)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var e=require("node:fs/promises"),t=require("pull-md-img"),r=require("ora"),o=require("cli-progress"),s=require("log4js"),i=require("axios"),n=require("rand-user-agent");var a,c,l=(s.configure({appenders:{cheese:{type:"console",layout:{type:"pattern",pattern:"%[%c [%p]:%] %m%n"}}},categories:{default:{appenders:["cheese"],level:"trace"}}}),s.getLogger("yuque-dl"));class u{constructor(e,t){this.bookPath="",this.progressFilePath="",this.progressInfo=[],this.curr=0,this.total=0,this.isDownloadInterrupted=!1,this.bar=null,this.completePromise=null,this.bookPath=e,this.progressFilePath=`${e}/progress.json`,this.total=t}async init(){this.progressInfo=await this.getProgress(),this.curr=this.progressInfo.length,this.curr!==this.total&&(this.curr>0&&this.curr!==this.total&&(this.isDownloadInterrupted=!0,l.info("根据上次数据继续断点下载")),this.bar=new o.SingleBar({format:"Download [{bar}] {percentage}% | {value}/{total}"},o.Presets.legacy),this.bar.start(this.total,this.curr))}async getProgress(){let t=[];try{const r=await e.readFile(this.progressFilePath,{encoding:"utf8"});t=JSON.parse(r)}catch(r){r&&"ENOENT"===r.code&&await e.writeFile(this.progressFilePath,JSON.stringify(t),{encoding:"utf8"})}return t}async updateProgress(t,r){this.curr=this.curr+1,r&&(this.progressInfo.push(t),await e.writeFile(this.progressFilePath,JSON.stringify(this.progressInfo),{encoding:"utf8"})),this.bar&&(this.bar.update(this.curr),this.curr>=this.total&&(this.bar.stop(),console.log("")))}pause(){this.bar&&this.bar.stop()}continue(){this.clearLine(2),this.bar?.start(this.total,this.curr)}clearLine(e){if(!(e<=0)&&"function"==typeof process?.stderr?.cursorTo){process.stderr.cursorTo(0);for(let t=0;t<e;t++)process.stderr.moveCursor(0,-1),process.stderr.clearLine(1)}}}class h{constructor(e){this.summaryInfo=e}async genFile(){const{bookName:t,bookDesc:r,bookPath:o,uuidMap:s}=this.summaryInfo;let i=`# ${t}\n\n`;r&&(i+=`> ${r}\n\n`);let n=i;const a=[];s.forEach((e=>{const t=e.toc,r=t.parent_uuid,o=this.findTree(a,r),s={text:t.title.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/,""),id:t.uuid,level:1,type:""},i=t.type.toLocaleLowerCase();"title"===i||""!==t.child_uuid?(s.type="title","boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s))):(s.type="link",s.link="link"===i?e.toc.url:e.path,"boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s)))}));n+=this.genSummaryContent(a,"");try{await e.writeFile(`${o}/SUMMARY.md`,n)}catch(e){l.error("Generate Summary Error")}}genSummaryContent(e,t){for(const r of e){if("title"===r.type)t+=`\n${"".padStart(r.level+1,"#")} ${r.text}\n\n`;else if("link"===r.type){const e=r.link?r.link.replace(/\s/g,"%20"):r.link;t+=`- [${r.text}](${e})\n`}Array.isArray(r.children)&&(t+=this.genSummaryContent(r.children,""))}return t}findIdItem(e,t){if(e.id===t)return e;if(e.children){const r=this.findTree(e.children,t);if(r)return r}return!1}findTree(e,t){if(!t)return!1;for(const r of e){const e=this.findIdItem(r,t);if(e)return e}return!1}}!function(e){e.TITLE="title",e.LINK="link",e.DOC="doc"}(a||(a={})),function(e){e.BOARD="board",e.TABLE="table",e.SHEET="sheet",e.DOC="doc"}(c||(c={}));const d=new Map([[c.BOARD,"画板类型"],[c.TABLE,"数据表类型"],[c.SHEET,"表格类型"],[c.DOC,"文档类型"]]),p="_yuque_session",g="https://www.yuque.com";function f({browser:e="chrome",os:t="mac os",device:r="desktop"}){r=r.toLowerCase(),e=e.toLowerCase(),t=t.toLowerCase();let o=n(r,e,t);if("chrome"===e)for(;o.includes("Chrome-Lighthouse")||o.includes("Gener8")||o.includes("HeadlessChrome")||o.includes("SMTBot");)o=n(r,e,t);if("safari"===e)for(;o.includes("Applebot");)o=n(r,e,t);return o}function w(e){const{key:t=p,token:r}=e,o={"user-agent":f({browser:"chrome",device:"desktop"})};return r&&(o.cookie=`${t}=${r};`),o}function k(e){return{headers:w(e),beforeRedirect:t=>{t.headers={...t?.headers||{},...w(e)}}}}const m=e=>{const{articleUrl:t,bookId:r,token:o,key:s,host:n=g}=e;let a=`${n}/api/docs/${t}`;const c={book_id:String(r),merge_dynamic_data:String(!1),mode:"markdown"},l=new URLSearchParams(c).toString();return a=`${a}?${l}`,i.get(a,k({token:o,key:s})).then((({data:e,status:t})=>({apiUrl:a,httpStatus:t,response:e})))};async function y(o){const{articleInfo:s,progressBar:i,token:n,key:a}=o,{bookId:l,itemUrl:u,savePath:h,saveFilePath:p,uuid:g,articleUrl:f,articleTitle:w,ignoreImg:k,host:y}=s,{httpStatus:b,apiUrl:$,response:I}=await m({articleUrl:u,bookId:l,token:n,host:y,key:a}),L=I?.data?.type?.toLocaleLowerCase();if([c.BOARD,c.SHEET,c.TABLE].includes(L)){const e=d.get(L);throw new Error(`download article Error: 暂不支持“${e}”的文档`)}if("string"!=typeof I?.data?.sourcecode)throw new Error(`download article Error: ${$}, http status ${b}`);let E=I.data.sourcecode;if(!k){i.pause(),console.log("");const e=r({text:`下载 "${w}" 的图片中...`});e.start();try{E=await t.run(E,{dist:h,imgDir:`./img/${g}`,isIgnoreConsole:!0})}catch(e){let t=`download article image Error: ${e.message}`;throw e.error&&e.url&&(t=`download article image Error ${e.url}: ${e.error?.message}`),new Error(t)}finally{e.stop(),i.continue()}}E=E.replace(/<br(\s?)\/>/gm,"\n"),w&&(E=`# ${w}\n\x3c!--page header--\x3e\n\n${E}\n\n`),f&&(E+=`\x3c!--page footer--\x3e\n- 原文: <${f}>`);try{return await e.writeFile(p,E),!0}catch(e){throw new Error(`download article Error ${f}: ${e.message}`)}}function b(e){if(!e)return"";return e.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/g,"")}exports.downloadArticle=y,exports.logger=l,exports.main=async function(t,r){const{bookId:o,tocList:s,bookName:n,bookDesc:c,bookSlug:d,host:p}=await((e,t)=>{const r=/decodeURIComponent\("(.+)"\)\);/m;return i.get(e,k(t)).then((({data:e="",status:t})=>200===t?e:"")).then((e=>{const t=r.exec(e)??"";if(!t[1])return{};const o=JSON.parse(decodeURIComponent(t[1]));return o.book?{bookId:o.book.id,bookSlug:o.book.slug,tocList:o.book.toc||[],bookName:o.book.name||"",bookDesc:o.book.description||"",host:o.space?.host||g}:{}}))})(t,{token:r.token,key:r.key});if(!o)throw new Error("No found book id");if(!s||0===s.length)throw new Error("No found toc list");const f=`${r.distDir}/${n?b(n):o}`;await e.mkdir(f,{recursive:!0});const w=s.length,m=new u(f,w);if(await m.init(),m.curr===w)return m.bar&&m.bar.stop(),void l.info(`√ 已完成: ${process.cwd()}/${f}`);const $=new Map;m.isDownloadInterrupted&&m.progressInfo.forEach((e=>{$.set(e.toc.uuid,e)}));const I=t.replace(new RegExp(`(.*?/${d}).*`),"$1");await async function(t){const{articleUrlPrefix:r,total:o,uuidMap:s,tocList:i,bookPath:n,bookId:c,progressBar:u,host:h,options:d}=t;let p=0,g=0,f=0;const w=[],k=[];for(let t=0;t<o;t++){const o=i[t];if("string"!=typeof o.type)continue;if(s.get(o.uuid))continue;const l=o.type.toLocaleLowerCase();if(l===a.TITLE||""!==o.child_uuid||l===a.LINK){let t=o;const r=[],i=[];for(;t;)r.unshift(b(t.title)),i.unshift(t.uuid),t=s.get(t.parent_uuid)?s.get(t.parent_uuid).toc:void 0;const c={path:r.join("/"),pathTitleList:r,pathIdList:i,toc:o};l===a.LINK?(f+=1,k.push(c)):await e.mkdir(`${n}/${r.join("/")}`,{recursive:!0}),s.set(o.uuid,c),await u.updateProgress(c,l!==a.LINK)}else if(o.url){g+=1;let e={path:"",pathTitleList:[],pathIdList:[]};s.get(o.parent_uuid)&&(e=s.get(o.parent_uuid));const t=b(o.title),i=[...e.pathTitleList,`${t}.md`],a=[...e.pathIdList,o.uuid],l={path:i.join("/"),pathTitleList:i,pathIdList:a,toc:o};let f=!0;const k=`${r}/${o.url}`;try{const t={bookId:c,itemUrl:o.url,savePath:`${n}/${e.path}`,saveFilePath:`${n}/${l.path}`,uuid:o.uuid,articleUrl:k,articleTitle:o.title,ignoreImg:d.ignoreImg,host:h};await y({articleInfo:t,progressBar:u,token:d.token,key:d.key})}catch(e){f=!1,p+=1,w.push({articleUrl:k,errItem:l,errMsg:e.message,err:e})}s.set(o.uuid,l),await u.updateProgress(l,f)}}if(f>0){l.warn("该知识库存在以下外链文章");for(const e of k)l.warn(`———— ✕ ${e.path} ${e.toc.url}`)}if(p>0){l.error(`本次执行总数${g}篇,✕ 失败${p}篇`);for(const e of w)l.error(`${e.errItem.path} ———— ${e.articleUrl}`),l.error(`———— ✕ ${e.errMsg}`);l.error("o(╥﹏╥)o 由于网络波动或链接失效以上下载失败,可重新执行命令重试(PS:不会影响已下载成功的数据)")}}({articleUrlPrefix:I,total:w,uuidMap:$,tocList:s,bookPath:f,bookId:o,progressBar:m,host:p,options:r});const L=new h({bookPath:f,bookName:n,bookDesc:c,uuidMap:$});await L.genFile();const E=process.cwd();l.info(`√ 生成目录 ${E}/${f}/SUMMARY.md`),m.curr===w&&l.info(`√ 已完成: ${E}/${f}`),process.exit(0)};
|
package/dist/cjs/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";require("node:fs/promises"),require("pull-md-img"),require("ora");var r=require("./index-
|
|
1
|
+
"use strict";require("node:fs/promises"),require("pull-md-img"),require("ora");var r=require("./index-87c3a8dd.js");require("cli-progress"),require("log4js"),require("axios"),require("rand-user-agent"),exports.downloadArticle=r.downloadArticle,exports.main=r.main;
|
package/dist/es/cli.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as o}from"node:fs";import{cac as
|
|
1
|
+
import{readFileSync as o}from"node:fs";import{cac as e}from"cac";import{m as r,l as i}from"./index-0527d511.js";import"node:fs/promises";import"pull-md-img";import"ora";import"cli-progress";import"log4js";import"axios";import"rand-user-agent";const t=e("yuque-dl"),{version:n}=JSON.parse(o(new URL("../../package.json",import.meta.url)).toString());t.command("<url>","语雀知识库url").option("-d, --distDir <dir>","下载的目录 eg: -d download",{default:"download"}).option("-i, --ignoreImg","忽略图片不下载",{default:!1}).option("-k, --key <key>",'语雀的cookie key, 默认是 "_yuque_session", 在某些企业版本中 key 不一样').option("-t, --token <token>","语雀的cookie key 对应的值").action((async(o,e)=>{try{await r(o,e)}catch(o){i.error(o.message||"unknown exception")}})),t.help(),t.version(n);try{t.parse()}catch(o){i.error(o.message||"unknown exception"),process.exit(1)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t,{writeFile as e,mkdir as r}from"node:fs/promises";import o from"pull-md-img";import s from"ora";import i from"cli-progress";import n from"log4js";import a from"axios";import c from"rand-user-agent";var l,u,h=(n.configure({appenders:{cheese:{type:"console",layout:{type:"pattern",pattern:"%[%c [%p]:%] %m%n"}}},categories:{default:{appenders:["cheese"],level:"trace"}}}),n.getLogger("yuque-dl"));class d{constructor(t,e){this.bookPath="",this.progressFilePath="",this.progressInfo=[],this.curr=0,this.total=0,this.isDownloadInterrupted=!1,this.bar=null,this.completePromise=null,this.bookPath=t,this.progressFilePath=`${t}/progress.json`,this.total=e}async init(){this.progressInfo=await this.getProgress(),this.curr=this.progressInfo.length,this.curr!==this.total&&(this.curr>0&&this.curr!==this.total&&(this.isDownloadInterrupted=!0,h.info("根据上次数据继续断点下载")),this.bar=new i.SingleBar({format:"Download [{bar}] {percentage}% | {value}/{total}"},i.Presets.legacy),this.bar.start(this.total,this.curr))}async getProgress(){let e=[];try{const r=await t.readFile(this.progressFilePath,{encoding:"utf8"});e=JSON.parse(r)}catch(r){r&&"ENOENT"===r.code&&await t.writeFile(this.progressFilePath,JSON.stringify(e),{encoding:"utf8"})}return e}async updateProgress(e,r){this.curr=this.curr+1,r&&(this.progressInfo.push(e),await t.writeFile(this.progressFilePath,JSON.stringify(this.progressInfo),{encoding:"utf8"})),this.bar&&(this.bar.update(this.curr),this.curr>=this.total&&(this.bar.stop(),console.log("")))}pause(){this.bar&&this.bar.stop()}continue(){this.clearLine(2),this.bar?.start(this.total,this.curr)}clearLine(t){if(!(t<=0)&&"function"==typeof process?.stderr?.cursorTo){process.stderr.cursorTo(0);for(let e=0;e<t;e++)process.stderr.moveCursor(0,-1),process.stderr.clearLine(1)}}}class p{constructor(t){this.summaryInfo=t}async genFile(){const{bookName:e,bookDesc:r,bookPath:o,uuidMap:s}=this.summaryInfo;let i=`# ${e}\n\n`;r&&(i+=`> ${r}\n\n`);let n=i;const a=[];s.forEach((t=>{const e=t.toc,r=e.parent_uuid,o=this.findTree(a,r),s={text:e.title.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/,""),id:e.uuid,level:1,type:""},i=e.type.toLocaleLowerCase();"title"===i||""!==e.child_uuid?(s.type="title","boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s))):(s.type="link",s.link="link"===i?t.toc.url:t.path,"boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s)))}));n+=this.genSummaryContent(a,"");try{await t.writeFile(`${o}/SUMMARY.md`,n)}catch(t){h.error("Generate Summary Error")}}genSummaryContent(t,e){for(const r of t){if("title"===r.type)e+=`\n${"".padStart(r.level+1,"#")} ${r.text}\n\n`;else if("link"===r.type){const t=r.link?r.link.replace(/\s/g,"%20"):r.link;e+=`- [${r.text}](${t})\n`}Array.isArray(r.children)&&(e+=this.genSummaryContent(r.children,""))}return e}findIdItem(t,e){if(t.id===e)return t;if(t.children){const r=this.findTree(t.children,e);if(r)return r}return!1}findTree(t,e){if(!e)return!1;for(const r of t){const t=this.findIdItem(r,e);if(t)return t}return!1}}!function(t){t.TITLE="title",t.LINK="link",t.DOC="doc"}(l||(l={})),function(t){t.BOARD="board",t.TABLE="table",t.SHEET="sheet",t.DOC="doc"}(u||(u={}));const f=new Map([[u.BOARD,"画板类型"],[u.TABLE,"数据表类型"],[u.SHEET,"表格类型"],[u.DOC,"文档类型"]]),g="_yuque_session",m="https://www.yuque.com";function w({browser:t="chrome",os:e="mac os",device:r="desktop"}){r=r.toLowerCase(),t=t.toLowerCase(),e=e.toLowerCase();let o=c(r,t,e);if("chrome"===t)for(;o.includes("Chrome-Lighthouse")||o.includes("Gener8")||o.includes("HeadlessChrome")||o.includes("SMTBot");)o=c(r,t,e);if("safari"===t)for(;o.includes("Applebot");)o=c(r,t,e);return o}function k(t){const{key:e=g,token:r}=t,o={"user-agent":w({browser:"chrome",device:"desktop"})};return r&&(o.cookie=`${e}=${r};`),o}function y(t){return{headers:k(t),beforeRedirect:e=>{e.headers={...e?.headers||{},...k(t)}}}}const b=t=>{const{articleUrl:e,bookId:r,token:o,key:s,host:i=m}=t;let n=`${i}/api/docs/${e}`;const c={book_id:String(r),merge_dynamic_data:String(!1),mode:"markdown"},l=new URLSearchParams(c).toString();return n=`${n}?${l}`,a.get(n,y({token:o,key:s})).then((({data:t,status:e})=>({apiUrl:n,httpStatus:e,response:t})))};async function $(t){const{articleInfo:r,progressBar:i,token:n,key:a}=t,{bookId:c,itemUrl:l,savePath:h,saveFilePath:d,uuid:p,articleUrl:g,articleTitle:m,ignoreImg:w,host:k}=r,{httpStatus:y,apiUrl:$,response:I}=await b({articleUrl:l,bookId:c,token:n,host:k,key:a}),L=I?.data?.type?.toLocaleLowerCase();if([u.BOARD,u.SHEET,u.TABLE].includes(L)){const t=f.get(L);throw new Error(`download article Error: 暂不支持“${t}”的文档`)}if("string"!=typeof I?.data?.sourcecode)throw new Error(`download article Error: ${$}, http status ${y}`);let E=I.data.sourcecode;if(!w){i.pause(),console.log("");const t=s({text:`下载 "${m}" 的图片中...`});t.start();try{E=await o.run(E,{dist:h,imgDir:`./img/${p}`,isIgnoreConsole:!0})}catch(t){let e=`download article image Error: ${t.message}`;throw t.error&&t.url&&(e=`download article image Error ${t.url}: ${t.error?.message}`),new Error(e)}finally{t.stop(),i.continue()}}E=E.replace(/<br(\s?)\/>/gm,"\n"),m&&(E=`# ${m}\n\x3c!--page header--\x3e\n\n${E}\n\n`),g&&(E+=`\x3c!--page footer--\x3e\n- 原文: <${g}>`);try{return await e(d,E),!0}catch(t){throw new Error(`download article Error ${g}: ${t.message}`)}}function I(t){if(!t)return"";return t.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/g,"")}async function L(t,e){const{bookId:o,tocList:s,bookName:i,bookDesc:n,bookSlug:c,host:u}=await((t,e)=>{const r=/decodeURIComponent\("(.+)"\)\);/m;return a.get(t,y(e)).then((({data:t="",status:e})=>200===e?t:"")).then((t=>{const e=r.exec(t)??"";if(!e[1])return{};const o=JSON.parse(decodeURIComponent(e[1]));return o.book?{bookId:o.book.id,bookSlug:o.book.slug,tocList:o.book.toc||[],bookName:o.book.name||"",bookDesc:o.book.description||"",host:o.space?.host||m}:{}}))})(t,{token:e.token,key:e.key});if(!o)throw new Error("No found book id");if(!s||0===s.length)throw new Error("No found toc list");const f=`${e.distDir}/${i?I(i):o}`;await r(f,{recursive:!0});const g=s.length,w=new d(f,g);if(await w.init(),w.curr===g)return w.bar&&w.bar.stop(),void h.info(`√ 已完成: ${process.cwd()}/${f}`);const k=new Map;w.isDownloadInterrupted&&w.progressInfo.forEach((t=>{k.set(t.toc.uuid,t)}));const b=t.replace(new RegExp(`(.*?/${c}).*`),"$1");await async function(t){const{articleUrlPrefix:e,total:o,uuidMap:s,tocList:i,bookPath:n,bookId:a,progressBar:c,host:u,options:d}=t;let p=0,f=0,g=0;const m=[],w=[];for(let t=0;t<o;t++){const o=i[t];if("string"!=typeof o.type)continue;if(s.get(o.uuid))continue;const h=o.type.toLocaleLowerCase();if(h===l.TITLE||""!==o.child_uuid||h===l.LINK){let t=o;const e=[],i=[];for(;t;)e.unshift(I(t.title)),i.unshift(t.uuid),t=s.get(t.parent_uuid)?s.get(t.parent_uuid).toc:void 0;const a={path:e.join("/"),pathTitleList:e,pathIdList:i,toc:o};h===l.LINK?(g+=1,w.push(a)):await r(`${n}/${e.join("/")}`,{recursive:!0}),s.set(o.uuid,a),await c.updateProgress(a,h!==l.LINK)}else if(o.url){f+=1;let t={path:"",pathTitleList:[],pathIdList:[]};s.get(o.parent_uuid)&&(t=s.get(o.parent_uuid));const r=I(o.title),i=[...t.pathTitleList,`${r}.md`],l=[...t.pathIdList,o.uuid],h={path:i.join("/"),pathTitleList:i,pathIdList:l,toc:o};let g=!0;const w=`${e}/${o.url}`;try{const e={bookId:a,itemUrl:o.url,savePath:`${n}/${t.path}`,saveFilePath:`${n}/${h.path}`,uuid:o.uuid,articleUrl:w,articleTitle:o.title,ignoreImg:d.ignoreImg,host:u};await $({articleInfo:e,progressBar:c,token:d.token,key:d.key})}catch(t){g=!1,p+=1,m.push({articleUrl:w,errItem:h,errMsg:t.message,err:t})}s.set(o.uuid,h),await c.updateProgress(h,g)}}if(g>0){h.warn("该知识库存在以下外链文章");for(const t of w)h.warn(`———— ✕ ${t.path} ${t.toc.url}`)}if(p>0){h.error(`本次执行总数${f}篇,✕ 失败${p}篇`);for(const t of m)h.error(`${t.errItem.path} ———— ${t.articleUrl}`),h.error(`———— ✕ ${t.errMsg}`);h.error("o(╥﹏╥)o 由于网络波动或链接失效以上下载失败,可重新执行命令重试(PS:不会影响已下载成功的数据)")}}({articleUrlPrefix:b,total:g,uuidMap:k,tocList:s,bookPath:f,bookId:o,progressBar:w,host:u,options:e});const L=new p({bookPath:f,bookName:i,bookDesc:n,uuidMap:k});await L.genFile();const E=process.cwd();h.info(`√ 生成目录 ${E}/${f}/SUMMARY.md`),w.curr===g&&h.info(`√ 已完成: ${E}/${f}`),process.exit(0)}export{$ as d,h as l,L as m};
|
package/dist/es/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"node:fs/promises";import"pull-md-img";import"ora";export{d as downloadArticle,m as main}from"./index-
|
|
1
|
+
import"node:fs/promises";import"pull-md-img";import"ora";export{d as downloadArticle,m as main}from"./index-0527d511.js";import"cli-progress";import"log4js";import"axios";import"rand-user-agent";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yuque-dl",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "yuque 知识库下载",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"cli-progress": "^3.12.0",
|
|
62
62
|
"log4js": "^6.9.1",
|
|
63
63
|
"ora": "^7.0.1",
|
|
64
|
-
"pull-md-img": "^0.0.
|
|
64
|
+
"pull-md-img": "^0.0.26",
|
|
65
65
|
"rand-user-agent": "1.0.109"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
package/types/api.d.ts
CHANGED
|
@@ -6,14 +6,21 @@ interface IKnowledgeBaseInfo {
|
|
|
6
6
|
tocList?: KnowledgeBase.Toc[];
|
|
7
7
|
bookName?: string;
|
|
8
8
|
bookDesc?: string;
|
|
9
|
+
host?: string;
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
+
interface GetHeaderParams {
|
|
12
|
+
key?: string;
|
|
13
|
+
token?: string;
|
|
14
|
+
}
|
|
15
|
+
type TGetKnowledgeBaseInfo = (url: string, headerParams: GetHeaderParams) => Promise<IKnowledgeBaseInfo>;
|
|
11
16
|
/** 获取知识库数据信息 */
|
|
12
17
|
export declare const getKnowledgeBaseInfo: TGetKnowledgeBaseInfo;
|
|
13
18
|
interface GetMdDataParams {
|
|
14
19
|
articleUrl: string;
|
|
15
20
|
bookId: number;
|
|
21
|
+
host?: string;
|
|
16
22
|
token?: string;
|
|
23
|
+
key?: string;
|
|
17
24
|
}
|
|
18
25
|
interface IGetDocsMdDataRes {
|
|
19
26
|
apiUrl: string;
|
package/types/cli.d.ts
CHANGED
package/types/constant.d.ts
CHANGED
|
@@ -16,4 +16,8 @@ declare enum ARTICLE_CONTENT_TYPE {
|
|
|
16
16
|
DOC = "doc"
|
|
17
17
|
}
|
|
18
18
|
declare const articleContentMap: Map<ARTICLE_CONTENT_TYPE, string>;
|
|
19
|
-
|
|
19
|
+
/** 默认语雀cookie KEY */
|
|
20
|
+
declare const DEFAULT_COOKIE_KEY = "_yuque_session";
|
|
21
|
+
/** 默认语雀域名 */
|
|
22
|
+
declare const DEFAULT_DOMAIN = "https://www.yuque.com";
|
|
23
|
+
export { ARTICLE_TOC_TYPE, ARTICLE_CONTENT_TYPE, articleContentMap, DEFAULT_COOKIE_KEY, DEFAULT_DOMAIN };
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ProgressBar from './ProgressBar';
|
|
2
2
|
import type { IOptions } from './cli';
|
|
3
|
-
interface
|
|
3
|
+
interface ArticleInfo {
|
|
4
4
|
bookId: number;
|
|
5
5
|
itemUrl: string;
|
|
6
6
|
savePath: string;
|
|
@@ -9,8 +9,19 @@ interface IDownloadArticleParams {
|
|
|
9
9
|
articleTitle: string;
|
|
10
10
|
articleUrl: string;
|
|
11
11
|
ignoreImg: boolean;
|
|
12
|
+
host?: string;
|
|
13
|
+
}
|
|
14
|
+
interface DownloadArticleParams {
|
|
15
|
+
/** 文章信息 */
|
|
16
|
+
articleInfo: ArticleInfo;
|
|
17
|
+
/** 进度条实例 */
|
|
18
|
+
progressBar: ProgressBar;
|
|
19
|
+
/** 语雀cookie token的值 */
|
|
20
|
+
token?: string;
|
|
21
|
+
/** 语雀cookie token的key */
|
|
22
|
+
key?: string;
|
|
12
23
|
}
|
|
13
24
|
/** 下载单篇文章 */
|
|
14
|
-
declare function downloadArticle(params:
|
|
25
|
+
declare function downloadArticle(params: DownloadArticleParams): Promise<boolean>;
|
|
15
26
|
declare function main(url: string, options: IOptions): Promise<void>;
|
|
16
27
|
export { downloadArticle, main };
|
package/.eslintrc.cjs
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
env: {
|
|
3
|
-
node: true,
|
|
4
|
-
},
|
|
5
|
-
parser: '@typescript-eslint/parser',
|
|
6
|
-
plugins: [
|
|
7
|
-
'@typescript-eslint',
|
|
8
|
-
],
|
|
9
|
-
extends: [
|
|
10
|
-
'eslint:recommended',
|
|
11
|
-
'plugin:@typescript-eslint/recommended',
|
|
12
|
-
],
|
|
13
|
-
rules: {
|
|
14
|
-
semi: ['error', 'never'],
|
|
15
|
-
'no-console': 'off',
|
|
16
|
-
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
|
|
17
|
-
// TODO
|
|
18
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
19
|
-
'@typescript-eslint/no-non-null-assertion': 'off'
|
|
20
|
-
},
|
|
21
|
-
}
|
package/.prettierrc.js
DELETED
package/GET_TOEKN.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# 如何获取语雀的token
|
|
2
|
-
|
|
3
|
-
> 以chrome为例其他浏览器也类似
|
|
4
|
-
|
|
5
|
-
1. 登录语雀,浏览器右击菜单"检查"或点击快捷键 F12(Mac是Option+Command+J)
|
|
6
|
-
2. 退出控制台后点击 `Application`
|
|
7
|
-
3. 点击左侧`Cookies` 下的 `https://www.yuque.com`
|
|
8
|
-
4. 右侧列表中找到 `Name`为 `_yuque_session` 双击`Value`列复制 **Value的值**(也就是下面图片中绿色部分)
|
|
9
|
-
|
|
10
|
-

|
|
11
|
-
|
|
12
|
-
之后就可以在终端执行
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
yuque-dl "知识库的url" -t "复制的token"
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
> 🚨Tips: cookie为个人登录的信息,请勿泄露自己的cookie给其他人
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var t=require("node:fs/promises"),e=require("pull-md-img"),r=require("ora"),o=require("cli-progress"),s=require("log4js"),i=require("axios"),n=require("rand-user-agent");var a,c,l=(s.configure({appenders:{cheese:{type:"console",layout:{type:"pattern",pattern:"%[%c [%p]:%] %m%n"}}},categories:{default:{appenders:["cheese"],level:"trace"}}}),s.getLogger("yuque-dl"));class u{constructor(t,e){this.bookPath="",this.progressFilePath="",this.progressInfo=[],this.curr=0,this.total=0,this.isDownloadInterrupted=!1,this.bar=null,this.completePromise=null,this.bookPath=t,this.progressFilePath=`${t}/progress.json`,this.total=e}async init(){this.progressInfo=await this.getProgress(),this.curr=this.progressInfo.length,this.curr!==this.total&&(this.curr>0&&this.curr!==this.total&&(this.isDownloadInterrupted=!0,l.info("根据上次数据继续断点下载")),this.bar=new o.SingleBar({format:"Download [{bar}] {percentage}% | {value}/{total}"},o.Presets.legacy),this.bar.start(this.total,this.curr))}async getProgress(){let e=[];try{const r=await t.readFile(this.progressFilePath,{encoding:"utf8"});e=JSON.parse(r)}catch(r){r&&"ENOENT"===r.code&&await t.writeFile(this.progressFilePath,JSON.stringify(e),{encoding:"utf8"})}return e}async updateProgress(e,r){this.curr=this.curr+1,r&&(this.progressInfo.push(e),await t.writeFile(this.progressFilePath,JSON.stringify(this.progressInfo),{encoding:"utf8"})),this.bar&&(this.bar.update(this.curr),this.curr>=this.total&&(this.bar.stop(),console.log("")))}pause(){this.bar&&this.bar.stop()}continue(){this.clearLine(2),this.bar?.start(this.total,this.curr)}clearLine(t){if(!(t<=0)&&"function"==typeof process?.stderr?.cursorTo){process.stderr.cursorTo(0);for(let e=0;e<t;e++)process.stderr.moveCursor(0,-1),process.stderr.clearLine(1)}}}class d{constructor(t){this.summaryInfo=t}async genFile(){const{bookName:e,bookDesc:r,bookPath:o,uuidMap:s}=this.summaryInfo;let i=`# ${e}\n\n`;r&&(i+=`> ${r}\n\n`);let n=i;const a=[];s.forEach((t=>{const e=t.toc,r=e.parent_uuid,o=this.findTree(a,r),s={text:e.title.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/,""),id:e.uuid,level:1,type:""},i=e.type.toLocaleLowerCase();"title"===i||""!==e.child_uuid?(s.type="title","boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s))):(s.type="link",s.link="link"===i?t.toc.url:t.path,"boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s)))}));n+=this.genSummaryContent(a,"");try{await t.writeFile(`${o}/SUMMARY.md`,n)}catch(t){l.error("Generate Summary Error")}}genSummaryContent(t,e){for(const r of t){if("title"===r.type)e+=`\n${"".padStart(r.level+1,"#")} ${r.text}\n\n`;else if("link"===r.type){const t=r.link?r.link.replace(/\s/g,"%20"):r.link;e+=`- [${r.text}](${t})\n`}Array.isArray(r.children)&&(e+=this.genSummaryContent(r.children,""))}return e}findIdItem(t,e){if(t.id===e)return t;if(t.children){const r=this.findTree(t.children,e);if(r)return r}return!1}findTree(t,e){if(!e)return!1;for(const r of t){const t=this.findIdItem(r,e);if(t)return t}return!1}}!function(t){t.TITLE="title",t.LINK="link",t.DOC="doc"}(a||(a={})),function(t){t.BOARD="board",t.TABLE="table",t.SHEET="sheet",t.DOC="doc"}(c||(c={}));const h=new Map([[c.BOARD,"画板类型"],[c.TABLE,"数据表类型"],[c.SHEET,"表格类型"],[c.DOC,"文档类型"]]);function p({browser:t="chrome",os:e="mac os",device:r="desktop"}){r=r.toLowerCase(),t=t.toLowerCase(),e=e.toLowerCase();let o=n(r,t,e);if("chrome"===t)for(;o.includes("Chrome-Lighthouse")||o.includes("Gener8")||o.includes("HeadlessChrome")||o.includes("SMTBot");)o=n(r,t,e);if("safari"===t)for(;o.includes("Applebot");)o=n(r,t,e);return o}function g(t){const e={"user-agent":p({browser:"chrome",device:"desktop"})};return t&&(e.cookie=`_yuque_session=${t}`),e}const f=t=>{const{articleUrl:e,bookId:r,token:o}=t;let s=`https://www.yuque.com/api/docs/${e}`;const n={book_id:String(r),merge_dynamic_data:String(!1),mode:"markdown"},a=new URLSearchParams(n).toString();return s=`${s}?${a}`,i.get(s,{headers:g(o)}).then((({data:t,status:e})=>({apiUrl:s,httpStatus:e,response:t})))};async function w(o,s,i){const{bookId:n,itemUrl:a,savePath:l,saveFilePath:u,uuid:d,articleUrl:p,articleTitle:g,ignoreImg:w}=o,{httpStatus:m,apiUrl:b,response:$}=await f({articleUrl:a,bookId:n,token:i}),k=$?.data?.type?.toLocaleLowerCase();if([c.BOARD,c.SHEET,c.TABLE].includes(k)){const t=h.get(k);throw new Error(`download article Error: 暂不支持“${t}”的文档`)}if("string"!=typeof $?.data?.sourcecode)throw new Error(`download article Error: ${b}, http status ${m}`);let y=$.data.sourcecode;if(!w){s.pause(),console.log("");const t=r({text:`下载 "${g}" 的图片中...`});t.start();try{y=await e.run(y,{dist:l,imgDir:`./img/${d}`,isIgnoreConsole:!0})}catch(t){let e=`download article image Error: ${t.message}`;throw t.error&&t.url&&(e=`download article image Error ${t.url}: ${t.error?.message}`),new Error(e)}finally{t.stop(),s.continue()}}y=y.replace(/<br(\s?)\/>/gm,"\n"),g&&(y=`# ${g}\n\x3c!--page header--\x3e\n\n${y}\n\n`),p&&(y+=`\x3c!--page footer--\x3e\n- 原文: <${p}>`);try{return await t.writeFile(u,y),!0}catch(t){throw new Error(`download article Error ${p}: ${t.message}`)}}function m(t){if(!t)return"";return t.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/g,"")}exports.downloadArticle=w,exports.logger=l,exports.main=async function(e,r){const{bookId:o,tocList:s,bookName:n,bookDesc:c,bookSlug:h}=await((t,e)=>{const r=/decodeURIComponent\("(.+)"\)\);/m;return i.get(t,{headers:g(e)}).then((({data:t="",status:e})=>200===e?t:"")).then((t=>{const e=r.exec(t)??"";if(!e[1])return{};const o=JSON.parse(decodeURIComponent(e[1]));return o.book?{bookId:o.book.id,bookSlug:o.book.slug,tocList:o.book.toc||[],bookName:o.book.name||"",bookDesc:o.book.description||""}:{}}))})(e,r.token);if(!o)throw new Error("No found book id");if(!s||0===s.length)throw new Error("No found toc list");const p=`${r.distDir}/${n?m(n):o}`;await t.mkdir(p,{recursive:!0});const f=s.length,b=new u(p,f);if(await b.init(),b.curr===f)return b.bar&&b.bar.stop(),void l.info(`√ 已完成: ${process.cwd()}/${p}`);const $=new Map;b.isDownloadInterrupted&&b.progressInfo.forEach((t=>{$.set(t.toc.uuid,t)}));const k=e.replace(new RegExp(`(.*?/${h}).*`),"$1");await async function(e){const{articleUrlPrefix:r,total:o,uuidMap:s,tocList:i,bookPath:n,bookId:c,progressBar:u,options:d}=e;let h=0,p=0,g=0;const f=[],b=[];for(let e=0;e<o;e++){const o=i[e];if("string"!=typeof o.type)continue;if(s.get(o.uuid))continue;const l=o.type.toLocaleLowerCase();if(l===a.TITLE||""!==o.child_uuid||l===a.LINK){let e=o;const r=[],i=[];for(;e;)r.unshift(m(e.title)),i.unshift(e.uuid),e=s.get(e.parent_uuid)?s.get(e.parent_uuid).toc:void 0;const c={path:r.join("/"),pathTitleList:r,pathIdList:i,toc:o};l===a.LINK?(g+=1,b.push(c)):await t.mkdir(`${n}/${r.join("/")}`,{recursive:!0}),s.set(o.uuid,c),await u.updateProgress(c,l!==a.LINK)}else if(o.url){p+=1;let t={path:"",pathTitleList:[],pathIdList:[]};s.get(o.parent_uuid)&&(t=s.get(o.parent_uuid));const e=m(o.title),i=[...t.pathTitleList,`${e}.md`],a=[...t.pathIdList,o.uuid],l={path:i.join("/"),pathTitleList:i,pathIdList:a,toc:o};let g=!0;const b=`${r}/${o.url}`;try{await w({bookId:c,itemUrl:o.url,savePath:`${n}/${t.path}`,saveFilePath:`${n}/${l.path}`,uuid:o.uuid,articleUrl:b,articleTitle:o.title,ignoreImg:d.ignoreImg},u,d.token)}catch(t){g=!1,h+=1,f.push({articleUrl:b,errItem:l,errMsg:t.message,err:t})}s.set(o.uuid,l),await u.updateProgress(l,g)}}if(g>0){l.warn("该知识库存在以下外链文章");for(const t of b)l.warn(`———— ✕ ${t.path} ${t.toc.url}`)}if(h>0){l.error(`本次执行总数${p}篇,✕ 失败${h}篇`);for(const t of f)l.error(`${t.errItem.path} ———— ${t.articleUrl}`),l.error(`———— ✕ ${t.errMsg}`);l.error("o(╥﹏╥)o 由于网络波动或链接失效以上下载失败,可重新执行命令重试(PS:不会影响已下载成功的数据)")}}({articleUrlPrefix:k,total:f,uuidMap:$,tocList:s,bookPath:p,bookId:o,progressBar:b,options:r});const y=new d({bookPath:p,bookName:n,bookDesc:c,uuidMap:$});await y.genFile();const I=process.cwd();l.info(`√ 生成目录 ${I}/${p}/SUMMARY.md`),b.curr===f&&l.info(`√ 已完成: ${I}/${p}`),process.exit(0)};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import t,{writeFile as e,mkdir as r}from"node:fs/promises";import o from"pull-md-img";import s from"ora";import i from"cli-progress";import n from"log4js";import a from"axios";import c from"rand-user-agent";var l,u,d=(n.configure({appenders:{cheese:{type:"console",layout:{type:"pattern",pattern:"%[%c [%p]:%] %m%n"}}},categories:{default:{appenders:["cheese"],level:"trace"}}}),n.getLogger("yuque-dl"));class h{constructor(t,e){this.bookPath="",this.progressFilePath="",this.progressInfo=[],this.curr=0,this.total=0,this.isDownloadInterrupted=!1,this.bar=null,this.completePromise=null,this.bookPath=t,this.progressFilePath=`${t}/progress.json`,this.total=e}async init(){this.progressInfo=await this.getProgress(),this.curr=this.progressInfo.length,this.curr!==this.total&&(this.curr>0&&this.curr!==this.total&&(this.isDownloadInterrupted=!0,d.info("根据上次数据继续断点下载")),this.bar=new i.SingleBar({format:"Download [{bar}] {percentage}% | {value}/{total}"},i.Presets.legacy),this.bar.start(this.total,this.curr))}async getProgress(){let e=[];try{const r=await t.readFile(this.progressFilePath,{encoding:"utf8"});e=JSON.parse(r)}catch(r){r&&"ENOENT"===r.code&&await t.writeFile(this.progressFilePath,JSON.stringify(e),{encoding:"utf8"})}return e}async updateProgress(e,r){this.curr=this.curr+1,r&&(this.progressInfo.push(e),await t.writeFile(this.progressFilePath,JSON.stringify(this.progressInfo),{encoding:"utf8"})),this.bar&&(this.bar.update(this.curr),this.curr>=this.total&&(this.bar.stop(),console.log("")))}pause(){this.bar&&this.bar.stop()}continue(){this.clearLine(2),this.bar?.start(this.total,this.curr)}clearLine(t){if(!(t<=0)&&"function"==typeof process?.stderr?.cursorTo){process.stderr.cursorTo(0);for(let e=0;e<t;e++)process.stderr.moveCursor(0,-1),process.stderr.clearLine(1)}}}class p{constructor(t){this.summaryInfo=t}async genFile(){const{bookName:e,bookDesc:r,bookPath:o,uuidMap:s}=this.summaryInfo;let i=`# ${e}\n\n`;r&&(i+=`> ${r}\n\n`);let n=i;const a=[];s.forEach((t=>{const e=t.toc,r=e.parent_uuid,o=this.findTree(a,r),s={text:e.title.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/,""),id:e.uuid,level:1,type:""},i=e.type.toLocaleLowerCase();"title"===i||""!==e.child_uuid?(s.type="title","boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s))):(s.type="link",s.link="link"===i?t.toc.url:t.path,"boolean"!=typeof o?(Array.isArray(o.children)||(o.children=[]),s.level=o.level+1,o.children.push(s)):(s.level=1,a.push(s)))}));n+=this.genSummaryContent(a,"");try{await t.writeFile(`${o}/SUMMARY.md`,n)}catch(t){d.error("Generate Summary Error")}}genSummaryContent(t,e){for(const r of t){if("title"===r.type)e+=`\n${"".padStart(r.level+1,"#")} ${r.text}\n\n`;else if("link"===r.type){const t=r.link?r.link.replace(/\s/g,"%20"):r.link;e+=`- [${r.text}](${t})\n`}Array.isArray(r.children)&&(e+=this.genSummaryContent(r.children,""))}return e}findIdItem(t,e){if(t.id===e)return t;if(t.children){const r=this.findTree(t.children,e);if(r)return r}return!1}findTree(t,e){if(!e)return!1;for(const r of t){const t=this.findIdItem(r,e);if(t)return t}return!1}}!function(t){t.TITLE="title",t.LINK="link",t.DOC="doc"}(l||(l={})),function(t){t.BOARD="board",t.TABLE="table",t.SHEET="sheet",t.DOC="doc"}(u||(u={}));const f=new Map([[u.BOARD,"画板类型"],[u.TABLE,"数据表类型"],[u.SHEET,"表格类型"],[u.DOC,"文档类型"]]);function g({browser:t="chrome",os:e="mac os",device:r="desktop"}){r=r.toLowerCase(),t=t.toLowerCase(),e=e.toLowerCase();let o=c(r,t,e);if("chrome"===t)for(;o.includes("Chrome-Lighthouse")||o.includes("Gener8")||o.includes("HeadlessChrome")||o.includes("SMTBot");)o=c(r,t,e);if("safari"===t)for(;o.includes("Applebot");)o=c(r,t,e);return o}function m(t){const e={"user-agent":g({browser:"chrome",device:"desktop"})};return t&&(e.cookie=`_yuque_session=${t}`),e}const w=t=>{const{articleUrl:e,bookId:r,token:o}=t;let s=`https://www.yuque.com/api/docs/${e}`;const i={book_id:String(r),merge_dynamic_data:String(!1),mode:"markdown"},n=new URLSearchParams(i).toString();return s=`${s}?${n}`,a.get(s,{headers:m(o)}).then((({data:t,status:e})=>({apiUrl:s,httpStatus:e,response:t})))};async function b(t,r,i){const{bookId:n,itemUrl:a,savePath:c,saveFilePath:l,uuid:d,articleUrl:h,articleTitle:p,ignoreImg:g}=t,{httpStatus:m,apiUrl:b,response:$}=await w({articleUrl:a,bookId:n,token:i}),y=$?.data?.type?.toLocaleLowerCase();if([u.BOARD,u.SHEET,u.TABLE].includes(y)){const t=f.get(y);throw new Error(`download article Error: 暂不支持“${t}”的文档`)}if("string"!=typeof $?.data?.sourcecode)throw new Error(`download article Error: ${b}, http status ${m}`);let k=$.data.sourcecode;if(!g){r.pause(),console.log("");const t=s({text:`下载 "${p}" 的图片中...`});t.start();try{k=await o.run(k,{dist:c,imgDir:`./img/${d}`,isIgnoreConsole:!0})}catch(t){let e=`download article image Error: ${t.message}`;throw t.error&&t.url&&(e=`download article image Error ${t.url}: ${t.error?.message}`),new Error(e)}finally{t.stop(),r.continue()}}k=k.replace(/<br(\s?)\/>/gm,"\n"),p&&(k=`# ${p}\n\x3c!--page header--\x3e\n\n${k}\n\n`),h&&(k+=`\x3c!--page footer--\x3e\n- 原文: <${h}>`);try{return await e(l,k),!0}catch(t){throw new Error(`download article Error ${h}: ${t.message}`)}}function $(t){if(!t)return"";return t.replace(/[\\/:*?"<>|\n\r]/g,"_").replace(/\s/g,"")}async function y(t,e){const{bookId:o,tocList:s,bookName:i,bookDesc:n,bookSlug:c}=await((t,e)=>{const r=/decodeURIComponent\("(.+)"\)\);/m;return a.get(t,{headers:m(e)}).then((({data:t="",status:e})=>200===e?t:"")).then((t=>{const e=r.exec(t)??"";if(!e[1])return{};const o=JSON.parse(decodeURIComponent(e[1]));return o.book?{bookId:o.book.id,bookSlug:o.book.slug,tocList:o.book.toc||[],bookName:o.book.name||"",bookDesc:o.book.description||""}:{}}))})(t,e.token);if(!o)throw new Error("No found book id");if(!s||0===s.length)throw new Error("No found toc list");const u=`${e.distDir}/${i?$(i):o}`;await r(u,{recursive:!0});const f=s.length,g=new h(u,f);if(await g.init(),g.curr===f)return g.bar&&g.bar.stop(),void d.info(`√ 已完成: ${process.cwd()}/${u}`);const w=new Map;g.isDownloadInterrupted&&g.progressInfo.forEach((t=>{w.set(t.toc.uuid,t)}));const y=t.replace(new RegExp(`(.*?/${c}).*`),"$1");await async function(t){const{articleUrlPrefix:e,total:o,uuidMap:s,tocList:i,bookPath:n,bookId:a,progressBar:c,options:u}=t;let h=0,p=0,f=0;const g=[],m=[];for(let t=0;t<o;t++){const o=i[t];if("string"!=typeof o.type)continue;if(s.get(o.uuid))continue;const d=o.type.toLocaleLowerCase();if(d===l.TITLE||""!==o.child_uuid||d===l.LINK){let t=o;const e=[],i=[];for(;t;)e.unshift($(t.title)),i.unshift(t.uuid),t=s.get(t.parent_uuid)?s.get(t.parent_uuid).toc:void 0;const a={path:e.join("/"),pathTitleList:e,pathIdList:i,toc:o};d===l.LINK?(f+=1,m.push(a)):await r(`${n}/${e.join("/")}`,{recursive:!0}),s.set(o.uuid,a),await c.updateProgress(a,d!==l.LINK)}else if(o.url){p+=1;let t={path:"",pathTitleList:[],pathIdList:[]};s.get(o.parent_uuid)&&(t=s.get(o.parent_uuid));const r=$(o.title),i=[...t.pathTitleList,`${r}.md`],l=[...t.pathIdList,o.uuid],d={path:i.join("/"),pathTitleList:i,pathIdList:l,toc:o};let f=!0;const m=`${e}/${o.url}`;try{await b({bookId:a,itemUrl:o.url,savePath:`${n}/${t.path}`,saveFilePath:`${n}/${d.path}`,uuid:o.uuid,articleUrl:m,articleTitle:o.title,ignoreImg:u.ignoreImg},c,u.token)}catch(t){f=!1,h+=1,g.push({articleUrl:m,errItem:d,errMsg:t.message,err:t})}s.set(o.uuid,d),await c.updateProgress(d,f)}}if(f>0){d.warn("该知识库存在以下外链文章");for(const t of m)d.warn(`———— ✕ ${t.path} ${t.toc.url}`)}if(h>0){d.error(`本次执行总数${p}篇,✕ 失败${h}篇`);for(const t of g)d.error(`${t.errItem.path} ———— ${t.articleUrl}`),d.error(`———— ✕ ${t.errMsg}`);d.error("o(╥﹏╥)o 由于网络波动或链接失效以上下载失败,可重新执行命令重试(PS:不会影响已下载成功的数据)")}}({articleUrlPrefix:y,total:f,uuidMap:w,tocList:s,bookPath:u,bookId:o,progressBar:g,options:e});const k=new p({bookPath:u,bookName:i,bookDesc:n,uuidMap:w});await k.genFile();const I=process.cwd();d.info(`√ 生成目录 ${I}/${u}/SUMMARY.md`),g.curr===f&&d.info(`√ 已完成: ${I}/${u}`),process.exit(0)}export{b as d,d as l,y as m};
|
package/eslint.log
DELETED
|
File without changes
|
package/rollup.config.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'rollup'
|
|
2
|
-
import typescript from '@rollup/plugin-typescript'
|
|
3
|
-
import terser from '@rollup/plugin-terser'
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
input: {
|
|
7
|
-
index: 'src/index.ts',
|
|
8
|
-
cli: 'src/cli.ts'
|
|
9
|
-
},
|
|
10
|
-
output:[
|
|
11
|
-
{
|
|
12
|
-
format: "cjs",
|
|
13
|
-
dir: "dist/cjs"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
format: "es",
|
|
17
|
-
dir: "dist/es",
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
plugins: [
|
|
21
|
-
typescript(),
|
|
22
|
-
terser()
|
|
23
|
-
]
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
|