feat: add WeChat media analysis adapters
This commit is contained in:
+100
-11
@@ -9,7 +9,11 @@ import { resolveSessionAccess } from './session-broker.mjs';
|
||||
import { isStubPublicHtmlContent, materializeMissingPublicHtmlWrites } from './mindspace-public-finish-sync.mjs';
|
||||
import { loadWechatMpConfig } from './wechat-mp-config.mjs';
|
||||
import { buildPublicUrl, PUBLISH_ROOT_DIR } from './user-publish.mjs';
|
||||
import { downloadTemporaryMedia, persistWechatImage } from './wechat-media.mjs';
|
||||
import {
|
||||
downloadTemporaryMedia,
|
||||
persistWechatAttachment,
|
||||
persistWechatImage,
|
||||
} from './wechat-media.mjs';
|
||||
import { normalizeWechatName, resolveWechatAddressName } from './wechat/user/display-name.mjs';
|
||||
import { buildAckText } from './wechat/ack/ack-provider.mjs';
|
||||
import { guardScheduleConfirmationReply } from './wechat/handlers/schedule-guard.mjs';
|
||||
@@ -100,6 +104,8 @@ function parseWechatMessage(xml) {
|
||||
description: parseXmlField(xml, 'Description'),
|
||||
url: parseXmlField(xml, 'Url'),
|
||||
thumbMediaId: parseXmlField(xml, 'ThumbMediaId'),
|
||||
fileName: parseXmlField(xml, 'FileName') || parseXmlField(xml, 'Filename'),
|
||||
fileSize: parseXmlField(xml, 'FileSize'),
|
||||
event: parseXmlField(xml, 'Event').toLowerCase(),
|
||||
eventKey: parseXmlField(xml, 'EventKey'),
|
||||
latitude: parseXmlField(xml, 'Latitude'),
|
||||
@@ -1210,6 +1216,22 @@ function normalizeWechatInboundIntent(inbound) {
|
||||
};
|
||||
}
|
||||
|
||||
if (msgType === 'file') {
|
||||
const filename = String(inbound?.fileName ?? '').trim();
|
||||
return {
|
||||
...base,
|
||||
displayText: filename ? `收到文件:${filename}` : '收到文件',
|
||||
agentText: '',
|
||||
media: {
|
||||
mediaId: inbound?.mediaId || '',
|
||||
},
|
||||
attachment: {
|
||||
filename,
|
||||
sizeBytes: normalizeNumber(inbound?.fileSize),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (msgType === 'location') {
|
||||
const latitude = normalizeNumber(inbound?.locationX);
|
||||
const longitude = normalizeNumber(inbound?.locationY);
|
||||
@@ -1434,8 +1456,10 @@ export function createWechatMpService({
|
||||
jsapiTicketUrl: config.jsapiTicketUrl || DEFAULT_WECHAT_JSAPI_TICKET_URL,
|
||||
mediaPublicBaseUrl: config.mediaPublicBaseUrl || config.publicBaseUrl,
|
||||
maxImageBytes: Math.max(1, Number(config.maxImageBytes ?? 10 * 1024 * 1024)),
|
||||
maxFileBytes: Math.max(1, Number(config.maxFileBytes ?? 30 * 1024 * 1024)),
|
||||
acceptVoice: config.acceptVoice !== false,
|
||||
acceptImage: config.acceptImage !== false,
|
||||
acceptFile: config.acceptFile !== false,
|
||||
acceptLocation: config.acceptLocation !== false,
|
||||
acceptLink: config.acceptLink !== false,
|
||||
asrTarget: config.asrTarget || DEFAULT_ASR_TARGET,
|
||||
@@ -1872,16 +1896,30 @@ export function createWechatMpService({
|
||||
}
|
||||
};
|
||||
|
||||
const buildIntentMetadata = (intent) => ({
|
||||
source: 'wechat_mp',
|
||||
msgType: intent.msgType,
|
||||
originalMsgId: intent.msgId || null,
|
||||
displayText: intent.displayText || '',
|
||||
mediaPublicUrl: intent.media?.publicUrl || null,
|
||||
recognition: intent.msgType === 'voice' ? intent.agentText || null : null,
|
||||
location: intent.location || null,
|
||||
link: intent.link || null,
|
||||
});
|
||||
const buildIntentMetadata = (intent) => {
|
||||
const mediaPublicUrl = intent.media?.publicUrl || null;
|
||||
const fileAttachment =
|
||||
intent.msgType === 'file' && mediaPublicUrl && intent.attachment?.filename
|
||||
? {
|
||||
assetId: '',
|
||||
downloadUrl: mediaPublicUrl,
|
||||
filename: intent.attachment.filename,
|
||||
mimeType: intent.attachment.mimeType || 'application/octet-stream',
|
||||
}
|
||||
: null;
|
||||
return {
|
||||
source: 'wechat_mp',
|
||||
msgType: intent.msgType,
|
||||
originalMsgId: intent.msgId || null,
|
||||
displayText: intent.displayText || '',
|
||||
mediaPublicUrl,
|
||||
...(intent.msgType === 'image' && mediaPublicUrl ? { imageUrls: [mediaPublicUrl] } : {}),
|
||||
...(fileAttachment ? { fileAttachments: [fileAttachment] } : {}),
|
||||
recognition: intent.msgType === 'voice' ? intent.agentText || null : null,
|
||||
location: intent.location || null,
|
||||
link: intent.link || null,
|
||||
};
|
||||
};
|
||||
|
||||
const persistIntentDetail = async ({ intent, userId = null, rawXmlHash = '' }) => {
|
||||
if (typeof userAuth.insertWechatMpMessageDetail !== 'function') return;
|
||||
@@ -2302,6 +2340,7 @@ export function createWechatMpService({
|
||||
const supportedByConfig =
|
||||
(intent.msgType === 'voice' && config.acceptVoice) ||
|
||||
(intent.msgType === 'image' && config.acceptImage) ||
|
||||
(intent.msgType === 'file' && config.acceptFile) ||
|
||||
(intent.msgType === 'location' && config.acceptLocation) ||
|
||||
(intent.msgType === 'link' && config.acceptLink) ||
|
||||
intent.msgType === 'text' ||
|
||||
@@ -2433,6 +2472,56 @@ export function createWechatMpService({
|
||||
}
|
||||
}
|
||||
|
||||
if (intent.msgType === 'file') {
|
||||
try {
|
||||
const accessToken = await getStableAccessToken();
|
||||
const persisted = await persistWechatAttachment(
|
||||
{
|
||||
userId: boundUser.userId,
|
||||
appId: config.appId,
|
||||
openid: inbound.fromUserName,
|
||||
msgId: inbound.msgId,
|
||||
mediaId: inbound.mediaId,
|
||||
filename: inbound.fileName,
|
||||
publicBaseUrl: config.mediaPublicBaseUrl,
|
||||
maxFileBytes: config.maxFileBytes,
|
||||
},
|
||||
{
|
||||
wechatFetch,
|
||||
accessToken,
|
||||
},
|
||||
);
|
||||
intent.media = {
|
||||
...intent.media,
|
||||
mediaId: inbound.mediaId || intent.media?.mediaId || '',
|
||||
publicUrl: persisted.publicUrl,
|
||||
format: persisted.contentType,
|
||||
source: persisted.source,
|
||||
};
|
||||
intent.attachment = {
|
||||
...intent.attachment,
|
||||
filename: persisted.filename,
|
||||
publicUrl: persisted.publicUrl,
|
||||
mimeType: persisted.contentType,
|
||||
sizeBytes: persisted.bytes,
|
||||
};
|
||||
intent.agentText = `[文件1: ${persisted.filename}]: ${persisted.publicUrl}`;
|
||||
intent.displayText = `文件:${persisted.filename}`;
|
||||
} catch (error) {
|
||||
await persistIntentDetail({ intent, userId: boundUser.userId, rawXmlHash });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
contentType: 'application/xml; charset=utf-8',
|
||||
body: buildWechatTextReply({
|
||||
toUserName: inbound.fromUserName,
|
||||
fromUserName: inbound.toUserName,
|
||||
content: error instanceof Error ? error.message : '文件处理失败,请稍后重试。',
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
intent.msgType === 'location' &&
|
||||
(intent.location?.latitude == null || intent.location?.longitude == null)
|
||||
|
||||
Reference in New Issue
Block a user