← Back to PRs

#19522: feat(bluebubbles): send TTS as native iMessage voice memos

by mwmacmahon open 2026-02-17 22:35 View on GitHub →
channel: bluebubbles size: S
## Summary Enables TTS replies to arrive as native iMessage voice bubbles instead of MP3 file attachments when using the BlueBubbles channel. ## Problem Three issues prevented voice memos from working correctly: 1. **OpenClaw gap:** `shouldVoice` in `tts.ts` only included Telegram. BlueBubbles TTS never got the `audioAsVoice` flag, and `monitor-processing.ts` never passed `asVoice` to `sendBlueBubblesMedia()` even though the parameter existed. 2. **BlueBubbles server bug:** BB's `convertMp3ToCaf()` converts to PCM Int16@44100Hz, but iMessage voice memos require Opus@24000Hz. iMessage rejects the wrong codec, showing 0-second unplayable audio. Filed upstream: BlueBubblesApp/bluebubbles-server#773 3. **Text dropped from voice memo replies:** When block streaming is active, the TTS-only payload intentionally omits text to avoid duplication. But for voice memo channels, block-streamed text arrives as separate messages that don't visually pair with the voice bubble — recipients only see a voice memo with no text. ## Changes | File | Change | |------|--------| | `src/tts/tts.ts:910` | Add BlueBubbles to `shouldVoice` condition | | `extensions/bluebubbles/src/monitor-processing.ts:752` | Pass `asVoice: payload.audioAsVoice === true` to `sendBlueBubblesMedia()` | | `extensions/bluebubbles/src/attachments.ts` | Add `convertMp3ToOpusCaf()` with platform guard — pre-converts MP3 to Opus@24000Hz CAF before BB API call | | `src/auto-reply/reply/dispatch-from-config.ts` | Include text in TTS-only payload when `audioAsVoice` is true | ## How It Works - Patches 1+2 route BlueBubbles TTS through the `isAudioMessage=true` code path - Patch 3 converts MP3→Opus CAF using macOS-native `/usr/bin/afconvert` before handing to BB. Includes `process.platform !== "darwin"` guard for graceful degradation on non-macOS - BB's send path (`messageInterface.ts:277`) only converts `.mp3` files, so our `.caf` passes through untouched to iMessage - Patch 4 ensures text is delivered alongside voice memos when block streaming is used — adds `text` to the TTS-only payload when `audioAsVoice` is true (non-voice payloads unchanged) ## Platform Note The conversion requires macOS `/usr/bin/afconvert` (always present on macOS). BlueBubbles itself is macOS-only (requires Messages.app and the Private API helper), so in practice this code path can only run on macOS. The platform guard is a safety net — it makes the macOS requirement explicit rather than relying on a cryptic "command not found" error, and ensures graceful degradation if the BB extension is ever ported. ## Testing - [x] MP3 as voice memo via BB API → voice bubble plays with correct duration - [x] TTS reply via iMessage → arrives as playable voice bubble with text - [x] Regular MP3 attachments → still work as file attachments (no regression) - [x] afconvert failure path → falls back to MP3, logs `BB_VOICE_CONVERT_FAIL` - [x] Voice memo includes paired text message (not voice-only) ## Related - Upstream BB bug: BlueBubblesApp/bluebubbles-server#773 - Fixes #16848 <!-- greptile_comment --> <h3>Greptile Summary</h3> Enables BlueBubbles to send TTS replies as native iMessage voice bubbles by routing through the voice memo code path and converting MP3 to Opus CAF format. The implementation correctly addresses three issues: adds BlueBubbles to the `shouldVoice` condition in `tts.ts:908`, passes the `asVoice` flag through to `sendBlueBubblesMedia()` in `monitor-processing.ts:941`, converts MP3 to the correct Opus@24000Hz CAF format using macOS `/usr/bin/afconvert`, and includes text in voice memo payloads to prevent text-audio separation in the conversation UI. The conversion logic includes proper platform guards and fallback handling. <h3>Confidence Score: 4/5</h3> - Safe to merge after addressing the command injection concern in attachments.ts - The implementation is well-architected with proper error handling, platform guards, and fallback mechanisms. The logic correctly routes BlueBubbles TTS through the voice memo path and solves the text-pairing issue. However, the use of `execSync` with string interpolation (already flagged in previous comments) should be replaced with `execFileSync` before merging to prevent potential command injection vulnerabilities, even though the current paths are system-generated. - Pay attention to `extensions/bluebubbles/src/attachments.ts` - ensure the command injection fix is applied before merging <sub>Last reviewed commit: 69f49de</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs