@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 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
- **文颜(Wenyan)** 是一套多平台 Markdown 排版与发布工具链, **文颜 CORE** 是其核心库,专注于:
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
- import { FormDataEncoder } from "form-data-encoder";
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 = defaultCache;
84
- constructor() {
85
- this.load();
86
- }
87
- load() {
88
- ensureDir(configDir);
89
- if (fs.existsSync(tokenPath)) {
90
- this.cache = safeReadJson(tokenPath, defaultCache);
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
- ensureDir(configDir);
96
- safeWriteJson(tokenPath, this.cache);
27
+ await this.adapter.saveToken(this.cache);
97
28
  } catch (error) {
98
- console.error("❌ 无法保存 token:", error);
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
- if (!this.cache) return false;
103
- return this.cache.appid === appid && this.cache.expireAt > Date.now() / 1e3 + 600;
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.cache = defaultCache;
118
- try {
119
- if (fs.existsSync(tokenPath)) {
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
- constructor() {
131
- this.load();
63
+ adapter;
64
+ initPromise;
65
+ constructor(adapter) {
66
+ this.adapter = adapter;
67
+ this.initPromise = this.load();
132
68
  }
133
- load() {
134
- ensureDir(configDir);
135
- if (fs.existsSync(cachePath)) {
136
- this.cache = safeReadJson(cachePath, {});
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
- ensureDir(configDir);
142
- safeWriteJson(cachePath, this.cache);
78
+ await this.adapter.saveCache(this.cache);
143
79
  } catch (error) {
144
- console.error("❌ 无法保存上传缓存:", error);
80
+ throw new Error(`无法保存上传缓存: ${error instanceof Error ? error.message : String(error)}`);
145
81
  }
146
82
  }
147
- get(md5) {
148
- return this.cache[md5];
83
+ async waitForInit() {
84
+ await this.initPromise;
149
85
  }
150
- set(md5, mediaId, url) {
151
- this.cache[md5] = { media_id: mediaId, url, updated_at: Date.now() };
152
- this.save();
86
+ async get(hash) {
87
+ await this.initPromise;
88
+ return this.cache[hash];
153
89
  }
154
- }
155
- const uploadCacheStore = new UploadCacheStore();
156
- const { uploadMaterial, publishArticle, fetchAccessToken } = createWechatClient(nodeHttpAdapter);
157
- const mediaIdMapping = /* @__PURE__ */ new Map();
158
- async function uploadImage(imageUrl, accessToken, fileName, relativePath) {
159
- let fileData;
160
- let finalName;
161
- let md5;
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
- const data = await uploadMaterial("image", fileData, finalName, accessToken);
208
- uploadCacheStore.set(md5, data.media_id, data.url);
209
- mediaIdMapping.set(data.url, data.media_id);
210
- return data;
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
- const dom = new JSDOM(content);
217
- const document = dom.window.document;
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
- const accessToken = await getAccessTokenWithCache(appIdFinal, appSecretFinal);
246
- const { html, firstImageId } = await uploadImages(content, accessToken, relativePath);
247
- let thumbMediaId = "";
248
- if (cover) {
249
- const cachedThumbMediaId = mediaIdMapping.get(cover);
250
- if (cachedThumbMediaId) {
251
- thumbMediaId = cachedThumbMediaId;
252
- } else {
253
- const resp = await uploadImage(cover, accessToken, "cover.jpg", relativePath);
254
- thumbMediaId = resp.media_id;
255
- mediaIdMapping.set(resp.url, resp.media_id);
256
- }
257
- } else {
258
- if (firstImageId.startsWith("https://mmbiz.qpic.cn")) {
259
- const cachedThumbMediaId = mediaIdMapping.get(firstImageId);
260
- if (cachedThumbMediaId) {
261
- thumbMediaId = cachedThumbMediaId;
262
- } else {
263
- const resp = await uploadImage(firstImageId, accessToken, "cover.jpg", relativePath);
264
- thumbMediaId = resp.media_id;
265
- mediaIdMapping.set(resp.url, resp.media_id);
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
- if (!thumbMediaId) {
272
- throw new Error("你必须指定一张封面图或者在正文中至少出现一张图片。");
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
- throw new Error(`上传到公众号草稿失败: ${JSON.stringify(data)}`);
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
- publishToDraft,
300
- publishToWechatDraft
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
- export interface PublishOptions {
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 interface ArticleOptions {
7
- title: string;
8
- content: string;
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 safeReadJson<T>(file: string, fallback: T): T;
3
- export declare function safeWriteJson(file: string, data: unknown): void;
4
- export declare function ensureDir(dir: string): void;
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 { ApplyStylesOptions } from "../core/index.js";
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
+ }