Files
WechatExplorer/src/main/wechat-share-card-service.ts
T
Wxw-Gu 4436d7c8ce feat: 新增实验性微信卡片分享与自动部署能力
补充 Cloudflare Worker、R2 存储、微信 JS-SDK 签名与上传鉴权
增加自动部署 Skill 和配置引导文档
优化报告工具栏、微信卡片弹窗及窄屏响应式布局
补充 Worker 鉴权、卡片生成、过期清理与安全转义测试
2026-08-13 10:48:07 +08:00

74 lines
2.7 KiB
TypeScript

import { nativeImage } from 'electron'
import fs from 'fs-extra'
import QRCode from 'qrcode'
import type {
PublishWechatShareCardRequest,
PublishWechatShareCardResult
} from '../shared/wechat-share-card'
import { WechatShareConfigStore } from './wechat-share-config-store'
const MAX_REPORT_BYTES = 25 * 1024 * 1024
export class WechatShareCardService {
constructor(private readonly configStore: WechatShareConfigStore) {}
async publish(request: PublishWechatShareCardRequest): Promise<PublishWechatShareCardResult> {
try {
const config = await this.configStore.loadRaw()
if (!config) return { success: false, error: '请先配置微信卡片服务' }
const png = await fs.readFile(request.pngPath)
if (!png.length) return { success: false, error: '日报图片为空' }
if (png.length > MAX_REPORT_BYTES) return { success: false, error: '日报图片不能超过 25 MB' }
const source = nativeImage.createFromBuffer(png)
if (source.isEmpty()) return { success: false, error: '无法读取日报图片' }
const size = source.getSize()
const squareSize = Math.min(size.width, size.height)
const thumbnail = source
.crop({ x: 0, y: 0, width: squareSize, height: squareSize })
.resize({ width: 360, height: 360, quality: 'best' })
.toJPEG(84)
const response = await fetch(`${config.serviceUrl}/api/cards`, {
method: 'POST',
headers: {
Authorization: `Bearer ${config.uploadToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: request.title.trim().slice(0, 64),
description: request.description.trim().slice(0, 120),
expiresInDays: Math.max(1, Math.min(30, request.expiresInDays || 7)),
imageBase64: png.toString('base64'),
thumbnailBase64: thumbnail.toString('base64')
}),
signal: AbortSignal.timeout(90_000)
})
const payload = (await response.json().catch(() => ({}))) as {
cardId?: string
shareUrl?: string
viewUrl?: string
expiresAt?: string
error?: string
}
if (!response.ok || !payload.shareUrl) {
return { success: false, error: payload.error || `卡片服务返回 HTTP ${response.status}` }
}
return {
success: true,
cardId: payload.cardId,
shareUrl: payload.shareUrl,
viewUrl: payload.viewUrl,
expiresAt: payload.expiresAt,
qrCodeDataUrl: await QRCode.toDataURL(payload.shareUrl, {
width: 360,
margin: 2,
errorCorrectionLevel: 'M'
})
}
} catch (error) {
return { success: false, error: error instanceof Error ? error.message : String(error) }
}
}
}