Files
memind/mindspace-chat-wechat-draft.mjs
T
john d8eaef3fbd feat(wechat): MindSpace public draft push with 143 host defaults
Add public-page WeChat draft button, publication standard conversion for poem/prose/generic pages, and quick push APIs. Default Studio release SSH targets to john@180.159.29.143 with 105 edge upstream via 10.10.0.2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 12:32:13 +08:00

163 lines
4.7 KiB
JavaScript

import path from 'node:path';
import { normalizeWorkspaceRelativePath } from './mindspace-workspace-relative-path.mjs';
import { validatePageForWechatDraft } from './mindspace-wechat-page-draft.mjs';
function normalizePublicHtmlRelativePath(relativePath) {
const normalized = normalizeWorkspaceRelativePath(relativePath);
if (!normalized || !normalized.startsWith('public/') || !normalized.toLowerCase().endsWith('.html')) {
throw Object.assign(new Error('仅支持 workspace 公开 HTML 页面'), { code: 'invalid_public_html_path' });
}
return normalized;
}
function titleFromPublicHtml(html, relativePath) {
const match = String(html ?? '').match(/<title[^>]*>([\s\S]*?)<\/title>/i);
const fromTitle = match?.[1]?.replace(/\s+/g, ' ').trim();
if (fromTitle) return fromTitle;
const basename = path.basename(String(relativePath ?? ''), '.html');
return basename || 'MindSpace 页面';
}
async function resolvePublicHtmlContext({
user,
relativePath,
mindSpacePages,
mindSpacePublications,
readWorkspaceHtml,
}) {
if (!mindSpacePages) {
throw Object.assign(new Error('MindSpace 页面服务未启用'), { code: 'mindspace_unavailable' });
}
if (typeof readWorkspaceHtml !== 'function') {
throw Object.assign(new Error('MindSpace 页面读取服务不可用'), { code: 'mindspace_read_unavailable' });
}
const normalized = normalizePublicHtmlRelativePath(relativePath);
const { html: content } = await readWorkspaceHtml({
userId: user.id,
relativePath: normalized,
});
const page = await mindSpacePages.findPageByRelativePath(user.id, normalized);
if (!page?.id) {
throw Object.assign(new Error('无法识别页面对应的 MindSpace 记录'), { code: 'page_not_found' });
}
let publicUrl = '';
if (mindSpacePublications?.getCurrent) {
const publication = await mindSpacePublications.getCurrent(user.id, page.id);
publicUrl = publication?.publicUrl ?? '';
}
return {
normalized,
content,
page,
pageTitle: page.title || titleFromPublicHtml(content, normalized),
pageSummary: page.summary ?? '',
publicUrl,
};
}
export async function getQuickWechatDraftFromPublicHtmlStatus({
user,
relativePath,
mindSpacePages,
mindSpacePublications,
readWorkspaceHtml,
getWechatMpConfig,
getWechatPageDraft,
}) {
const configService = getWechatMpConfig?.();
const config = configService
? await configService.getConfig(user.id)
: { configured: false };
let context = null;
let validation = null;
try {
context = await resolvePublicHtmlContext({
user,
relativePath,
mindSpacePages,
mindSpacePublications,
readWorkspaceHtml,
});
validation = validatePageForWechatDraft(context.content, {
pageTitle: context.pageTitle,
pageSummary: context.pageSummary,
publicUrl: context.publicUrl,
});
} catch (error) {
if (error?.code === 'page_not_found' || error?.code === 'invalid_public_html_path') {
return {
configured: Boolean(config.configured),
pageId: null,
canPush: false,
validation: {
ok: false,
profile: 'unknown',
blockers: [error.message],
warnings: [],
estimatedContentLength: 0,
},
};
}
throw error;
}
let latestRun = null;
const draftService = getWechatPageDraft?.();
if (draftService?.listRuns) {
const runs = await draftService.listRuns(user.id, { pageId: context.page.id, limit: 1 });
latestRun = runs[0] ?? null;
}
return {
configured: Boolean(config.configured),
pageId: context.page.id,
pageTitle: context.pageTitle,
canPush: Boolean(config.configured) && validation.ok,
validation,
latestRun,
};
}
export async function quickWechatDraftFromPublicHtml({
user,
relativePath,
mindSpacePages,
mindSpacePublications,
readWorkspaceHtml,
getWechatPageDraft,
}) {
const draftService = getWechatPageDraft?.();
if (!draftService?.pushPageDraft) {
throw Object.assign(new Error('公众号草稿推送未启用'), { code: 'wechat_draft_unavailable' });
}
const context = await resolvePublicHtmlContext({
user,
relativePath,
mindSpacePages,
mindSpacePublications,
readWorkspaceHtml,
});
const validation = validatePageForWechatDraft(context.content, {
pageTitle: context.pageTitle,
pageSummary: context.pageSummary,
publicUrl: context.publicUrl,
});
if (!validation.ok) {
throw Object.assign(new Error(validation.blockers[0] ?? '页面不符合公众号草稿推送要求'), {
code: 'wechat_draft_validation_failed',
validation,
});
}
return draftService.pushPageDraft(user.id, context.page.id, {
triggeredBy: user.id,
skipValidation: true,
});
}