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>
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user