yuque2book
Version:
convert yuque book to a static book
245 lines (244 loc) • 10.8 kB
JavaScript
;
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');
});
};