@wenyan-md/core 2.0.7 → 3.0.0
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 +2 -2
- package/dist/publish.js +114 -254
- package/dist/types/node/clientPublish.d.ts +15 -0
- package/dist/types/node/configStore.d.ts +8 -7
- package/dist/types/node/publish.d.ts +6 -10
- package/dist/types/node/render.d.ts +5 -0
- package/dist/types/node/theme.d.ts +16 -0
- package/dist/types/node/tokenStoreNodeAdapter.d.ts +7 -0
- package/dist/types/node/types.d.ts +31 -0
- package/dist/types/node/uploadCacheNodeAdapter.d.ts +8 -0
- package/dist/types/node/utils.d.ts +14 -4
- package/dist/types/node/wrapper.d.ts +8 -10
- package/dist/types/publish.d.ts +22 -0
- package/dist/types/tokenStore.d.ts +24 -0
- package/dist/types/uploadCacheStore.d.ts +27 -0
- package/dist/types/wechat.d.ts +1 -1
- package/dist/wechat.js +5 -5
- package/dist/wrapper.js +656 -6
- package/package.json +6 -12
- package/dist/configStore-lZ5bhrcC.js +0 -111
- package/dist/http.js +0 -1
- package/dist/types/node/tokenStore.d.ts +0 -18
- package/dist/types/node/uploadCacheStore.d.ts +0 -15
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img alt="logo" src="https://raw.githubusercontent.com/caol64/wenyan/main/Data/256-mac.png" />
|
|
2
|
+
<img alt="logo" src="https://raw.githubusercontent.com/caol64/wenyan/main/Data/256-mac.png" width="128" />
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
# 文颜 CORE
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
## 简介
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**[文颜(Wenyan)](https://wenyan.yuzhi.tech/)** 是一套多平台 Markdown 排版与发布工具链, **文颜 CORE** 是其核心库,专注于:
|
|
15
15
|
|
|
16
16
|
- Markdown → HTML 渲染
|
|
17
17
|
- 主题排版(公众号 / Web)
|
package/dist/publish.js
CHANGED
|
@@ -1,301 +1,161 @@
|
|
|
1
|
-
import { JSDOM } from "jsdom";
|
|
2
|
-
import { fileFromPath } from "formdata-node/file-from-path";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { stat } from "node:fs/promises";
|
|
5
1
|
import { createWechatClient } from "./wechat.js";
|
|
6
|
-
|
|
7
|
-
import { FormData } from "formdata-node";
|
|
8
|
-
import { Readable } from "node:stream";
|
|
9
|
-
import fs from "node:fs";
|
|
10
|
-
import { c as configDir, e as ensureDir, s as safeReadJson, d as safeWriteJson, m as md5FromBuffer, f as md5FromFile } from "./configStore-lZ5bhrcC.js";
|
|
11
|
-
function normalizePath(p) {
|
|
12
|
-
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
13
|
-
}
|
|
14
|
-
function isAbsolutePath(path2) {
|
|
15
|
-
if (!path2) return false;
|
|
16
|
-
const winAbsPattern = /^[a-zA-Z]:\//;
|
|
17
|
-
const linuxAbsPattern = /^\//;
|
|
18
|
-
return winAbsPattern.test(path2) || linuxAbsPattern.test(path2);
|
|
19
|
-
}
|
|
20
|
-
const RuntimeEnv = {
|
|
21
|
-
isContainer: !!process.env.CONTAINERIZED,
|
|
22
|
-
hostFilePath: normalizePath(process.env.HOST_FILE_PATH || ""),
|
|
23
|
-
containerFilePath: normalizePath(process.env.CONTAINER_FILE_PATH || "/mnt/host-downloads"),
|
|
24
|
-
resolveLocalPath(inputPath, relativeBase) {
|
|
25
|
-
if (!this.isContainer) {
|
|
26
|
-
if (relativeBase) {
|
|
27
|
-
return path.resolve(relativeBase, inputPath);
|
|
28
|
-
} else {
|
|
29
|
-
if (!path.isAbsolute(inputPath)) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
`Invalid input: '${inputPath}'. When relativeBase is not provided, inputPath must be an absolute path.`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
return path.normalize(inputPath);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
let normalizedInput = normalizePath(inputPath);
|
|
38
|
-
relativeBase = normalizePath(relativeBase || "");
|
|
39
|
-
if (relativeBase) {
|
|
40
|
-
if (!isAbsolutePath(normalizedInput)) {
|
|
41
|
-
normalizedInput = relativeBase + (normalizedInput.startsWith("/") ? "" : "/") + normalizedInput;
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
if (!isAbsolutePath(normalizedInput)) {
|
|
45
|
-
throw new Error(
|
|
46
|
-
`Invalid input: '${inputPath}'. When relativeBase is not provided, inputPath must be an absolute path.`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (this.hostFilePath && normalizedInput.startsWith(this.hostFilePath)) {
|
|
51
|
-
let relativePart = normalizedInput.slice(this.hostFilePath.length);
|
|
52
|
-
if (relativePart && !relativePart.startsWith("/")) {
|
|
53
|
-
return normalizedInput;
|
|
54
|
-
}
|
|
55
|
-
if (!relativePart.startsWith("/")) {
|
|
56
|
-
relativePart = "/" + relativePart;
|
|
57
|
-
}
|
|
58
|
-
return this.containerFilePath + relativePart;
|
|
59
|
-
}
|
|
60
|
-
return normalizedInput;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
const nodeHttpAdapter = {
|
|
64
|
-
fetch,
|
|
65
|
-
createMultipart(field, file, filename) {
|
|
66
|
-
const form = new FormData();
|
|
67
|
-
form.append(field, file, filename);
|
|
68
|
-
const encoder = new FormDataEncoder(form);
|
|
69
|
-
return {
|
|
70
|
-
body: Readable.from(encoder),
|
|
71
|
-
headers: encoder.headers,
|
|
72
|
-
duplex: "half"
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
const tokenPath = path.join(configDir, "token.json");
|
|
77
|
-
const defaultCache = {
|
|
2
|
+
const defaultTokenCache = {
|
|
78
3
|
appid: "",
|
|
79
4
|
accessToken: "",
|
|
80
5
|
expireAt: 0
|
|
81
6
|
};
|
|
82
7
|
class TokenStore {
|
|
83
|
-
cache =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
8
|
+
cache = { ...defaultTokenCache };
|
|
9
|
+
adapter;
|
|
10
|
+
initPromise;
|
|
11
|
+
constructor(adapter) {
|
|
12
|
+
this.adapter = adapter;
|
|
13
|
+
this.initPromise = this.load();
|
|
14
|
+
}
|
|
15
|
+
async load() {
|
|
16
|
+
try {
|
|
17
|
+
const loadedData = await this.adapter.loadToken();
|
|
18
|
+
if (loadedData) {
|
|
19
|
+
this.cache = loadedData;
|
|
20
|
+
}
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(`无法加载 token: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
23
|
}
|
|
92
24
|
}
|
|
93
|
-
save() {
|
|
25
|
+
async save() {
|
|
94
26
|
try {
|
|
95
|
-
|
|
96
|
-
safeWriteJson(tokenPath, this.cache);
|
|
27
|
+
await this.adapter.saveToken(this.cache);
|
|
97
28
|
} catch (error) {
|
|
98
|
-
|
|
29
|
+
throw new Error(`无法保存 token: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
30
|
}
|
|
100
31
|
}
|
|
32
|
+
async waitForInit() {
|
|
33
|
+
await this.initPromise;
|
|
34
|
+
}
|
|
101
35
|
isValid(appid) {
|
|
102
|
-
|
|
103
|
-
|
|
36
|
+
const currentTime = Math.floor(Date.now() / 1e3);
|
|
37
|
+
const bufferTime = 600;
|
|
38
|
+
const isAppidMatch = this.cache.appid === appid;
|
|
39
|
+
const isNotExpired = this.cache.expireAt > currentTime + bufferTime;
|
|
40
|
+
return isAppidMatch && isNotExpired;
|
|
104
41
|
}
|
|
105
42
|
getToken(appid) {
|
|
106
43
|
return this.isValid(appid) ? this.cache.accessToken : null;
|
|
107
44
|
}
|
|
108
|
-
setToken(appid, accessToken, expiresIn) {
|
|
45
|
+
async setToken(appid, accessToken, expiresIn) {
|
|
46
|
+
await this.initPromise;
|
|
109
47
|
this.cache = {
|
|
110
48
|
appid,
|
|
111
49
|
accessToken,
|
|
50
|
+
// 计算绝对过期时间戳(秒)
|
|
112
51
|
expireAt: Math.floor(Date.now() / 1e3) + expiresIn
|
|
113
52
|
};
|
|
114
|
-
this.save();
|
|
53
|
+
await this.save();
|
|
115
54
|
}
|
|
116
|
-
clear() {
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
fs.unlinkSync(tokenPath);
|
|
121
|
-
}
|
|
122
|
-
} catch {
|
|
123
|
-
}
|
|
55
|
+
async clear() {
|
|
56
|
+
await this.initPromise;
|
|
57
|
+
this.cache = { ...defaultTokenCache };
|
|
58
|
+
await this.adapter.clearToken();
|
|
124
59
|
}
|
|
125
60
|
}
|
|
126
|
-
const tokenStore = new TokenStore();
|
|
127
|
-
const cachePath = path.join(configDir, "upload-cache.json");
|
|
128
61
|
class UploadCacheStore {
|
|
129
62
|
cache = {};
|
|
130
|
-
|
|
131
|
-
|
|
63
|
+
adapter;
|
|
64
|
+
initPromise;
|
|
65
|
+
constructor(adapter) {
|
|
66
|
+
this.adapter = adapter;
|
|
67
|
+
this.initPromise = this.load();
|
|
132
68
|
}
|
|
133
|
-
load() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
69
|
+
async load() {
|
|
70
|
+
try {
|
|
71
|
+
this.cache = await this.adapter.loadCache();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new Error(`无法加载上传缓存: ${error instanceof Error ? error.message : String(error)}`);
|
|
137
74
|
}
|
|
138
75
|
}
|
|
139
|
-
save() {
|
|
76
|
+
async save() {
|
|
140
77
|
try {
|
|
141
|
-
|
|
142
|
-
safeWriteJson(cachePath, this.cache);
|
|
78
|
+
await this.adapter.saveCache(this.cache);
|
|
143
79
|
} catch (error) {
|
|
144
|
-
|
|
80
|
+
throw new Error(`无法保存上传缓存: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
81
|
}
|
|
146
82
|
}
|
|
147
|
-
|
|
148
|
-
|
|
83
|
+
async waitForInit() {
|
|
84
|
+
await this.initPromise;
|
|
149
85
|
}
|
|
150
|
-
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
86
|
+
async get(hash) {
|
|
87
|
+
await this.initPromise;
|
|
88
|
+
return this.cache[hash];
|
|
153
89
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (imageUrl.startsWith("http")) {
|
|
163
|
-
const response = await fetch(imageUrl);
|
|
164
|
-
if (!response.ok || !response.body) {
|
|
165
|
-
throw new Error(`Failed to download image from URL: ${imageUrl}`);
|
|
166
|
-
}
|
|
167
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
168
|
-
if (arrayBuffer.byteLength === 0) {
|
|
169
|
-
throw new Error(`远程图片大小为0,无法上传: ${imageUrl}`);
|
|
170
|
-
}
|
|
171
|
-
const buffer = Buffer.from(arrayBuffer);
|
|
172
|
-
md5 = md5FromBuffer(buffer);
|
|
173
|
-
const cached = uploadCacheStore.get(md5);
|
|
174
|
-
if (cached) {
|
|
175
|
-
mediaIdMapping.set(cached.url, cached.media_id);
|
|
176
|
-
return {
|
|
177
|
-
media_id: cached.media_id,
|
|
178
|
-
url: cached.url
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const fileNameFromUrl = path.basename(imageUrl.split("?")[0]);
|
|
182
|
-
const ext = path.extname(fileNameFromUrl);
|
|
183
|
-
finalName = fileName ?? (ext === "" ? `${fileNameFromUrl}.jpg` : fileNameFromUrl);
|
|
184
|
-
const contentType = response.headers.get("content-type") || "image/jpeg";
|
|
185
|
-
fileData = new Blob([buffer], { type: contentType });
|
|
186
|
-
} else {
|
|
187
|
-
const resolvedPath = RuntimeEnv.resolveLocalPath(imageUrl, relativePath);
|
|
188
|
-
const stats = await stat(resolvedPath);
|
|
189
|
-
if (stats.size === 0) {
|
|
190
|
-
throw new Error(`本地图片大小为0,无法上传: ${resolvedPath}`);
|
|
191
|
-
}
|
|
192
|
-
md5 = md5FromFile(resolvedPath);
|
|
193
|
-
const cached = uploadCacheStore.get(md5);
|
|
194
|
-
if (cached) {
|
|
195
|
-
mediaIdMapping.set(cached.url, cached.media_id);
|
|
196
|
-
return {
|
|
197
|
-
media_id: cached.media_id,
|
|
198
|
-
url: cached.url
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
const fileNameFromLocal = path.basename(resolvedPath);
|
|
202
|
-
const ext = path.extname(fileNameFromLocal);
|
|
203
|
-
finalName = fileName ?? (ext === "" ? `${fileNameFromLocal}.jpg` : fileNameFromLocal);
|
|
204
|
-
const fileFromPathResult = await fileFromPath(resolvedPath);
|
|
205
|
-
fileData = new Blob([await fileFromPathResult.arrayBuffer()], { type: fileFromPathResult.type });
|
|
90
|
+
async set(hash, mediaId, url) {
|
|
91
|
+
await this.initPromise;
|
|
92
|
+
this.cache[hash] = {
|
|
93
|
+
media_id: mediaId,
|
|
94
|
+
url,
|
|
95
|
+
updated_at: Date.now()
|
|
96
|
+
};
|
|
97
|
+
await this.save();
|
|
206
98
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
async function uploadImages(content, accessToken, relativePath) {
|
|
213
|
-
if (!content.includes("<img")) {
|
|
214
|
-
return { html: content, firstImageId: "" };
|
|
99
|
+
async clear() {
|
|
100
|
+
await this.initPromise;
|
|
101
|
+
this.cache = {};
|
|
102
|
+
await this.adapter.clearCache();
|
|
215
103
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const images = Array.from(document.querySelectorAll("img"));
|
|
219
|
-
const uploadPromises = images.map(async (element) => {
|
|
220
|
-
const dataSrc = element.getAttribute("src");
|
|
221
|
-
if (dataSrc) {
|
|
222
|
-
if (!dataSrc.startsWith("https://mmbiz.qpic.cn")) {
|
|
223
|
-
const resp = await uploadImage(dataSrc, accessToken, void 0, relativePath);
|
|
224
|
-
element.setAttribute("src", resp.url);
|
|
225
|
-
return resp.media_id;
|
|
226
|
-
} else {
|
|
227
|
-
return dataSrc;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return null;
|
|
231
|
-
});
|
|
232
|
-
const mediaIds = (await Promise.all(uploadPromises)).filter(Boolean);
|
|
233
|
-
const firstImageId = mediaIds[0] || "";
|
|
234
|
-
const updatedHtml = dom.serialize();
|
|
235
|
-
return { html: updatedHtml, firstImageId };
|
|
236
|
-
}
|
|
237
|
-
async function publishToWechatDraft(articleOptions, publishOptions = {}) {
|
|
238
|
-
const { title, content, cover, author, source_url } = articleOptions;
|
|
239
|
-
const { appId, appSecret, relativePath } = publishOptions;
|
|
240
|
-
const appIdFinal = appId ?? process.env.WECHAT_APP_ID;
|
|
241
|
-
const appSecretFinal = appSecret ?? process.env.WECHAT_APP_SECRET;
|
|
242
|
-
if (!appIdFinal || !appSecretFinal) {
|
|
243
|
-
throw new Error("请通过参数或环境变量 WECHAT_APP_ID / WECHAT_APP_SECRET 提供公众号凭据");
|
|
104
|
+
async calcHash(buffer) {
|
|
105
|
+
return this.adapter.calcHash(buffer);
|
|
244
106
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
107
|
+
}
|
|
108
|
+
class WechatPublisher {
|
|
109
|
+
tokenStore;
|
|
110
|
+
uploadCacheStore;
|
|
111
|
+
uploadMaterial;
|
|
112
|
+
publishArticle;
|
|
113
|
+
fetchAccessToken;
|
|
114
|
+
constructor(httpAdapter, tokenStoreAdapter, uploadCacheStoreAdapter) {
|
|
115
|
+
const { uploadMaterial, publishArticle, fetchAccessToken } = createWechatClient(httpAdapter);
|
|
116
|
+
this.uploadMaterial = uploadMaterial;
|
|
117
|
+
this.publishArticle = publishArticle;
|
|
118
|
+
this.fetchAccessToken = fetchAccessToken;
|
|
119
|
+
this.tokenStore = tokenStoreAdapter ? new TokenStore(tokenStoreAdapter) : void 0;
|
|
120
|
+
this.uploadCacheStore = uploadCacheStoreAdapter ? new UploadCacheStore(uploadCacheStoreAdapter) : void 0;
|
|
121
|
+
}
|
|
122
|
+
async getAccessTokenWithCache(appId, appSecret) {
|
|
123
|
+
if (!this.tokenStore) {
|
|
124
|
+
const result2 = await this.fetchAccessToken(appId, appSecret);
|
|
125
|
+
return result2.access_token;
|
|
126
|
+
}
|
|
127
|
+
const cached = this.tokenStore.getToken(appId);
|
|
128
|
+
if (cached) {
|
|
129
|
+
return cached;
|
|
130
|
+
}
|
|
131
|
+
const result = await this.fetchAccessToken(appId, appSecret);
|
|
132
|
+
await this.tokenStore.setToken(appId, result.access_token, result.expires_in);
|
|
133
|
+
return result.access_token;
|
|
134
|
+
}
|
|
135
|
+
async uploadImage(file, filename, accessToken) {
|
|
136
|
+
let hash;
|
|
137
|
+
if (this.uploadCacheStore) {
|
|
138
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
139
|
+
hash = await this.uploadCacheStore.calcHash(arrayBuffer);
|
|
140
|
+
const cached = await this.uploadCacheStore.get(hash);
|
|
141
|
+
if (cached) {
|
|
142
|
+
return {
|
|
143
|
+
media_id: cached.media_id,
|
|
144
|
+
url: cached.url
|
|
145
|
+
};
|
|
266
146
|
}
|
|
267
|
-
} else {
|
|
268
|
-
thumbMediaId = firstImageId;
|
|
269
147
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const data = await publishArticle(accessToken, {
|
|
275
|
-
title,
|
|
276
|
-
content: html,
|
|
277
|
-
thumb_media_id: thumbMediaId,
|
|
278
|
-
author,
|
|
279
|
-
content_source_url: source_url
|
|
280
|
-
});
|
|
281
|
-
if (data.media_id) {
|
|
148
|
+
const data = await this.uploadMaterial("image", file, filename, accessToken);
|
|
149
|
+
if (this.uploadCacheStore && hash) {
|
|
150
|
+
await this.uploadCacheStore.set(hash, data.media_id, data.url);
|
|
151
|
+
}
|
|
282
152
|
return data;
|
|
283
153
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
async function publishToDraft(title, content, cover = "", options = {}) {
|
|
287
|
-
return publishToWechatDraft({ title, content, cover }, options);
|
|
288
|
-
}
|
|
289
|
-
async function getAccessTokenWithCache(appId, appSecret) {
|
|
290
|
-
const cached = tokenStore.getToken(appId);
|
|
291
|
-
if (cached) {
|
|
292
|
-
return cached;
|
|
154
|
+
async publishToDraft(accessToken, options) {
|
|
155
|
+
return await this.publishArticle(accessToken, options);
|
|
293
156
|
}
|
|
294
|
-
const result = await fetchAccessToken(appId, appSecret);
|
|
295
|
-
tokenStore.setToken(appId, result.access_token, result.expires_in);
|
|
296
|
-
return result.access_token;
|
|
297
157
|
}
|
|
298
158
|
export {
|
|
299
|
-
|
|
300
|
-
|
|
159
|
+
WechatPublisher,
|
|
160
|
+
defaultTokenCache as d
|
|
301
161
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ClientPublishOptions, StyledContent } from "./types.js";
|
|
2
|
+
export declare function getServerUrl(options: ClientPublishOptions): string;
|
|
3
|
+
export declare function getHeaders(options: ClientPublishOptions): Record<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* 物理连通性测试
|
|
6
|
+
*/
|
|
7
|
+
export declare function healthCheck(serverUrl: string): Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* 鉴权探针测试
|
|
10
|
+
*/
|
|
11
|
+
export declare function verifyAuth(serverUrl: string, headers: Record<string, string>): Promise<void>;
|
|
12
|
+
export declare function uploadStyledContent(gzhContent: StyledContent, serverUrl: string, headers: Record<string, string>): Promise<string>;
|
|
13
|
+
export declare function requestServerPublish(mdFileId: string, serverUrl: string, headers: Record<string, string>, options: ClientPublishOptions): Promise<string>;
|
|
14
|
+
export declare function uploadLocalImages(content: string, serverUrl: string, headers: Record<string, string>, relativePath?: string): Promise<string>;
|
|
15
|
+
export declare function uploadCover(serverUrl: string, headers: Record<string, string>, cover?: string, relativePath?: string): Promise<string | undefined>;
|
|
@@ -11,16 +11,17 @@ export declare const configDir: string;
|
|
|
11
11
|
export declare const configPath: string;
|
|
12
12
|
declare class ConfigStore {
|
|
13
13
|
private config;
|
|
14
|
+
private initPromise;
|
|
14
15
|
constructor();
|
|
15
16
|
private load;
|
|
16
17
|
private save;
|
|
17
|
-
getConfig(): WenyanConfig
|
|
18
|
-
getThemes(): ThemeConfigOptions[]
|
|
19
|
-
getThemeById(themeId: string): string | undefined
|
|
20
|
-
addThemeToConfig(name: string, content: string): void
|
|
21
|
-
addThemeFile(themeId: string, themeContent: string): string
|
|
22
|
-
deleteThemeFromConfig(themeId: string): void
|
|
23
|
-
deleteThemeFile(filePath: string): void
|
|
18
|
+
getConfig(): Promise<WenyanConfig>;
|
|
19
|
+
getThemes(): Promise<ThemeConfigOptions[]>;
|
|
20
|
+
getThemeById(themeId: string): Promise<string | undefined>;
|
|
21
|
+
addThemeToConfig(name: string, content: string): Promise<void>;
|
|
22
|
+
addThemeFile(themeId: string, themeContent: string): Promise<string>;
|
|
23
|
+
deleteThemeFromConfig(themeId: string): Promise<void>;
|
|
24
|
+
deleteThemeFile(filePath: string): Promise<void>;
|
|
24
25
|
}
|
|
25
26
|
export declare const configStore: ConfigStore;
|
|
26
27
|
export {};
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { WechatPublishResponse } from "../wechat.js";
|
|
2
|
+
import { ArticleOptions } from "../publish.js";
|
|
3
|
+
interface PublishOptions {
|
|
2
4
|
appId?: string;
|
|
3
5
|
appSecret?: string;
|
|
4
6
|
relativePath?: string;
|
|
5
7
|
}
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
cover?: string;
|
|
10
|
-
author?: string;
|
|
11
|
-
source_url?: string;
|
|
12
|
-
}
|
|
13
|
-
export declare function publishToWechatDraft(articleOptions: ArticleOptions, publishOptions?: PublishOptions): Promise<import("../wechat.js").WechatPublishResponse>;
|
|
14
|
-
export declare function publishToDraft(title: string, content: string, cover?: string, options?: PublishOptions): Promise<import("../wechat.js").WechatPublishResponse>;
|
|
8
|
+
export declare function publishToWechatDraft(articleOptions: ArticleOptions, publishOptions?: PublishOptions): Promise<WechatPublishResponse>;
|
|
9
|
+
export declare function publishToDraft(title: string, content: string, cover?: string, options?: PublishOptions): Promise<WechatPublishResponse>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ApplyStylesOptions } from "../core/index.js";
|
|
2
|
+
import { GetInputContentFn, RenderContext, RenderOptions, StyledContent } from "./types.js";
|
|
3
|
+
export declare function renderWithTheme(markdownContent: string, options: RenderOptions): Promise<StyledContent>;
|
|
4
|
+
export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<StyledContent>;
|
|
5
|
+
export declare function prepareRenderContext(inputContent: string | undefined, options: RenderOptions, getInputContent: GetInputContentFn): Promise<RenderContext>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ThemeOptions {
|
|
2
|
+
list?: boolean;
|
|
3
|
+
add?: boolean;
|
|
4
|
+
name?: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
rm?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ThemeInfo {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
isBuiltin: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function listThemes(): Promise<ThemeInfo[]>;
|
|
15
|
+
export declare function addTheme(name?: string, path?: string): Promise<void>;
|
|
16
|
+
export declare function removeTheme(name: string): Promise<void>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TokenCache, TokenStorageAdapter } from "../tokenStore.js";
|
|
2
|
+
export declare const tokenPath: string;
|
|
3
|
+
export declare class NodeTokenStorageAdapter implements TokenStorageAdapter {
|
|
4
|
+
loadToken(): Promise<TokenCache | null>;
|
|
5
|
+
saveToken(cache: TokenCache): Promise<void>;
|
|
6
|
+
clearToken(): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface RenderOptions {
|
|
2
|
+
file?: string;
|
|
3
|
+
theme?: string;
|
|
4
|
+
customTheme?: string;
|
|
5
|
+
highlight: string;
|
|
6
|
+
macStyle: boolean;
|
|
7
|
+
footnote: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface PublishOptions extends RenderOptions {
|
|
10
|
+
}
|
|
11
|
+
export interface ClientPublishOptions extends RenderOptions {
|
|
12
|
+
server?: string;
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
clientVersion?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RenderContext {
|
|
17
|
+
gzhContent: StyledContent;
|
|
18
|
+
absoluteDirPath: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface StyledContent {
|
|
21
|
+
content: string;
|
|
22
|
+
title?: string;
|
|
23
|
+
cover?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
author?: string;
|
|
26
|
+
source_url?: string;
|
|
27
|
+
}
|
|
28
|
+
export type GetInputContentFn = (inputContent?: string, filePath?: string) => Promise<{
|
|
29
|
+
content: string;
|
|
30
|
+
absoluteDirPath?: string;
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CacheData, UploadCacheStorageAdapter } from "../uploadCacheStore.js";
|
|
2
|
+
export declare const cachePath: string;
|
|
3
|
+
export declare class NodeUploadCacheAdapter implements UploadCacheStorageAdapter {
|
|
4
|
+
loadCache(): Promise<CacheData>;
|
|
5
|
+
saveCache(cache: CacheData): Promise<void>;
|
|
6
|
+
clearCache(): Promise<void>;
|
|
7
|
+
calcHash(buffer: ArrayBuffer): Promise<string>;
|
|
8
|
+
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
2
|
+
export declare function readFileContent(filePath: string): Promise<string>;
|
|
3
|
+
export declare function readBinaryFile(filePath: string): Promise<Buffer>;
|
|
4
|
+
export declare function safeReadJson<T>(file: string, fallback: T): Promise<T>;
|
|
5
|
+
export declare function safeWriteJson(file: string, data: unknown): Promise<void>;
|
|
6
|
+
export declare function ensureDir(dir: string): Promise<void>;
|
|
5
7
|
export declare function md5FromBuffer(buf: crypto.BinaryLike): string;
|
|
6
|
-
export declare function md5FromFile(filePath: string): string
|
|
8
|
+
export declare function md5FromFile(filePath: string): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* 路径标准化工具函数
|
|
11
|
+
* 将 Windows 的反斜杠 \ 转换为正斜杠 /,并去除末尾斜杠
|
|
12
|
+
* 目的:在 Linux 容器内也能正确处理 Windows 路径字符串
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizePath(p: string): string;
|
|
15
|
+
export declare function isAbsolutePath(path: string): boolean;
|
|
16
|
+
export declare function getNormalizeFilePath(inputPath: string): string;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export interface StyledContent {
|
|
3
|
-
content: string;
|
|
4
|
-
title?: string;
|
|
5
|
-
cover?: string;
|
|
6
|
-
description?: string;
|
|
7
|
-
author?: string;
|
|
8
|
-
source_url?: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function renderStyledContent(content: string, options?: ApplyStylesOptions): Promise<StyledContent>;
|
|
1
|
+
import { ClientPublishOptions, GetInputContentFn, PublishOptions, StyledContent } from "./types.js";
|
|
11
2
|
export declare function getGzhContent(content: string, themeId: string, hlThemeId: string, isMacStyle?: boolean, isAddFootnote?: boolean): Promise<StyledContent>;
|
|
3
|
+
export declare function renderAndPublish(inputContent: string | undefined, options: PublishOptions, getInputContent: GetInputContentFn): Promise<string>;
|
|
4
|
+
export declare function renderAndPublishToServer(inputContent: string | undefined, options: ClientPublishOptions, getInputContent: GetInputContentFn): Promise<string>;
|
|
12
5
|
export * from "./configStore.js";
|
|
6
|
+
export * from "./render.js";
|
|
7
|
+
export * from "./theme.js";
|
|
8
|
+
export * from "./types.js";
|
|
9
|
+
export * from "./utils.js";
|
|
10
|
+
export * from "./publish.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HttpAdapter } from "./http.js";
|
|
2
|
+
import { TokenStorageAdapter } from "./tokenStore.js";
|
|
3
|
+
import { UploadCacheStorageAdapter } from "./uploadCacheStore.js";
|
|
4
|
+
import { WechatPublishOptions, WechatPublishResponse, WechatUploadResponse } from "./wechat.js";
|
|
5
|
+
export interface ArticleOptions {
|
|
6
|
+
title: string;
|
|
7
|
+
content: string;
|
|
8
|
+
cover?: string;
|
|
9
|
+
author?: string;
|
|
10
|
+
source_url?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class WechatPublisher {
|
|
13
|
+
private tokenStore;
|
|
14
|
+
private uploadCacheStore;
|
|
15
|
+
private uploadMaterial;
|
|
16
|
+
private publishArticle;
|
|
17
|
+
private fetchAccessToken;
|
|
18
|
+
constructor(httpAdapter: HttpAdapter, tokenStoreAdapter?: TokenStorageAdapter, uploadCacheStoreAdapter?: UploadCacheStorageAdapter);
|
|
19
|
+
getAccessTokenWithCache(appId: string, appSecret: string): Promise<string>;
|
|
20
|
+
uploadImage(file: Blob, filename: string, accessToken: string): Promise<WechatUploadResponse>;
|
|
21
|
+
publishToDraft(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface TokenCache {
|
|
2
|
+
appid: string;
|
|
3
|
+
accessToken: string;
|
|
4
|
+
expireAt: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const defaultTokenCache: TokenCache;
|
|
7
|
+
export interface TokenStorageAdapter {
|
|
8
|
+
loadToken(): Promise<TokenCache | null>;
|
|
9
|
+
saveToken(cache: TokenCache): Promise<void>;
|
|
10
|
+
clearToken(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare class TokenStore {
|
|
13
|
+
private cache;
|
|
14
|
+
private adapter;
|
|
15
|
+
private initPromise;
|
|
16
|
+
constructor(adapter: TokenStorageAdapter);
|
|
17
|
+
private load;
|
|
18
|
+
private save;
|
|
19
|
+
waitForInit(): Promise<void>;
|
|
20
|
+
isValid(appid: string): boolean;
|
|
21
|
+
getToken(appid: string): string | null;
|
|
22
|
+
setToken(appid: string, accessToken: string, expiresIn: number): Promise<void>;
|
|
23
|
+
clear(): Promise<void>;
|
|
24
|
+
}
|