@@ -59,13 +59,15 @@ export function buildMediaTarget(ctx: {
5959 to : string ;
6060 account : GatewayAccount ;
6161 replyToId ?: string | null ;
62+ extraLocalRoots ?: readonly string [ ] ;
6263} ) : MediaTargetContext {
6364 const target = parseTarget ( ctx . to ) ;
6465 return {
6566 targetType : target . type ,
6667 targetId : target . id ,
6768 account : ctx . account ,
6869 replyToId : ctx . replyToId ?? undefined ,
70+ extraLocalRoots : ctx . extraLocalRoots ,
6971 } ;
7072}
7173
@@ -307,6 +309,7 @@ export async function sendVoice(
307309) : Promise < OutboundResult > {
308310 const resolvedMediaPath = resolveOutboundMediaPath ( voicePath , "voice" , {
309311 allowMissingLocalPath : true ,
312+ extraLocalRoots : ctx . extraLocalRoots ? [ ...ctx . extraLocalRoots ] : undefined ,
310313 } ) ;
311314 if ( ! resolvedMediaPath . ok ) {
312315 return { channel : "qqbot" , error : resolvedMediaPath . error } ;
@@ -368,16 +371,19 @@ async function sendVoiceFromLocal(
368371 }
369372
370373 // Re-check containment after the file appears to prevent symlink-race escapes.
371- const safeMediaPath = resolveQQBotPayloadLocalFilePath ( mediaPath ) ;
372- if ( ! safeMediaPath ) {
374+ const safeMediaPath = resolveOutboundMediaPath ( mediaPath , "voice" , {
375+ extraLocalRoots : ctx . extraLocalRoots ? [ ...ctx . extraLocalRoots ] : undefined ,
376+ } ) ;
377+ if ( ! safeMediaPath . ok ) {
373378 debugWarn ( `sendVoice: blocked local voice path outside QQ Bot media storage` ) ;
374- return { channel : "qqbot" , error : "Voice path must be inside QQ Bot media storage" } ;
379+ return { channel : "qqbot" , error : safeMediaPath . error } ;
375380 }
381+ const safeLocalPath = safeMediaPath . mediaPath ;
376382
377- const needsTranscode = shouldTranscodeVoice ( safeMediaPath ) ;
383+ const needsTranscode = shouldTranscodeVoice ( safeLocalPath ) ;
378384
379385 if ( needsTranscode && ! transcodeEnabled ) {
380- const ext = normalizeLowercaseStringOrEmpty ( path . extname ( safeMediaPath ) ) ;
386+ const ext = normalizeLowercaseStringOrEmpty ( path . extname ( safeLocalPath ) ) ;
381387 debugLog (
382388 `sendVoice: transcode disabled, format ${ ext } needs transcode, returning error for fallback` ,
383389 ) ;
@@ -388,11 +394,11 @@ async function sendVoiceFromLocal(
388394 }
389395
390396 try {
391- const silkBase64 = await audioFileToSilkBase64 ( safeMediaPath , directUploadFormats ) ;
397+ const silkBase64 = await audioFileToSilkBase64 ( safeLocalPath , directUploadFormats ) ;
392398 let uploadBase64 = silkBase64 ;
393399
394400 if ( ! uploadBase64 ) {
395- const buf = await readFileAsync ( safeMediaPath ) ;
401+ const buf = await readFileAsync ( safeLocalPath ) ;
396402 uploadBase64 = buf . toString ( "base64" ) ;
397403 debugLog ( `sendVoice: SILK conversion failed, uploading raw (${ formatFileSize ( buf . length ) } )` ) ;
398404 } else {
@@ -409,7 +415,7 @@ async function sendVoiceFromLocal(
409415 kind : "voice" ,
410416 source : { base64 : uploadBase64 } ,
411417 msgId : ctx . replyToId ,
412- localPathForMeta : safeMediaPath ,
418+ localPathForMeta : safeLocalPath ,
413419 } ) ;
414420 return { channel : "qqbot" , messageId : r . id , timestamp : r . timestamp } ;
415421 }
0 commit comments