@@ -15,6 +15,7 @@ import {
1515} from "./types" ;
1616import type { WeixinMessage , WechatMessageItem , WechatCredentials , CDNMedia } from "./types" ;
1717
18+
1819export interface WechatMessageArgs extends ChannelMessageArgs {
1920 messageId : number ;
2021 fromUserId : string ;
@@ -37,7 +38,6 @@ export class WechatService implements IChannelService {
3738 private _abortController ?: AbortController ;
3839 private _updatesBuf = "" ;
3940 private _lastMessageId = 0 ;
40- /** Map of `botId:userId` → context_token, needed for typing and reply continuity */
4141 private _contextTokens = new Map < string , string > ( ) ;
4242 private _botId : string ;
4343
@@ -226,61 +226,80 @@ export class WechatService implements IChannelService {
226226 const items = msg . item_list ?? [ ] ;
227227 const parts : Array < { type : string ; text ?: string ; [ key : string ] : any } > = [ ] ;
228228 let hasImage = false ;
229+
229230 for ( const item of items ) {
230- switch ( item . type ) {
231- case WechatMessageItemType . TEXT : parts . push ( { type : 'text' , text : item . text_item ?. text ?? "" } ) ; break ;
232- case WechatMessageItemType . VOICE : parts . push ( { type : 'text' , text : item . voice_item ?. text ?? "" } ) ; break ;
233- case WechatMessageItemType . FILE : {
234- const fileContent = await this . extractFileContent ( msg . message_id , item ) ;
235- parts . push ( { type : 'text' , text : fileContent } ) ;
236- break ;
237- }
238- case WechatMessageItemType . IMAGE : {
239- const imagePart = await this . extractImagePart ( msg . message_id , item ) ;
240- parts . push ( imagePart ) ;
241- if ( imagePart . type === 'image_url' ) hasImage = true ;
242- break ;
231+ if ( item . ref_msg ?. message_item ) {
232+ const refParts = await this . extractItemParts ( msg . message_id , item . ref_msg . message_item , "引用" ) ;
233+ for ( const rp of refParts ) {
234+ parts . push ( rp ) ;
235+ if ( rp . type === 'image_url' ) hasImage = true ;
243236 }
244- case WechatMessageItemType . VIDEO : parts . push ( { type : 'text' , text : "[视频]" } ) ; break ;
237+ }
238+
239+ const itemParts = await this . extractItemParts ( msg . message_id , item ) ;
240+ for ( const p of itemParts ) {
241+ parts . push ( p ) ;
242+ if ( p . type === 'image_url' ) hasImage = true ;
245243 }
246244 }
247- // If no images, return plain string for backward compat
248245 if ( ! hasImage ) return parts . map ( p => p . text ! ) . join ( "\n" ) . trim ( ) ;
249246 return parts ;
250247 }
251248
252- private async extractImagePart ( messageId : number | undefined , item : WechatMessageItem ) : Promise < { type : string ; [ key : string ] : any } > {
253- const media = item . image_item ?. media ;
254- if ( ! media ?. encrypt_query_param ) {
255- return { type : 'text' , text : "[图片]" } ;
256- }
257- try {
258- const buffer = await this . api . downloadFromCDN ( media . encrypt_query_param , media . aes_key ) ;
259- const filePath = path . join ( os . tmpdir ( ) , `wechat_${ messageId ?? Date . now ( ) } _img` ) ;
260- await fs . writeFile ( filePath , buffer ) ;
261- const dataUrl = await readImageAsDataUrl ( filePath ) ;
262- return { type : 'image_url' , image_url : { url : dataUrl } } ;
263- } catch ( e : any ) {
264- this . logger ?. error ( `Failed to download image: ${ e . message } ` ) ;
265- return { type : 'text' , text : "[图片]" } ;
266- }
267- }
249+ private async extractItemParts ( messageId : number | undefined , item : WechatMessageItem , label ?: string ) : Promise < Array < { type : string ; [ key : string ] : any } > > {
250+ switch ( item . type ) {
251+ case WechatMessageItemType . TEXT : {
252+ const text = item . text_item ?. text ?? "" ;
253+ return [ { type : 'text' , text : label ? `[${ label } : ${ text } ]` : text } ] ;
254+ }
268255
269- private async extractFileContent ( messageId : number | undefined , item : WechatMessageItem ) : Promise < string > {
270- const fileName = item . file_item ?. file_name ?? "unknown_file" ;
271- const media = item . file_item ?. media ;
272- if ( ! media ?. encrypt_query_param ) {
273- return `[文件: ${ fileName } ]` ;
274- }
275- try {
276- const buffer = await this . api . downloadFromCDN ( media . encrypt_query_param , media . aes_key ) ;
277- const ext = path . extname ( fileName ) ;
278- const filePath = path . join ( os . tmpdir ( ) , `wechat_${ messageId ?? Date . now ( ) } ${ ext } ` ) ;
279- await fs . writeFile ( filePath , buffer ) ;
280- return `[file: ${ fileName } ](${ filePath } )` ;
281- } catch ( e : any ) {
282- this . logger ?. error ( `Failed to download file "${ fileName } ": ${ e . message } ` ) ;
283- return `[文件: ${ fileName } ]` ;
256+ case WechatMessageItemType . VOICE : {
257+ const text = item . voice_item ?. text ?? "" ;
258+ return [ { type : 'text' , text : label ? `[${ label } : ${ text } ]` : text } ] ;
259+ }
260+
261+ case WechatMessageItemType . IMAGE : {
262+ const media = item . image_item ?. media ;
263+ const imgFallback = label ? `[${ label } : 图片]` : "[图片]" ;
264+ if ( ! media ?. encrypt_query_param ) return [ { type : 'text' , text : imgFallback } ] ;
265+ try {
266+ const buffer = await this . api . downloadFromCDN ( media . encrypt_query_param , media . aes_key ) ;
267+ const filePath = path . join ( os . tmpdir ( ) , `wechat_${ messageId ?? Date . now ( ) } _img` ) ;
268+ await fs . writeFile ( filePath , buffer ) ;
269+ const dataUrl = await readImageAsDataUrl ( filePath ) ;
270+ const parts : Array < { type : string ; [ key : string ] : any } > = [ ] ;
271+ if ( label ) parts . push ( { type : 'text' , text : imgFallback } ) ;
272+ parts . push ( { type : 'image_url' , image_url : { url : dataUrl } } ) ;
273+ return parts ;
274+ } catch ( e : any ) {
275+ this . logger ?. error ( `Failed to download image: ${ e . message } ` ) ;
276+ return [ { type : 'text' , text : imgFallback } ] ;
277+ }
278+ }
279+
280+ case WechatMessageItemType . FILE : {
281+ const fileName = item . file_item ?. file_name ?? "unknown_file" ;
282+ const media = item . file_item ?. media ;
283+ const fileFallback = label ? `[${ label } : 文件 ${ fileName } ]` : `[文件: ${ fileName } ]` ;
284+ if ( ! media ?. encrypt_query_param ) return [ { type : 'text' , text : fileFallback } ] ;
285+ try {
286+ const buffer = await this . api . downloadFromCDN ( media . encrypt_query_param , media . aes_key ) ;
287+ const ext = path . extname ( fileName ) ;
288+ const filePath = path . join ( os . tmpdir ( ) , `wechat_${ messageId ?? Date . now ( ) } ${ ext } ` ) ;
289+ await fs . writeFile ( filePath , buffer ) ;
290+ const linkText = label ? `[${ label } : file ${ fileName } ](${ filePath } )` : `[file: ${ fileName } ](${ filePath } )` ;
291+ return [ { type : 'text' , text : linkText } ] ;
292+ } catch ( e : any ) {
293+ this . logger ?. error ( `Failed to download file "${ fileName } ": ${ e . message } ` ) ;
294+ return [ { type : 'text' , text : fileFallback } ] ;
295+ }
296+ }
297+
298+ case WechatMessageItemType . VIDEO :
299+ return [ { type : 'text' , text : label ? `[${ label } : 视频]` : "[视频]" } ] ;
300+
301+ default :
302+ return [ ] ;
284303 }
285304 }
286305
0 commit comments