From 226621f523c2e7ec6ca50c8dada64bb520be88b0 Mon Sep 17 00:00:00 2001 From: Chad Kouse Date: Thu, 31 Jul 2025 12:48:43 -0400 Subject: [PATCH 1/5] Allow double puppeting with isolated appservice registration --- custompuppet.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custompuppet.go b/custompuppet.go index 8ea2593d..b1aa2176 100644 --- a/custompuppet.go +++ b/custompuppet.go @@ -22,6 +22,7 @@ import ( "encoding/hex" "errors" "fmt" + "strings" "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" @@ -86,6 +87,9 @@ func (user *User) loginWithSharedSecret() error { if loginSecret == "appservice" { client.AccessToken = user.bridge.AS.Registration.AppToken req.Type = mautrix.AuthTypeAppservice + } else if strings.HasPrefix(loginSecret, "as_token:") { + client.AccessToken = loginSecret[9:] + req.Type = mautrix.AuthTypeAppservice } else { mac := hmac.New(sha512.New, []byte(loginSecret)) mac.Write([]byte(user.MXID)) From ee338f9ad3a05a5a7d1f92805521ce291a8527b5 Mon Sep 17 00:00:00 2001 From: Chad Kouse Date: Fri, 27 Mar 2026 18:21:55 -0400 Subject: [PATCH 2/5] Use afconvert for audio messages instead of (non-working) ffmpeg --- portal.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/portal.go b/portal.go index 8afb8904..38b8f205 100644 --- a/portal.go +++ b/portal.go @@ -21,6 +21,7 @@ import ( "crypto/sha256" "errors" "fmt" + "os/exec" "path/filepath" "runtime/debug" "sort" @@ -1411,15 +1412,15 @@ func (portal *Portal) handleMatrixMediaDirect(url id.ContentURI, file *event.Enc // Only convert when sending to iMessage. SMS users probably don't want CAF. if portal.Identifier.Service == "iMessage" && isMSC3245Voice && strings.HasPrefix(mimeType, "audio/") { - filePath, err = ffmpeg.ConvertPath(context.TODO(), filePath, ".caf", []string{}, []string{"-c:a", "libopus"}, false) - mimeType = "audio/x-caf" - isVoiceMemo = true - filename = filepath.Base(filePath) - - if err != nil { + cafPath := strings.TrimSuffix(filePath, filepath.Ext(filePath)) + ".caf" + if err = exec.Command("afconvert", "-f", "caff", "-d", "opus", filePath, cafPath).Run(); err != nil { log.Errorfln("Failed to transcode voice message to CAF. Error: %w", err) return } + filePath = cafPath + mimeType = "audio/x-caf" + isVoiceMemo = true + filename = filepath.Base(filePath) } resp, err = portal.bridge.IM.SendFile(portal.getTargetGUID("media message", evt.ID, ""), caption, filename, filePath, messageReplyID, messageReplyPart, mimeType, isVoiceMemo, metadata) From 3e6a9551151cca7bc5cab618d8e450eddc99bbce Mon Sep 17 00:00:00 2001 From: Chad Kouse Date: Fri, 27 Mar 2026 18:26:31 -0400 Subject: [PATCH 3/5] Add logging to debug afconvert issue --- portal.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/portal.go b/portal.go index 38b8f205..612bf6f2 100644 --- a/portal.go +++ b/portal.go @@ -1413,8 +1413,10 @@ func (portal *Portal) handleMatrixMediaDirect(url id.ContentURI, file *event.Enc // Only convert when sending to iMessage. SMS users probably don't want CAF. if portal.Identifier.Service == "iMessage" && isMSC3245Voice && strings.HasPrefix(mimeType, "audio/") { cafPath := strings.TrimSuffix(filePath, filepath.Ext(filePath)) + ".caf" - if err = exec.Command("afconvert", "-f", "caff", "-d", "opus", filePath, cafPath).Run(); err != nil { - log.Errorfln("Failed to transcode voice message to CAF. Error: %w", err) + cmd := exec.Command("afconvert", "-f", "caff", "-d", "opus", filePath, cafPath) + if out, convErr := cmd.CombinedOutput(); convErr != nil { + log.Errorfln("Failed to transcode voice message to CAF: %v\nafconvert output: %s", convErr, out) + err = convErr return } filePath = cafPath From 8fd9979f27d9ee96d9e95ee91acedd549fcde911 Mon Sep 17 00:00:00 2001 From: Chad Kouse Date: Fri, 27 Mar 2026 18:29:17 -0400 Subject: [PATCH 4/5] First convert ogg to aiff, then aiff to caf for voice memos --- portal.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/portal.go b/portal.go index 612bf6f2..7298a7f9 100644 --- a/portal.go +++ b/portal.go @@ -1412,10 +1412,18 @@ func (portal *Portal) handleMatrixMediaDirect(url id.ContentURI, file *event.Enc // Only convert when sending to iMessage. SMS users probably don't want CAF. if portal.Identifier.Service == "iMessage" && isMSC3245Voice && strings.HasPrefix(mimeType, "audio/") { - cafPath := strings.TrimSuffix(filePath, filepath.Ext(filePath)) + ".caf" - cmd := exec.Command("afconvert", "-f", "caff", "-d", "opus", filePath, cafPath) - if out, convErr := cmd.CombinedOutput(); convErr != nil { - log.Errorfln("Failed to transcode voice message to CAF: %v\nafconvert output: %s", convErr, out) + basePath := strings.TrimSuffix(filePath, filepath.Ext(filePath)) + aiffPath := basePath + ".aiff" + cafPath := basePath + ".caf" + // ffmpeg decodes OGG/Opus (which afconvert cannot read) to AIFF + if out, convErr := exec.Command("ffmpeg", "-i", filePath, "-f", "aiff", aiffPath).CombinedOutput(); convErr != nil { + log.Errorfln("Failed to decode voice message to AIFF: %v\nffmpeg output: %s", convErr, out) + err = convErr + return + } + // afconvert encodes AIFF -> CAF/AAC (the standard format for iMessage voice memos) + if out, convErr := exec.Command("afconvert", "-f", "caff", "-d", "aac", aiffPath, cafPath).CombinedOutput(); convErr != nil { + log.Errorfln("Failed to encode voice message to CAF: %v\nafconvert output: %s", convErr, out) err = convErr return } From 6bdcc46c588e4ba8ffbceac4e089a24d8b916a54 Mon Sep 17 00:00:00 2001 From: Chad Kouse Date: Thu, 2 Apr 2026 08:35:07 -0400 Subject: [PATCH 5/5] Trying to fix voice memos still --- portal.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/portal.go b/portal.go index 7298a7f9..9187d114 100644 --- a/portal.go +++ b/portal.go @@ -1411,19 +1411,21 @@ func (portal *Portal) handleMatrixMediaDirect(url id.ContentURI, file *event.Enc _, isMSC3245Voice := evt.Content.Raw["org.matrix.msc3245.voice"] // Only convert when sending to iMessage. SMS users probably don't want CAF. + // iMessage voice memos use CAF with Opus codec at 24000 Hz (since iOS 12.2). + // ffmpeg cannot mux Opus into CAF, so we use a two-stage conversion: + // 1. ffmpeg decodes OGG to AIFF (afconvert cannot read OGG) + // 2. afconvert encodes AIFF to CAF/Opus if portal.Identifier.Service == "iMessage" && isMSC3245Voice && strings.HasPrefix(mimeType, "audio/") { basePath := strings.TrimSuffix(filePath, filepath.Ext(filePath)) aiffPath := basePath + ".aiff" cafPath := basePath + ".caf" - // ffmpeg decodes OGG/Opus (which afconvert cannot read) to AIFF if out, convErr := exec.Command("ffmpeg", "-i", filePath, "-f", "aiff", aiffPath).CombinedOutput(); convErr != nil { log.Errorfln("Failed to decode voice message to AIFF: %v\nffmpeg output: %s", convErr, out) err = convErr return } - // afconvert encodes AIFF -> CAF/AAC (the standard format for iMessage voice memos) - if out, convErr := exec.Command("afconvert", "-f", "caff", "-d", "aac", aiffPath, cafPath).CombinedOutput(); convErr != nil { - log.Errorfln("Failed to encode voice message to CAF: %v\nafconvert output: %s", convErr, out) + if out, convErr := exec.Command("afconvert", "-f", "caff", "-d", "opus", aiffPath, cafPath).CombinedOutput(); convErr != nil { + log.Errorfln("Failed to encode voice message to CAF/Opus: %v\nafconvert output: %s", convErr, out) err = convErr return }