diff --git a/commands.go b/commands.go index 728f2d62..e3021c01 100644 --- a/commands.go +++ b/commands.go @@ -989,7 +989,7 @@ func fnCreate(ce *WrappedCommandEvent) { avatarSet := false if ok { roomAvatarEvent.Content.ParseRaw(event.StateRoomAvatar) - avatarURL = roomAvatarEvent.Content.AsRoomAvatar().URL + avatarURL = roomAvatarEvent.Content.AsRoomAvatar().URL.ParseOrIgnore() if !avatarURL.IsEmpty() { avatarBytes, err = ce.Bot.DownloadBytes(ce.Ctx, avatarURL) if err != nil { diff --git a/connector/connector.go b/connector/connector.go index 9667ae50..770259c8 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -145,8 +145,115 @@ type SignalClient struct { Client *signalmeow.Client } +func (s *SignalClient) contactToUserInfo(contact *types.Recipient) *bridgev2.UserInfo { + isBot := false + ui := &bridgev2.UserInfo{ + IsBot: &isBot, + } + // TODO use template for name + if contact.ContactName != "" { + ui.Name = &contact.ContactName + } else if contact.Profile.Name != "" { + ui.Name = &contact.Profile.Name + } else if contact.E164 != "" { + ui.Name = &contact.E164 + } + // TODO only use this if contact avatars are allowed + if contact.ContactAvatar.Hash != "" { + ui.Avatar = &bridgev2.Avatar{ + ID: networkid.AvatarID("hash:" + contact.ContactAvatar.Hash), + Get: func(ctx context.Context) ([]byte, error) { + if contact.ContactAvatar.Image == nil { + return nil, fmt.Errorf("contact avatar not available") + } + return contact.ContactAvatar.Image, nil + }, + } + } else if contact.Profile.AvatarPath != "" { + ui.Avatar = &bridgev2.Avatar{ + ID: makeAvatarPathID(contact.Profile.AvatarPath), + Get: func(ctx context.Context) ([]byte, error) { + return s.Client.DownloadUserAvatar(ctx, contact.Profile.AvatarPath, contact.Profile.Key) + }, + } + } else { + ui.Avatar = &bridgev2.Avatar{ + ID: "", + Remove: true, + } + } + return ui +} + +func (s *SignalClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) { + userID, err := parseUserID(ghost.ID) + if err != nil { + return nil, err + } + contact, err := s.Client.ContactByACI(ctx, userID) + if err != nil { + return nil, err + } + return s.contactToUserInfo(contact), nil +} + +func makeAvatarPathID(avatarPath string) networkid.AvatarID { + if avatarPath == "" { + return "" + } + return networkid.AvatarID("path:" + avatarPath) +} + func (s *SignalClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.PortalInfo, error) { - return &bridgev2.PortalInfo{}, nil + userID, groupID, err := s.parsePortalID(portal.ID) + if err != nil { + return nil, err + } + isSpace := false + if groupID != "" { + groupInfo, err := s.Client.RetrieveGroupByID(ctx, groupID, 0) + if err != nil { + return nil, err + } + isDM := false + members := make([]networkid.UserID, len(groupInfo.Members)) + for i, member := range groupInfo.Members { + members[i] = makeUserID(member.ACI) + } + return &bridgev2.PortalInfo{ + Name: &groupInfo.Title, + Topic: &groupInfo.Description, + Avatar: &bridgev2.Avatar{ + ID: makeAvatarPathID(groupInfo.AvatarPath), + Get: func(ctx context.Context) ([]byte, error) { + return s.Client.DownloadGroupAvatar(ctx, groupInfo) + }, + Remove: groupInfo.AvatarPath == "", + }, + Members: members, + IsDirectChat: &isDM, + IsSpace: &isSpace, + }, nil + } else if userID.Type == libsignalgo.ServiceIDTypePNI { + isDM := true + // TODO set name/avatar because we don't have the recipient user ID + return &bridgev2.PortalInfo{ + Members: []networkid.UserID{makeUserID(s.Client.Store.ACI)}, + IsDirectChat: &isDM, + IsSpace: &isSpace, + }, nil + } else { + isDM := true + return &bridgev2.PortalInfo{ + Members: []networkid.UserID{makeUserID(userID.UUID), makeUserID(s.Client.Store.ACI)}, + IsDirectChat: &isDM, + IsSpace: &isSpace, + }, nil + } +} + +func (s *SignalClient) IsThisUser(_ context.Context, userID networkid.UserID) bool { + return userID == makeUserID(s.Client.Store.ACI) } func (s *SignalClient) Connect(ctx context.Context) error { @@ -162,24 +269,31 @@ func (s *SignalClient) IsLoggedIn() bool { return s.Client.IsLoggedIn() } -func (s *SignalClient) parsePortalID(portalID networkid.PortalID) (string, error) { +func parseUserID(userID networkid.UserID) (uuid.UUID, error) { + return uuid.Parse(string(userID)) +} + +func (s *SignalClient) parsePortalID(portalID networkid.PortalID) (userID libsignalgo.ServiceID, groupID types.GroupIdentifier, err error) { parts := strings.Split(string(portalID), "|") if len(parts) == 1 { if len(parts[0]) == 44 { - return parts[0], nil + groupID = types.GroupIdentifier(parts[0]) + } else { + err = fmt.Errorf("invalid portal ID: expected group ID to be 44 characters") } - return "", fmt.Errorf("invalid portal ID: expected group ID to be 44 characters") } else if len(parts) == 2 { ourACI := s.Client.Store.ACI.String() if parts[0] == ourACI { - return parts[1], nil + userID, err = libsignalgo.ServiceIDFromString(parts[1]) } else if parts[1] == ourACI { - return parts[0], nil + userID, err = libsignalgo.ServiceIDFromString(parts[0]) } else { - return "", fmt.Errorf("invalid portal ID: expected one side to be our ACI") + err = fmt.Errorf("invalid portal ID: expected one side to be our ACI") } + } else { + err = fmt.Errorf("invalid portal ID: unexpected number of pipe-separated parts") } - return "", fmt.Errorf("invalid portal ID: unexpected number of pipe-separated parts") + return } func (s *SignalClient) getPortalID(chatID string) networkid.PortalID { @@ -237,6 +351,13 @@ type msgconvContext struct { } func (s *SignalClient) convertMessage(ctx context.Context, portal *bridgev2.Portal, data *events.ChatEvent) (*bridgev2.ConvertedMessage, error) { + mcCtx := &msgconvContext{ + Connector: s.Main, + Intent: s.Main.Bridge.Bot, // TODO get correct intent? + Client: s, + Portal: portal, + } + ctx = context.WithValue(ctx, msgconvContextKey, mcCtx) dataMsg := data.Event.(*signalpb.DataMessage) converted := s.Main.MsgConv.ToMatrix(ctx, dataMsg) var replyTo *networkid.MessageOptionalPartID @@ -304,20 +425,10 @@ func (s *SignalClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Ma ReplyTo: msg.ReplyTo, } ctx = context.WithValue(ctx, msgconvContextKey, mcCtx) - chatID, err := s.parsePortalID(msg.Portal.ID) + userID, groupID, err := s.parsePortalID(msg.Portal.ID) if err != nil { return nil, err } - var userID libsignalgo.ServiceID - var groupID types.GroupIdentifier - if len(chatID) == 44 { - groupID = types.GroupIdentifier(chatID) - } else { - userID, err = libsignalgo.ServiceIDFromString(chatID) - if err != nil { - return nil, err - } - } converted, err := s.Main.MsgConv.ToSignal(ctx, msg.Event, msg.Content, msg.OrigSender != nil) if err != nil { return nil, err @@ -414,7 +525,11 @@ func (mpm *msgconvPortalMethods) GetClient(ctx context.Context) *signalmeow.Clie func (mpm *msgconvPortalMethods) GetData(ctx context.Context) *legacydb.Portal { mcCtx := ctx.Value(msgconvContextKey).(*msgconvContext) portal := mcCtx.Portal - chatID, _ := mcCtx.Client.parsePortalID(portal.ID) + userID, groupID, _ := mcCtx.Client.parsePortalID(portal.ID) + chatID := string(groupID) + if chatID == "" { + chatID = userID.String() + } pk := legacydb.PortalKey{ ChatID: chatID, } diff --git a/go.mod b/go.mod index 87438053..22b07b36 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/net v0.25.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.18.2-0.20240527105318-9254a5d6c1d9 + maunium.net/go/mautrix v0.18.2-0.20240528174923-3c7b3e13efe4 nhooyr.io/websocket v1.8.11 ) diff --git a/go.sum b/go.sum index f68078ab..4d6c887f 100644 --- a/go.sum +++ b/go.sum @@ -95,7 +95,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.18.2-0.20240527105318-9254a5d6c1d9 h1:pRnJkxSjLsJSWON0yWzHNDyQqeebvgCeRXzozntEj/0= -maunium.net/go/mautrix v0.18.2-0.20240527105318-9254a5d6c1d9/go.mod h1:Ln4XquIKL5MttTUGNUSbiEGX3XYC0P6jzT9XjLFFPdY= +maunium.net/go/mautrix v0.18.2-0.20240528174923-3c7b3e13efe4 h1:b+pK+EJL2XsweSKlwhd8eeqD4v395dKrcJZmCFTBmBY= +maunium.net/go/mautrix v0.18.2-0.20240528174923-3c7b3e13efe4/go.mod h1:Ln4XquIKL5MttTUGNUSbiEGX3XYC0P6jzT9XjLFFPdY= nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/portal.go b/portal.go index 2e90ac77..b0ba6353 100644 --- a/portal.go +++ b/portal.go @@ -1765,7 +1765,7 @@ func (portal *Portal) CreateMatrixRoom(ctx context.Context, user *User, groupRev initialState = append(initialState, &event.Event{ Type: event.StateRoomAvatar, Content: event.Content{Parsed: &event.RoomAvatarEventContent{ - URL: portal.AvatarURL, + URL: portal.AvatarURL.CUString(), }}, }) } @@ -2910,17 +2910,18 @@ func (portal *Portal) HandleMatrixMeta(brSender bridge.User, evt *event.Event) { portal.Topic = content.Topic groupChange.ModifyDescription = &content.Topic case *event.RoomAvatarEventContent: - if content.URL == portal.AvatarURL { + url := content.URL.ParseOrIgnore() + if url == portal.AvatarURL { return } var data []byte - if !content.URL.IsEmpty() { - data, err = portal.MainIntent().DownloadBytes(ctx, content.URL) + if !url.IsEmpty() { + data, err = portal.MainIntent().DownloadBytes(ctx, url) if err != nil { - log.Err(err).Stringer("Failed to download updated avatar %s", content.URL) + log.Err(err).Stringer("Failed to download updated avatar %s", url) return } - log.Debug().Stringers("%s set the group avatar to %s", []fmt.Stringer{sender.MXID, content.URL}) + log.Debug().Stringers("%s set the group avatar to %s", []fmt.Stringer{sender.MXID, url}) } else { log.Debug().Stringer("%s removed the group avatar", sender.MXID) } @@ -2933,7 +2934,7 @@ func (portal *Portal) HandleMatrixMeta(brSender bridge.User, evt *event.Event) { hash := sha256.Sum256(data) avatarHash = hex.EncodeToString(hash[:]) avatarChanged = true - avatarURL = content.URL + avatarURL = url } revision, err := sender.Client.UpdateGroup(ctx, groupChange, portal.GroupID()) if err != nil { diff --git a/user.go b/user.go index a579e8b5..0a72b05f 100644 --- a/user.go +++ b/user.go @@ -292,7 +292,7 @@ func (user *User) GetSpaceRoom(ctx context.Context) id.RoomID { Type: event.StateRoomAvatar, Content: event.Content{ Parsed: &event.RoomAvatarEventContent{ - URL: user.bridge.Config.AppService.Bot.ParsedAvatar, + URL: user.bridge.Config.AppService.Bot.ParsedAvatar.CUString(), }, }, }},