UNPKG

yuque2book

Version:

convert yuque book to a static book

245 lines (244 loc) 10.8 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var _this = this; Object.defineProperty(exports, "__esModule", { value: true }); var cheerio = require("cheerio"); var co = require("co"); var debug = require("debug"); var fs = require("fs-extra"); var _ = require("lodash"); var path = require("path"); var request = require("superagent"); var url_1 = require("url"); var log = debug("yuque2book"); // TODO: !! THE FILE TO BE TO REFACTOR AND TEST !! var reg = /.+\/(.+\.[a-zA-Z0-9]+).*$/; var yuqueUrlCheckReg = /^https:\/\/.*yuque\.com/; var pipePromise = function (reader, writer) { return new Promise(function (res, rej) { reader.pipe(writer); writer.on("end", res); writer.on("close", res); writer.on("err", rej); }); }; exports.localize = function (dir, folder, token) { return __awaiter(_this, void 0, void 0, function () { var base, list, _i, list_1, h, json, html, $, _html; return __generator(this, function (_a) { switch (_a.label) { case 0: base = path.join(folder); // make images dir return [4 /*yield*/, fs.ensureDir(path.join(base, "img"))]; case 1: // make images dir _a.sent(); return [4 /*yield*/, fs.ensureDir(path.join(base, "attach"))]; case 2: _a.sent(); return [4 /*yield*/, fs.readdir(base)]; case 3: list = _a.sent(); list = list.filter(function (name) { return name.endsWith(".json"); }); _i = 0, list_1 = list; _a.label = 4; case 4: if (!(_i < list_1.length)) return [3 /*break*/, 8]; h = list_1[_i]; json = JSON.parse(fs.readFileSync(path.join(base, h)).toString()); html = json.body_html; if (!html) { return [3 /*break*/, 7]; } $ = cheerio.load(html); // 保存图片 return [4 /*yield*/, saveFiles($, "img", "src", base, "img", undefined, null, token)]; case 5: // 保存图片 _a.sent(); // 保存附件, 并且只保存上传到语雀上的附件 return [4 /*yield*/, saveFiles($, "a", "href", base, "attach", yuqueUrlCheckReg, function (src) { return src.replace("/attachments/", "/api/v2/attachments/"); }, token)]; case 6: // 保存附件, 并且只保存上传到语雀上的附件 _a.sent(); toLocalUrl($, yuqueUrlCheckReg); _html = $("html").html(); fs.writeFileSync(path.join(base, h), JSON.stringify(__assign({}, json, { body_html: "<div>" + _html + "</div>" }), null, 2)); _a.label = 7; case 7: _i++; return [3 /*break*/, 4]; case 8: return [2 /*return*/, folder]; } }); }); }; /** * 将文件保存到本地 * @param {jQuery} $ * @param {string} tagName 标签名称 * @param {string} attr 标签的属性 * @param {string} base 存放的基本地址 * @param {string} folder 存放文件的文件夹 * @param {regexp} filter 文件地址筛选 * @param {function} replace src重命名 * @return {Promise} */ var saveFiles = function ($, tagName, attr, base, folder, filter, replace, token) { if (filter === void 0) { filter = null; } return co(function () { var $files, _i, $files_1, item, src, pathname, filename, fileSaveDir, targetUrl; return __generator(this, function (_a) { switch (_a.label) { case 0: $files = []; $(tagName).each(function (index, item) { $files.push(item); }); if (!$files.length) { return [2 /*return*/]; } _i = 0, $files_1 = $files; _a.label = 1; case 1: if (!(_i < $files_1.length)) return [3 /*break*/, 4]; item = $files_1[_i]; src = $(item).attr(attr); if (!src) { return [3 /*break*/, 3]; } if (filter && _.isRegExp(filter)) { if (!filter.test(src)) { return [3 /*break*/, 3]; } } pathname = url_1.parse(src).pathname; if (!pathname) { log(attr, "pathname can not be match", pathname); return [3 /*break*/, 3]; } filename = _.get(pathname.match(reg), "[1]"); if (!filename) { log(attr, " can not be match", src); return [3 /*break*/, 3]; } filename = filename.replace("/", "-"); filename = path.basename(decodeURIComponent(filename)); fileSaveDir = path.join(base, folder); if (!fs.existsSync(fileSaveDir)) { fs.mkdirpSync(fileSaveDir); } targetUrl = src; if (replace && typeof replace === "function") { targetUrl = replace(src); } log("download start", targetUrl); return [4 /*yield*/, pipePromise(request(targetUrl) .set("X-Auth-Token", token) .set("User-Agent", "gitlab-build-robot"), fs.createWriteStream(path.join(fileSaveDir, filename)))]; case 2: _a.sent(); log("create file", path.join(fileSaveDir, filename)); log("download finish", src); $(item).attr(attr, "data/" + folder + "/" + filename); _a.label = 3; case 3: _i++; return [3 /*break*/, 1]; case 4: return [2 /*return*/]; } }); }); }; /** * 将文档中的语雀url全部转换成本地的url * 所有的yuque的url,都会变成 otherBooks/${group}_${book}这样的形式 * 所以你的yuque文档如果外链了别的文档, 那么你就必须将另外一本book下载之后存放在这本book下 * TODO: 如果两本book嵌套链接怎么办? * @param $ * @param filter */ var toLocalUrl = function ($, filter) { var $links = []; $("a").each(function (index, item) { $links.push(item); }); for (var _i = 0, $links_1 = $links; _i < $links_1.length; _i++) { var link = $links_1[_i]; var src = $(link).attr("href"); if (!src) { continue; } // 若是mp4则跳过 if (/^https:\/\/.*yuque\.[^.]+\.com\/.*\.mp4$/.test(src)) { continue; } if (_.isRegExp(filter)) { if (!filter.test(src)) { continue; } } // ["https:", "", "yuque.com", "dtboost", "qd6g6q", "evcrbc"] var _a = src.split("/"), protocol = _a[0], white = _a[1], host = _a[2], group = _a[3], book = _a[4], page = _a[5]; log("url parser", protocol, white, host, group, book, page); if (!group) { continue; } var newLink = ""; if (page) { var links = page.split("#"); newLink += "#/" + links[0] + ".html?anchor=" + _.get(links, "[1]", ''); } $(link).attr("href", newLink); } // 订正链接卡片不跳转的问题 $("div[data-lake-card=\"yuque\"]").each(function (index, item) { $(item).find('a').attr('target', '_blank'); }); };