Skip to content
This repository has been archived by the owner on Nov 13, 2024. It is now read-only.

Commit

Permalink
Merge branch 'af/use-latest-profile-info'
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFerr committed Feb 21, 2024
2 parents a70ef27 + 4b56dcf commit 7f72542
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 84 deletions.
4 changes: 2 additions & 2 deletions config/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,12 @@ type DisplaynameParams struct {
func (bc BridgeConfig) FormatDisplayname(contact *types.Contact) string {
var buffer strings.Builder
_ = bc.displaynameTemplate.Execute(&buffer, DisplaynameParams{
ProfileName: contact.ProfileName,
ProfileName: contact.Profile.Name,
ContactName: contact.ContactName,
//Username: contact.Username,
PhoneNumber: contact.E164,
UUID: contact.UUID.String(),
AboutEmoji: contact.ProfileAboutEmoji,
AboutEmoji: contact.Profile.AboutEmoji,
})
return buffer.String()
}
Expand Down
59 changes: 26 additions & 33 deletions pkg/signalmeow/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (cli *Client) StoreContactDetailsAsContact(ctx context.Context, contactDeta
existingContact.ContactName = contactDetails.GetName()
if profileKeyString := contactDetails.GetProfileKey(); profileKeyString != nil {
profileKey := libsignalgo.ProfileKey(profileKeyString)
existingContact.ProfileKey = &profileKey
existingContact.Profile.Key = &profileKey
err = cli.Store.ProfileKeyStore.StoreProfileKey(ctx, existingContact.UUID, profileKey)
if err != nil {
log.Err(err).Msg("storing profile key")
Expand Down Expand Up @@ -95,17 +95,17 @@ func (cli *Client) StoreContactDetailsAsContact(ctx context.Context, contactDeta
return existingContact, nil
}

func (cli *Client) fetchContactThenTryAndUpdateWithProfile(ctx context.Context, profileUUID uuid.UUID) (*types.Contact, error) {
func (cli *Client) fetchContactThenTryAndUpdateWithProfile(ctx context.Context, profileUUID uuid.UUID) (existingContact *types.Contact, otherSourceUUID uuid.UUID, err error) {
log := zerolog.Ctx(ctx).With().
Str("action", "fetch contact then try and update with profile").
Stringer("profile_uuid", profileUUID).
Logger()
contactChanged := false

existingContact, err := cli.Store.ContactStore.LoadContact(ctx, profileUUID)
existingContact, err = cli.Store.ContactStore.LoadContact(ctx, profileUUID)
if err != nil {
log.Err(err).Msg("error loading contact")
return nil, err
return
}
if existingContact == nil {
log.Debug().Msg("creating new contact")
Expand All @@ -116,56 +116,49 @@ func (cli *Client) fetchContactThenTryAndUpdateWithProfile(ctx context.Context,
} else {
log.Debug().Msg("updating existing contact")
}
profile, lastFetched, err := cli.RetrieveProfileByID(ctx, profileUUID)
if err != nil {
log.Err(err).Msg("error retrieving profile")
//return nil, nil, err
profile, lastFetched, fetchErr := cli.RetrieveProfileByID(ctx, profileUUID)
if fetchErr != nil {
log.Err(fetchErr).Msg("error retrieving profile")
// Don't return here, we still want to return what we have
} else if profile != nil {
if existingContact.ProfileName != profile.Name {
existingContact.ProfileName = profile.Name
if existingContact.Profile.Name != profile.Name {
existingContact.Profile.Name = profile.Name
contactChanged = true
}
if existingContact.ProfileAbout != profile.About {
existingContact.ProfileAbout = profile.About
if existingContact.Profile.About != profile.About {
existingContact.Profile.About = profile.About
contactChanged = true
}
if existingContact.ProfileAboutEmoji != profile.AboutEmoji {
existingContact.ProfileAboutEmoji = profile.AboutEmoji
if existingContact.Profile.AboutEmoji != profile.AboutEmoji {
existingContact.Profile.AboutEmoji = profile.AboutEmoji
contactChanged = true
}
if existingContact.ProfileAvatarPath != profile.AvatarPath {
existingContact.ProfileAvatarPath = profile.AvatarPath
if existingContact.Profile.AvatarPath != profile.AvatarPath {
existingContact.Profile.AvatarPath = profile.AvatarPath
contactChanged = true
}
if existingContact.ProfileKey == nil || *existingContact.ProfileKey != profile.Key {
existingContact.ProfileKey = &profile.Key
if existingContact.Profile.Key == nil || *existingContact.Profile.Key != profile.Key {
existingContact.Profile.Key = &profile.Key
contactChanged = true
}
}

if contactChanged {
existingContact.ProfileFetchTs = lastFetched.UnixMilli()
err := cli.Store.ContactStore.StoreContact(ctx, *existingContact)
err = cli.Store.ContactStore.StoreContact(ctx, *existingContact)
if err != nil {
log.Err(err).Msg("error storing contact")
return nil, err
return
}
}

if err != nil {
var otherContact *types.Contact
otherContact, err = cli.Store.ContactStore.LoadContactWithLatestOtherProfile(ctx, existingContact)
if err != nil {
log.Err(err).Msg("error retrieving contact with a newer profile from other users")
} else if otherContact != nil {
existingContact.ProfileName = otherContact.ProfileName
existingContact.ProfileAbout = otherContact.ProfileAbout
existingContact.ProfileAboutEmoji = otherContact.ProfileAboutEmoji
existingContact.ProfileAvatarPath = otherContact.ProfileAvatarPath
if fetchErr != nil {
otherSourceUUID, fetchErr = cli.Store.ContactStore.UpdateContactWithLatestProfile(ctx, existingContact)
if fetchErr != nil {
log.Err(fetchErr).Msg("error retrieving latest profile for contact from other users")
}
}
return existingContact, nil
return
}

func (cli *Client) UpdateContactE164(ctx context.Context, uuid uuid.UUID, e164 string) error {
Expand Down Expand Up @@ -195,7 +188,7 @@ func (cli *Client) UpdateContactE164(ctx context.Context, uuid uuid.UUID, e164 s
return cli.Store.ContactStore.StoreContact(ctx, *existingContact)
}

func (cli *Client) ContactByID(ctx context.Context, uuid uuid.UUID) (*types.Contact, error) {
func (cli *Client) ContactByID(ctx context.Context, uuid uuid.UUID) (contact *types.Contact, otherSourceUUID uuid.UUID, err error) {
return cli.fetchContactThenTryAndUpdateWithProfile(ctx, uuid)
}

Expand All @@ -208,7 +201,7 @@ func (cli *Client) ContactByE164(ctx context.Context, e164 string) (*types.Conta
if contact == nil {
return nil, nil
}
contact, err = cli.fetchContactThenTryAndUpdateWithProfile(ctx, contact.UUID)
contact, _, err = cli.fetchContactThenTryAndUpdateWithProfile(ctx, contact.UUID)
return contact, err
}

Expand Down
11 changes: 6 additions & 5 deletions pkg/signalmeow/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/rs/zerolog"

"github.com/element-hq/mautrix-signal/pkg/libsignalgo"
"github.com/element-hq/mautrix-signal/pkg/signalmeow/types"
"github.com/element-hq/mautrix-signal/pkg/signalmeow/web"
)

Expand Down Expand Up @@ -71,11 +72,8 @@ type ProfileResponse struct {
}

type Profile struct {
Name string
About string
AboutEmoji string
AvatarPath string
Key libsignalgo.ProfileKey
types.ProfileFields
Key libsignalgo.ProfileKey
}

type ProfileCache struct {
Expand Down Expand Up @@ -249,6 +247,9 @@ func (cli *Client) fetchProfileByID(ctx context.Context, signalID uuid.UUID) (*P
}

func (cli *Client) DownloadUserAvatar(ctx context.Context, avatarPath string, profileKey *libsignalgo.ProfileKey) ([]byte, error) {
if profileKey == nil {
return nil, fmt.Errorf("failed to prepare request: profileKey is nil")
}
username, password := cli.Store.BasicAuthCreds()
opts := &web.HTTPReqOpt{
Host: web.CDN1Hostname,
Expand Down
66 changes: 46 additions & 20 deletions pkg/signalmeow/store/contact_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
type ContactStore interface {
LoadContact(ctx context.Context, theirUUID uuid.UUID) (*types.Contact, error)
LoadContactByE164(ctx context.Context, e164 string) (*types.Contact, error)
LoadContactWithLatestOtherProfile(ctx context.Context, contact *types.Contact) (*types.Contact, error)
UpdateContactWithLatestProfile(ctx context.Context, contact *types.Contact) (sourceUUID uuid.UUID, err error)
StoreContact(ctx context.Context, contact types.Contact) error
AllContacts(ctx context.Context) ([]*types.Contact, error)
UpdatePhone(ctx context.Context, theirUUID uuid.UUID, newE164 string) error
Expand All @@ -58,13 +58,7 @@ const (
getAllContactsOfUserQuery = getAllContactsQuery + `WHERE our_aci_uuid = $1`
getContactByUUIDQuery = getAllContactsQuery + `WHERE our_aci_uuid = $1 AND aci_uuid = $2`
getContactByPhoneQuery = getAllContactsQuery + `WHERE our_aci_uuid = $1 AND e164_number = $2`

getContactWithLatestOtherProfileQuery = getAllContactsQuery + `
WHERE our_aci_uuid <> $1 AND aci_uuid = $2 AND LENGTH(COALESCE(profile_key, '')) > 0
ORDER BY profile_fetch_ts DESC LIMIT 1
`

upsertContactQuery = `
upsertContactQuery = `
INSERT INTO signalmeow_contacts (
our_aci_uuid,
aci_uuid,
Expand Down Expand Up @@ -122,10 +116,10 @@ func scanContact(row dbutil.Scannable) (*types.Contact, error) {
&contact.ContactName,
&contact.ContactAvatar.Hash,
&profileKey,
&contact.ProfileName,
&contact.ProfileAbout,
&contact.ProfileAboutEmoji,
&contact.ProfileAvatarPath,
&contact.Profile.Name,
&contact.Profile.About,
&contact.Profile.AboutEmoji,
&contact.Profile.AvatarPath,
&contact.ProfileAvatarHash,
&contact.ProfileFetchTs,
)
Expand All @@ -136,7 +130,7 @@ func scanContact(row dbutil.Scannable) (*types.Contact, error) {
}
if len(profileKey) != 0 {
profileKeyConverted := libsignalgo.ProfileKey(profileKey)
contact.ProfileKey = &profileKeyConverted
contact.Profile.Key = &profileKeyConverted
}
return &contact, err
}
Expand All @@ -149,8 +143,40 @@ func (s *SQLStore) LoadContactByE164(ctx context.Context, e164 string) (*types.C
return scanContact(s.db.QueryRow(ctx, getContactByPhoneQuery, s.ACI, e164))
}

func (s *SQLStore) LoadContactWithLatestOtherProfile(ctx context.Context, contact *types.Contact) (*types.Contact, error) {
return scanContact(s.db.QueryRow(ctx, getContactWithLatestOtherProfileQuery, s.ACI, contact.UUID))
func (s *SQLStore) UpdateContactWithLatestProfile(ctx context.Context, contact *types.Contact) (sourceUUID uuid.UUID, err error) {
var profileKey []byte
err = s.db.QueryRow(
ctx,
`SELECT
profile_key,
profile_name,
profile_about,
profile_about_emoji,
profile_avatar_path,
our_aci_uuid
FROM signalmeow_contacts
WHERE
our_aci_uuid <> $1 AND
aci_uuid = $2 AND
LENGTH(COALESCE(profile_key, '')) > 0
ORDER BY profile_fetch_ts DESC LIMIT 1`,
s.ACI,
contact.UUID,
).Scan(
&profileKey,
&contact.Profile.Name,
&contact.Profile.About,
&contact.Profile.AboutEmoji,
&contact.Profile.AvatarPath,
&sourceUUID,
)
if errors.Is(err, sql.ErrNoRows) {
err = nil
} else if err == nil {
profileKeyConverted := libsignalgo.ProfileKey(profileKey)
contact.Profile.Key = &profileKeyConverted
}
return
}

func (s *SQLStore) AllContacts(ctx context.Context) ([]*types.Contact, error) {
Expand All @@ -170,11 +196,11 @@ func (s *SQLStore) StoreContact(ctx context.Context, contact types.Contact) erro
contact.E164,
contact.ContactName,
contact.ContactAvatar.Hash,
contact.ProfileKey.Slice(),
contact.ProfileName,
contact.ProfileAbout,
contact.ProfileAboutEmoji,
contact.ProfileAvatarPath,
contact.Profile.Key.Slice(),
contact.Profile.Name,
contact.Profile.About,
contact.Profile.AboutEmoji,
contact.Profile.AvatarPath,
contact.ProfileAvatarHash,
contact.ProfileFetchTs,
)
Expand Down
11 changes: 6 additions & 5 deletions pkg/signalmeow/types/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ type Contact struct {
E164 string
ContactName string
ContactAvatar ContactAvatar
ProfileKey *libsignalgo.ProfileKey
ProfileName string
ProfileAbout string
ProfileAboutEmoji string
ProfileAvatarPath string
Profile ContactProfile
ProfileAvatarHash string
ProfileFetchTs int64
}

type ContactProfile struct {
ProfileFields
Key *libsignalgo.ProfileKey
}

type ContactAvatar struct {
Image []byte
ContentType string
Expand Down
24 changes: 24 additions & 0 deletions pkg/signalmeow/types/profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// mautrix-signal - A Matrix-signal puppeting bridge.
// Copyright (C) 2023 Scott Weber, Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package types

type ProfileFields struct {
Name string
About string
AboutEmoji string
AvatarPath string
}
Loading

0 comments on commit 7f72542

Please sign in to comment.