@wenyan-md/core 3.0.3 → 3.0.5
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/dist/core.js +2 -2
- package/dist/publish.js +58 -3
- package/dist/types/credentialStore.d.ts +24 -0
- package/dist/types/node/configStore.d.ts +0 -1
- package/dist/types/node/credentialStoreNodeAdapter.d.ts +7 -0
- package/dist/types/node/publish.d.ts +2 -0
- package/dist/types/node/types.d.ts +2 -1
- package/dist/types/node/utils.d.ts +1 -0
- package/dist/types/publish.d.ts +2 -1
- package/dist/wrapper.js +135 -91
- package/package.json +5 -2
package/dist/core.js
CHANGED
|
@@ -524,9 +524,9 @@ function createMathJaxParser(options = {}) {
|
|
|
524
524
|
fontCache: options.fontCache ?? "none"
|
|
525
525
|
});
|
|
526
526
|
function addContainer(math, doc) {
|
|
527
|
+
const container = math.typesetRoot;
|
|
527
528
|
const tag = math.display ? "section" : "span";
|
|
528
529
|
const cls = math.display ? "block-equation" : "inline-equation";
|
|
529
|
-
const container = math.typesetRoot;
|
|
530
530
|
if (math.math) {
|
|
531
531
|
doc.adaptor.setAttribute(container, "math", math.math);
|
|
532
532
|
}
|
|
@@ -1087,7 +1087,7 @@ function transformUl(ulElement) {
|
|
|
1087
1087
|
function tableToAsciiArt(table) {
|
|
1088
1088
|
const rowsElements = Array.from(table.querySelectorAll("tr"));
|
|
1089
1089
|
const rows = rowsElements.map(
|
|
1090
|
-
(tr) => Array.from(tr.querySelectorAll("th, td")).map((td) => td.
|
|
1090
|
+
(tr) => Array.from(tr.querySelectorAll("th, td")).map((td) => (td?.textContent || "").trim())
|
|
1091
1091
|
);
|
|
1092
1092
|
if (rows.length === 0) return "";
|
|
1093
1093
|
const columnWidths = rows[0].map(
|
package/dist/publish.js
CHANGED
|
@@ -107,6 +107,57 @@ class UploadCacheStore {
|
|
|
107
107
|
return this.adapter.calcHash(buffer);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
+
const defaultCredential = {};
|
|
111
|
+
class CredentialStore {
|
|
112
|
+
credential = { ...defaultCredential };
|
|
113
|
+
adapter;
|
|
114
|
+
initPromise;
|
|
115
|
+
constructor(adapter) {
|
|
116
|
+
this.adapter = adapter;
|
|
117
|
+
this.initPromise = this.load();
|
|
118
|
+
}
|
|
119
|
+
async load() {
|
|
120
|
+
try {
|
|
121
|
+
const loadedData = await this.adapter.loadCredential();
|
|
122
|
+
if (loadedData) {
|
|
123
|
+
this.credential = loadedData;
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new Error(`无法加载凭据: ${error instanceof Error ? error.message : String(error)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async save() {
|
|
130
|
+
try {
|
|
131
|
+
await this.adapter.saveCredential(this.credential);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new Error(`无法保存凭据: ${error instanceof Error ? error.message : String(error)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async _getWechatCredential() {
|
|
137
|
+
await this.initPromise;
|
|
138
|
+
return this.credential.wechat ?? {};
|
|
139
|
+
}
|
|
140
|
+
async getWechatCredential(appId) {
|
|
141
|
+
const wechat = await this._getWechatCredential();
|
|
142
|
+
if (!wechat) return null;
|
|
143
|
+
const appSecret = wechat[appId];
|
|
144
|
+
if (!appSecret) return null;
|
|
145
|
+
return { appId, appSecret };
|
|
146
|
+
}
|
|
147
|
+
async saveWechatCredential(appId, appSecret) {
|
|
148
|
+
await this.initPromise;
|
|
149
|
+
this.credential.wechat ??= {};
|
|
150
|
+
this.credential.wechat[appId] = appSecret;
|
|
151
|
+
await this.save();
|
|
152
|
+
}
|
|
153
|
+
async deleteWechatCredential(appId) {
|
|
154
|
+
await this.initPromise;
|
|
155
|
+
if (this.credential.wechat) {
|
|
156
|
+
delete this.credential.wechat[appId];
|
|
157
|
+
await this.save();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
110
161
|
class WechatPublisher {
|
|
111
162
|
tokenStore;
|
|
112
163
|
uploadCacheStore;
|
|
@@ -134,12 +185,13 @@ class WechatPublisher {
|
|
|
134
185
|
await this.tokenStore.setToken(appId, result.access_token, result.expires_in);
|
|
135
186
|
return result.access_token;
|
|
136
187
|
}
|
|
137
|
-
async uploadImage(file, filename, accessToken) {
|
|
188
|
+
async uploadImage(file, filename, accessToken, appId) {
|
|
138
189
|
let hash;
|
|
139
190
|
if (this.uploadCacheStore) {
|
|
140
191
|
const arrayBuffer = await file.arrayBuffer();
|
|
141
192
|
hash = await this.uploadCacheStore.calcHash(arrayBuffer);
|
|
142
|
-
const
|
|
193
|
+
const cacheKey = appId ? `${hash}:${appId}` : hash;
|
|
194
|
+
const cached = await this.uploadCacheStore.get(cacheKey);
|
|
143
195
|
if (cached) {
|
|
144
196
|
return {
|
|
145
197
|
media_id: cached.media_id,
|
|
@@ -149,7 +201,8 @@ class WechatPublisher {
|
|
|
149
201
|
}
|
|
150
202
|
const data = await this.uploadMaterial("image", file, filename, accessToken);
|
|
151
203
|
if (this.uploadCacheStore && hash) {
|
|
152
|
-
|
|
204
|
+
const cacheKey = appId ? `${hash}:${appId}` : hash;
|
|
205
|
+
await this.uploadCacheStore.set(cacheKey, data.media_id, data.url);
|
|
153
206
|
}
|
|
154
207
|
return data;
|
|
155
208
|
}
|
|
@@ -166,8 +219,10 @@ class WechatPublisher {
|
|
|
166
219
|
}
|
|
167
220
|
}
|
|
168
221
|
export {
|
|
222
|
+
CredentialStore,
|
|
169
223
|
TokenStore,
|
|
170
224
|
UploadCacheStore,
|
|
171
225
|
WechatPublisher,
|
|
226
|
+
defaultCredential,
|
|
172
227
|
defaultTokenCache
|
|
173
228
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface WenyanCredential {
|
|
2
|
+
wechat?: Record<string, string>;
|
|
3
|
+
}
|
|
4
|
+
export declare const defaultCredential: WenyanCredential;
|
|
5
|
+
export interface CredentialStorageAdapter {
|
|
6
|
+
loadCredential(): Promise<WenyanCredential | null>;
|
|
7
|
+
saveCredential(credential: WenyanCredential): Promise<void>;
|
|
8
|
+
clearCredential(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare class CredentialStore {
|
|
11
|
+
private credential;
|
|
12
|
+
private adapter;
|
|
13
|
+
private initPromise;
|
|
14
|
+
constructor(adapter: CredentialStorageAdapter);
|
|
15
|
+
private load;
|
|
16
|
+
private save;
|
|
17
|
+
private _getWechatCredential;
|
|
18
|
+
getWechatCredential(appId: string): Promise<{
|
|
19
|
+
appId: string;
|
|
20
|
+
appSecret: string;
|
|
21
|
+
} | null>;
|
|
22
|
+
saveWechatCredential(appId: string, appSecret: string): Promise<void>;
|
|
23
|
+
deleteWechatCredential(appId: string): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CredentialStorageAdapter, WenyanCredential } from "../credentialStore.js";
|
|
2
|
+
export declare const credentialPath: string;
|
|
3
|
+
export declare class NodeCredentialStorageAdapter implements CredentialStorageAdapter {
|
|
4
|
+
loadCredential(): Promise<WenyanCredential | null>;
|
|
5
|
+
saveCredential(credential: WenyanCredential): Promise<void>;
|
|
6
|
+
clearCredential(): Promise<void>;
|
|
7
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { WechatPublishResponse } from "../wechat.js";
|
|
2
2
|
import { ArticleOptions, WechatPublisher } from "../publish.js";
|
|
3
|
+
import { CredentialStore } from "../credentialStore.js";
|
|
3
4
|
export declare const wechatPublisher: WechatPublisher;
|
|
5
|
+
export declare const credentialStore: CredentialStore;
|
|
4
6
|
interface PublishOptions {
|
|
5
7
|
appId?: string;
|
|
6
8
|
appSecret?: string;
|
|
@@ -7,8 +7,9 @@ export interface RenderOptions {
|
|
|
7
7
|
footnote: boolean;
|
|
8
8
|
}
|
|
9
9
|
export interface PublishOptions extends RenderOptions {
|
|
10
|
+
appId?: string;
|
|
10
11
|
}
|
|
11
|
-
export interface ClientPublishOptions extends
|
|
12
|
+
export interface ClientPublishOptions extends PublishOptions {
|
|
12
13
|
server?: string;
|
|
13
14
|
apiKey?: string;
|
|
14
15
|
clientVersion?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
export declare const configDir: string;
|
|
2
3
|
export declare function readFileContent(filePath: string): Promise<string>;
|
|
3
4
|
export declare function readBinaryFile(filePath: string): Promise<Buffer>;
|
|
4
5
|
export declare function safeReadJson<T>(file: string, fallback: T): Promise<T>;
|
package/dist/types/publish.d.ts
CHANGED
|
@@ -17,9 +17,10 @@ export declare class WechatPublisher {
|
|
|
17
17
|
private fetchAccessToken;
|
|
18
18
|
constructor(httpAdapter: HttpAdapter, tokenStoreAdapter?: TokenStorageAdapter, uploadCacheStoreAdapter?: UploadCacheStorageAdapter);
|
|
19
19
|
getAccessTokenWithCache(appId: string, appSecret: string): Promise<string>;
|
|
20
|
-
uploadImage(file: Blob, filename: string, accessToken: string): Promise<WechatUploadResponse>;
|
|
20
|
+
uploadImage(file: Blob, filename: string, accessToken: string, appId?: string): Promise<WechatUploadResponse>;
|
|
21
21
|
publishToDraft(accessToken: string, options: WechatPublishOptions): Promise<WechatPublishResponse>;
|
|
22
22
|
clearCache(): Promise<void>;
|
|
23
23
|
}
|
|
24
24
|
export * from "./tokenStore.js";
|
|
25
25
|
export * from "./uploadCacheStore.js";
|
|
26
|
+
export * from "./credentialStore.js";
|
package/dist/wrapper.js
CHANGED
|
@@ -4,13 +4,22 @@ import https from "node:https";
|
|
|
4
4
|
import { JSDOM } from "jsdom";
|
|
5
5
|
import fs, { stat } from "node:fs/promises";
|
|
6
6
|
import crypto from "node:crypto";
|
|
7
|
+
import os from "node:os";
|
|
7
8
|
import { fileFromPath } from "formdata-node/file-from-path";
|
|
8
9
|
import { FormDataEncoder } from "form-data-encoder";
|
|
9
10
|
import { FormData } from "formdata-node";
|
|
10
11
|
import { Readable } from "node:stream";
|
|
11
|
-
import
|
|
12
|
-
import { defaultTokenCache, WechatPublisher } from "./publish.js";
|
|
12
|
+
import { defaultTokenCache, defaultCredential, WechatPublisher, CredentialStore } from "./publish.js";
|
|
13
13
|
import { createWenyanCore, getAllGzhThemes } from "./core.js";
|
|
14
|
+
const configDir = (() => {
|
|
15
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
16
|
+
return path.join(process.env.XDG_CONFIG_HOME, "wenyan-md");
|
|
17
|
+
}
|
|
18
|
+
if (process.env.APPDATA) {
|
|
19
|
+
return path.join(process.env.APPDATA, "wenyan-md");
|
|
20
|
+
}
|
|
21
|
+
return path.join(os.homedir(), ".config", "wenyan-md");
|
|
22
|
+
})();
|
|
14
23
|
async function readFileContent(filePath) {
|
|
15
24
|
return await fs.readFile(filePath, "utf-8");
|
|
16
25
|
}
|
|
@@ -233,7 +242,7 @@ async function uploadStyledContent(gzhContent, serverUrl, headers) {
|
|
|
233
242
|
return mdFileId;
|
|
234
243
|
}
|
|
235
244
|
async function requestServerPublish(mdFileId, serverUrl, headers, options) {
|
|
236
|
-
const { theme, customTheme, highlight, macStyle, footnote } = options;
|
|
245
|
+
const { theme, customTheme, highlight, macStyle, footnote, appId } = options;
|
|
237
246
|
const publishRes = await fetch(`${serverUrl}/publish`, {
|
|
238
247
|
method: "POST",
|
|
239
248
|
headers: {
|
|
@@ -246,7 +255,8 @@ async function requestServerPublish(mdFileId, serverUrl, headers, options) {
|
|
|
246
255
|
highlight,
|
|
247
256
|
customTheme,
|
|
248
257
|
macStyle,
|
|
249
|
-
footnote
|
|
258
|
+
footnote,
|
|
259
|
+
appId
|
|
250
260
|
})
|
|
251
261
|
});
|
|
252
262
|
const publishData = await publishRes.json();
|
|
@@ -330,80 +340,6 @@ const nodeHttpAdapter = {
|
|
|
330
340
|
};
|
|
331
341
|
}
|
|
332
342
|
};
|
|
333
|
-
const defaultConfig = {};
|
|
334
|
-
const configDir = process.env.APPDATA ? path.join(process.env.APPDATA, "wenyan-md") : path.join(os.homedir(), ".config", "wenyan-md");
|
|
335
|
-
const configPath = path.join(configDir, "config.json");
|
|
336
|
-
class ConfigStore {
|
|
337
|
-
config = { ...defaultConfig };
|
|
338
|
-
initPromise;
|
|
339
|
-
constructor() {
|
|
340
|
-
this.initPromise = this.load();
|
|
341
|
-
}
|
|
342
|
-
async load() {
|
|
343
|
-
await ensureDir(configDir);
|
|
344
|
-
this.config = await safeReadJson(configPath, defaultConfig);
|
|
345
|
-
}
|
|
346
|
-
async save() {
|
|
347
|
-
try {
|
|
348
|
-
await ensureDir(configDir);
|
|
349
|
-
await safeWriteJson(configPath, this.config);
|
|
350
|
-
} catch (error) {
|
|
351
|
-
throw new Error(`无法保存配置文件: ${error instanceof Error ? error.message : String(error)}`);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
async getConfig() {
|
|
355
|
-
await this.initPromise;
|
|
356
|
-
return this.config;
|
|
357
|
-
}
|
|
358
|
-
async getThemes() {
|
|
359
|
-
await this.initPromise;
|
|
360
|
-
return Object.values(this.config.themes ?? {});
|
|
361
|
-
}
|
|
362
|
-
async getThemeById(themeId) {
|
|
363
|
-
await this.initPromise;
|
|
364
|
-
const themeOption = this.config.themes?.[themeId];
|
|
365
|
-
if (!themeOption) return void 0;
|
|
366
|
-
const absoluteFilePath = path.join(configDir, themeOption.path);
|
|
367
|
-
try {
|
|
368
|
-
return await fs.readFile(absoluteFilePath, "utf-8");
|
|
369
|
-
} catch {
|
|
370
|
-
return void 0;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
async addThemeToConfig(name, content) {
|
|
374
|
-
await this.initPromise;
|
|
375
|
-
const savedPath = await this.addThemeFile(name, content);
|
|
376
|
-
this.config.themes ??= {};
|
|
377
|
-
this.config.themes[name] = {
|
|
378
|
-
id: name,
|
|
379
|
-
name,
|
|
380
|
-
path: savedPath
|
|
381
|
-
};
|
|
382
|
-
await this.save();
|
|
383
|
-
}
|
|
384
|
-
async addThemeFile(themeId, themeContent) {
|
|
385
|
-
const filePath = `themes/${themeId}.css`;
|
|
386
|
-
const absoluteFilePath = path.join(configDir, filePath);
|
|
387
|
-
await ensureDir(path.dirname(absoluteFilePath));
|
|
388
|
-
await fs.writeFile(absoluteFilePath, themeContent, "utf-8");
|
|
389
|
-
return filePath;
|
|
390
|
-
}
|
|
391
|
-
async deleteThemeFromConfig(themeId) {
|
|
392
|
-
await this.initPromise;
|
|
393
|
-
const theme = this.config.themes?.[themeId];
|
|
394
|
-
if (!theme) return;
|
|
395
|
-
await this.deleteThemeFile(theme.path);
|
|
396
|
-
delete this.config.themes[themeId];
|
|
397
|
-
await this.save();
|
|
398
|
-
}
|
|
399
|
-
async deleteThemeFile(filePath) {
|
|
400
|
-
try {
|
|
401
|
-
await fs.unlink(path.join(configDir, filePath));
|
|
402
|
-
} catch {
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const configStore = new ConfigStore();
|
|
407
343
|
const tokenPath = path.join(configDir, "token.json");
|
|
408
344
|
class NodeTokenStorageAdapter {
|
|
409
345
|
async loadToken() {
|
|
@@ -447,9 +383,28 @@ class NodeUploadCacheAdapter {
|
|
|
447
383
|
return crypto.createHash("md5").update(Buffer.from(buffer)).digest("hex");
|
|
448
384
|
}
|
|
449
385
|
}
|
|
386
|
+
const credentialPath = path.join(configDir, "credential.json");
|
|
387
|
+
class NodeCredentialStorageAdapter {
|
|
388
|
+
async loadCredential() {
|
|
389
|
+
await ensureDir(configDir);
|
|
390
|
+
return await safeReadJson(credentialPath, defaultCredential);
|
|
391
|
+
}
|
|
392
|
+
async saveCredential(credential) {
|
|
393
|
+
await ensureDir(configDir);
|
|
394
|
+
await safeWriteJson(credentialPath, credential);
|
|
395
|
+
}
|
|
396
|
+
async clearCredential() {
|
|
397
|
+
throw new Error("Method not implemented.");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
450
400
|
const mediaIdMapping = /* @__PURE__ */ new Map();
|
|
451
|
-
const wechatPublisher = new WechatPublisher(
|
|
452
|
-
|
|
401
|
+
const wechatPublisher = new WechatPublisher(
|
|
402
|
+
nodeHttpAdapter,
|
|
403
|
+
new NodeTokenStorageAdapter(),
|
|
404
|
+
new NodeUploadCacheAdapter()
|
|
405
|
+
);
|
|
406
|
+
const credentialStore = new CredentialStore(new NodeCredentialStorageAdapter());
|
|
407
|
+
async function uploadImage(imageUrl, accessToken, fileName, relativePath, appId) {
|
|
453
408
|
let fileData;
|
|
454
409
|
let finalName;
|
|
455
410
|
if (imageUrl.startsWith("http")) {
|
|
@@ -478,11 +433,11 @@ async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
|
|
|
478
433
|
const fileFromPathResult = await fileFromPath(resolvedPath);
|
|
479
434
|
fileData = new Blob([await fileFromPathResult.arrayBuffer()], { type: fileFromPathResult.type });
|
|
480
435
|
}
|
|
481
|
-
const data = await wechatPublisher.uploadImage(fileData, finalName, accessToken);
|
|
436
|
+
const data = await wechatPublisher.uploadImage(fileData, finalName, accessToken, appId);
|
|
482
437
|
mediaIdMapping.set(data.url, data.media_id);
|
|
483
438
|
return data;
|
|
484
439
|
}
|
|
485
|
-
async function uploadImages(content, accessToken, relativePath) {
|
|
440
|
+
async function uploadImages(content, accessToken, relativePath, appId) {
|
|
486
441
|
if (!content.includes("<img")) {
|
|
487
442
|
return { html: content, firstImageId: "" };
|
|
488
443
|
}
|
|
@@ -493,7 +448,7 @@ async function uploadImages(content, accessToken, relativePath) {
|
|
|
493
448
|
const dataSrc = element.getAttribute("src");
|
|
494
449
|
if (dataSrc) {
|
|
495
450
|
if (!dataSrc.startsWith("https://mmbiz.qpic.cn")) {
|
|
496
|
-
const resp = await uploadImage(dataSrc, accessToken, void 0, relativePath);
|
|
451
|
+
const resp = await uploadImage(dataSrc, accessToken, void 0, relativePath, appId);
|
|
497
452
|
element.setAttribute("src", resp.url);
|
|
498
453
|
return resp.media_id;
|
|
499
454
|
} else {
|
|
@@ -510,20 +465,16 @@ async function uploadImages(content, accessToken, relativePath) {
|
|
|
510
465
|
async function publishToWechatDraft(articleOptions, publishOptions = {}) {
|
|
511
466
|
const { title, content, cover, author, source_url } = articleOptions;
|
|
512
467
|
const { appId, appSecret, relativePath } = publishOptions;
|
|
513
|
-
const appIdFinal = appId
|
|
514
|
-
const appSecretFinal = appSecret ?? process.env.WECHAT_APP_SECRET;
|
|
515
|
-
if (!appIdFinal || !appSecretFinal) {
|
|
516
|
-
throw new Error("请通过参数或环境变量 WECHAT_APP_ID / WECHAT_APP_SECRET 提供公众号凭据");
|
|
517
|
-
}
|
|
468
|
+
const { appId: appIdFinal, appSecret: appSecretFinal } = await getAppIdAndSecret(appId, appSecret);
|
|
518
469
|
const accessToken = await wechatPublisher.getAccessTokenWithCache(appIdFinal, appSecretFinal);
|
|
519
|
-
const { html, firstImageId } = await uploadImages(content, accessToken, relativePath);
|
|
470
|
+
const { html, firstImageId } = await uploadImages(content, accessToken, relativePath, appIdFinal);
|
|
520
471
|
let thumbMediaId;
|
|
521
472
|
if (cover) {
|
|
522
473
|
const cachedThumbMediaId = mediaIdMapping.get(cover);
|
|
523
474
|
if (cachedThumbMediaId) {
|
|
524
475
|
thumbMediaId = cachedThumbMediaId;
|
|
525
476
|
} else {
|
|
526
|
-
const resp = await uploadImage(cover, accessToken, "cover.jpg", relativePath);
|
|
477
|
+
const resp = await uploadImage(cover, accessToken, "cover.jpg", relativePath, appIdFinal);
|
|
527
478
|
thumbMediaId = resp.media_id;
|
|
528
479
|
}
|
|
529
480
|
} else {
|
|
@@ -532,7 +483,7 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
|
|
|
532
483
|
if (cachedThumbMediaId) {
|
|
533
484
|
thumbMediaId = cachedThumbMediaId;
|
|
534
485
|
} else {
|
|
535
|
-
const resp = await uploadImage(firstImageId, accessToken, "cover.jpg", relativePath);
|
|
486
|
+
const resp = await uploadImage(firstImageId, accessToken, "cover.jpg", relativePath, appIdFinal);
|
|
536
487
|
thumbMediaId = resp.media_id;
|
|
537
488
|
}
|
|
538
489
|
} else {
|
|
@@ -557,6 +508,97 @@ async function publishToWechatDraft(articleOptions, publishOptions = {}) {
|
|
|
557
508
|
async function publishToDraft(title, content, cover = "", options = {}) {
|
|
558
509
|
return publishToWechatDraft({ title, content, cover }, options);
|
|
559
510
|
}
|
|
511
|
+
async function getAppIdAndSecret(appId, appSecret) {
|
|
512
|
+
if (appId && appSecret) {
|
|
513
|
+
return { appId, appSecret };
|
|
514
|
+
}
|
|
515
|
+
const envAppId = process.env.WECHAT_APP_ID;
|
|
516
|
+
const envAppSecret = process.env.WECHAT_APP_SECRET;
|
|
517
|
+
if (envAppId && envAppSecret && (envAppId === appId || !appId)) {
|
|
518
|
+
return { appId: envAppId, appSecret: envAppSecret };
|
|
519
|
+
}
|
|
520
|
+
if (!appId) {
|
|
521
|
+
throw new Error("未提供 AppID:请通过参数、环境变量或配置文件指定。");
|
|
522
|
+
}
|
|
523
|
+
const credential = await credentialStore.getWechatCredential(appId);
|
|
524
|
+
if (credential?.appId && credential?.appSecret) {
|
|
525
|
+
return { appId: credential.appId, appSecret: credential.appSecret };
|
|
526
|
+
}
|
|
527
|
+
throw new Error(`未能找到 AppID 为 "${appId}" 的公众号凭据,请检查配置文件。`);
|
|
528
|
+
}
|
|
529
|
+
const defaultConfig = {};
|
|
530
|
+
const configPath = path.join(configDir, "config.json");
|
|
531
|
+
class ConfigStore {
|
|
532
|
+
config = { ...defaultConfig };
|
|
533
|
+
initPromise;
|
|
534
|
+
constructor() {
|
|
535
|
+
this.initPromise = this.load();
|
|
536
|
+
}
|
|
537
|
+
async load() {
|
|
538
|
+
await ensureDir(configDir);
|
|
539
|
+
this.config = await safeReadJson(configPath, defaultConfig);
|
|
540
|
+
}
|
|
541
|
+
async save() {
|
|
542
|
+
try {
|
|
543
|
+
await ensureDir(configDir);
|
|
544
|
+
await safeWriteJson(configPath, this.config);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
throw new Error(`无法保存配置文件: ${error instanceof Error ? error.message : String(error)}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async getConfig() {
|
|
550
|
+
await this.initPromise;
|
|
551
|
+
return this.config;
|
|
552
|
+
}
|
|
553
|
+
async getThemes() {
|
|
554
|
+
await this.initPromise;
|
|
555
|
+
return Object.values(this.config.themes ?? {});
|
|
556
|
+
}
|
|
557
|
+
async getThemeById(themeId) {
|
|
558
|
+
await this.initPromise;
|
|
559
|
+
const themeOption = this.config.themes?.[themeId];
|
|
560
|
+
if (!themeOption) return void 0;
|
|
561
|
+
const absoluteFilePath = path.join(configDir, themeOption.path);
|
|
562
|
+
try {
|
|
563
|
+
return await fs.readFile(absoluteFilePath, "utf-8");
|
|
564
|
+
} catch {
|
|
565
|
+
return void 0;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async addThemeToConfig(name, content) {
|
|
569
|
+
await this.initPromise;
|
|
570
|
+
const savedPath = await this.addThemeFile(name, content);
|
|
571
|
+
this.config.themes ??= {};
|
|
572
|
+
this.config.themes[name] = {
|
|
573
|
+
id: name,
|
|
574
|
+
name,
|
|
575
|
+
path: savedPath
|
|
576
|
+
};
|
|
577
|
+
await this.save();
|
|
578
|
+
}
|
|
579
|
+
async addThemeFile(themeId, themeContent) {
|
|
580
|
+
const filePath = `themes/${themeId}.css`;
|
|
581
|
+
const absoluteFilePath = path.join(configDir, filePath);
|
|
582
|
+
await ensureDir(path.dirname(absoluteFilePath));
|
|
583
|
+
await fs.writeFile(absoluteFilePath, themeContent, "utf-8");
|
|
584
|
+
return filePath;
|
|
585
|
+
}
|
|
586
|
+
async deleteThemeFromConfig(themeId) {
|
|
587
|
+
await this.initPromise;
|
|
588
|
+
const theme = this.config.themes?.[themeId];
|
|
589
|
+
if (!theme) return;
|
|
590
|
+
await this.deleteThemeFile(theme.path);
|
|
591
|
+
delete this.config.themes[themeId];
|
|
592
|
+
await this.save();
|
|
593
|
+
}
|
|
594
|
+
async deleteThemeFile(filePath) {
|
|
595
|
+
try {
|
|
596
|
+
await fs.unlink(path.join(configDir, filePath));
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const configStore = new ConfigStore();
|
|
560
602
|
const wenyanCoreInstance = await createWenyanCore();
|
|
561
603
|
async function renderWithTheme(markdownContent, options) {
|
|
562
604
|
if (!markdownContent) {
|
|
@@ -683,6 +725,7 @@ async function renderAndPublish(inputContent, options, getInputContent) {
|
|
|
683
725
|
source_url: gzhContent.source_url
|
|
684
726
|
},
|
|
685
727
|
{
|
|
728
|
+
appId: options.appId,
|
|
686
729
|
relativePath: absoluteDirPath
|
|
687
730
|
}
|
|
688
731
|
);
|
|
@@ -710,6 +753,7 @@ export {
|
|
|
710
753
|
configDir,
|
|
711
754
|
configPath,
|
|
712
755
|
configStore,
|
|
756
|
+
credentialStore,
|
|
713
757
|
ensureDir,
|
|
714
758
|
getGzhContent,
|
|
715
759
|
getNormalizeFilePath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenyan-md/core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
4
4
|
"description": "Core library for Wenyan markdown rendering & publishing",
|
|
5
5
|
"author": "Lei <caol64@gmail.com> (https://github.com/caol64)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"vitest": "^3.2.4"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"css-tree": "^3.1
|
|
63
|
+
"css-tree": "^3.2.1",
|
|
64
64
|
"front-matter": "^4.0.2",
|
|
65
65
|
"highlight.js": "11.10.0",
|
|
66
66
|
"marked": "^15.0.12",
|
|
@@ -83,6 +83,9 @@
|
|
|
83
83
|
"optional": true
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
|
+
"publishConfig": {
|
|
87
|
+
"registry": "https://registry.npmjs.org/"
|
|
88
|
+
},
|
|
86
89
|
"scripts": {
|
|
87
90
|
"typecheck": "tsc --noEmit",
|
|
88
91
|
"build": "vite build && tsc",
|