2025-05-09 13:57:28 +03:00
|
|
|
package connector
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2026-03-06 00:12:23 +02:00
|
|
|
"os"
|
2025-05-09 13:57:28 +03:00
|
|
|
|
|
|
|
|
"maunium.net/go/mautrix/bridgev2"
|
|
|
|
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
|
|
|
|
"maunium.net/go/mautrix/mediaproxy"
|
|
|
|
|
|
|
|
|
|
"go.mau.fi/mautrix-signal/pkg/signalid"
|
|
|
|
|
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
|
|
|
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var _ bridgev2.DirectMediableNetwork = (*SignalConnector)(nil)
|
|
|
|
|
|
|
|
|
|
func (s *SignalConnector) SetUseDirectMedia() {
|
|
|
|
|
s.MsgConv.DirectMedia = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaID, params map[string]string) (mediaproxy.GetMediaResponse, error) {
|
|
|
|
|
log := s.Bridge.Log.With().Str("component", "direct download").Logger()
|
|
|
|
|
|
|
|
|
|
info, err := signalid.ParseDirectMediaInfo(mediaID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to parse direct media id: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch info := info.(type) {
|
|
|
|
|
case *signalid.DirectMediaAttachment:
|
|
|
|
|
log.Info().
|
|
|
|
|
Uint64("cdn_id", info.CDNID).
|
|
|
|
|
Str("cdn_key", info.CDNKey).
|
|
|
|
|
Uint32("cdn_number", info.CDNNumber).
|
|
|
|
|
Int("key_len", len(info.Key)).
|
|
|
|
|
Int("digest_len", len(info.Digest)).
|
2025-07-28 16:17:55 +03:00
|
|
|
Bool("plaintext_digest", info.PlaintextDigest).
|
2025-05-09 13:57:28 +03:00
|
|
|
Uint32("size", info.Size).
|
|
|
|
|
Msg("Direct downloading attachment")
|
|
|
|
|
|
2026-03-06 00:12:23 +02:00
|
|
|
return &mediaproxy.GetMediaResponseFile{
|
|
|
|
|
Callback: func(w *os.File) (*mediaproxy.FileMeta, error) {
|
|
|
|
|
_, err := signalmeow.DownloadAttachment(
|
|
|
|
|
ctx, info.CDNID, info.CDNKey, info.CDNNumber, info.Key, info.Digest, info.PlaintextDigest, info.Size, w,
|
2025-07-28 16:17:55 +03:00
|
|
|
)
|
2025-05-09 13:57:28 +03:00
|
|
|
if err != nil {
|
2026-03-06 00:12:23 +02:00
|
|
|
return nil, err
|
2025-05-09 13:57:28 +03:00
|
|
|
}
|
2026-03-06 00:12:23 +02:00
|
|
|
return &mediaproxy.FileMeta{}, nil
|
2025-05-09 13:57:28 +03:00
|
|
|
},
|
|
|
|
|
}, nil
|
|
|
|
|
case *signalid.DirectMediaGroupAvatar:
|
|
|
|
|
log.Info().
|
|
|
|
|
Stringer("user_id", info.UserID).
|
|
|
|
|
Hex("group_id", info.GroupID[:]).
|
|
|
|
|
Str("group_avatar_path", info.GroupAvatarPath).
|
|
|
|
|
Msg("Direct downloading group avatar")
|
|
|
|
|
|
|
|
|
|
groupID := types.GroupIdentifier(base64.StdEncoding.EncodeToString(info.GroupID[:]))
|
|
|
|
|
|
|
|
|
|
userLogin, err := s.Bridge.GetExistingUserLoginByID(ctx, signalid.MakeUserLoginID(info.UserID))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to get user login: %w", err)
|
2025-06-02 18:11:30 +03:00
|
|
|
} else if userLogin == nil {
|
|
|
|
|
return nil, bridgev2.ErrNotLoggedIn
|
2025-05-09 13:57:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client := userLogin.Client.(*SignalClient)
|
|
|
|
|
|
|
|
|
|
groupMasterKey, err := client.Client.Store.GroupStore.MasterKeyFromGroupIdentifier(ctx, groupID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to to get group master key: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &mediaproxy.GetMediaResponseCallback{
|
|
|
|
|
Callback: func(w io.Writer) (int64, error) {
|
|
|
|
|
data, err := client.Client.DownloadGroupAvatar(ctx, info.GroupAvatarPath, groupMasterKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("Direct download failed")
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = w.Write(data)
|
|
|
|
|
return int64(len(data)), err
|
|
|
|
|
},
|
|
|
|
|
}, nil
|
|
|
|
|
case *signalid.DirectMediaProfileAvatar:
|
|
|
|
|
log.Info().
|
|
|
|
|
Stringer("user_id", info.UserID).
|
|
|
|
|
Stringer("contact_id", info.ContactID).
|
|
|
|
|
Str("profile_avatar_path", info.ProfileAvatarPath).
|
|
|
|
|
Msg("Direct downloading profile avatar")
|
|
|
|
|
|
|
|
|
|
userLogin, err := s.Bridge.GetExistingUserLoginByID(ctx, signalid.MakeUserLoginID(info.UserID))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to get user login: %w", err)
|
2025-06-02 18:11:30 +03:00
|
|
|
} else if userLogin == nil {
|
|
|
|
|
return nil, bridgev2.ErrNotLoggedIn
|
2025-05-09 13:57:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client := userLogin.Client.(*SignalClient)
|
|
|
|
|
|
|
|
|
|
profileKey, err := client.Client.Store.RecipientStore.LoadProfileKey(ctx, info.ContactID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to get contact: %w", err)
|
2025-06-02 18:11:30 +03:00
|
|
|
} else if profileKey == nil {
|
|
|
|
|
return nil, fmt.Errorf("profile key not found")
|
2025-05-09 13:57:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &mediaproxy.GetMediaResponseCallback{
|
|
|
|
|
Callback: func(w io.Writer) (int64, error) {
|
|
|
|
|
data, err := client.Client.DownloadUserAvatar(ctx, info.ProfileAvatarPath, *profileKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("Direct download failed")
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = w.Write(data)
|
|
|
|
|
return int64(len(data)), err
|
|
|
|
|
},
|
|
|
|
|
}, nil
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("no downloader for direct media type: %T", info)
|
|
|
|
|
}
|
|
|
|
|
}
|