Implement avatars

This commit is contained in:
Tulir Asokan 2023-09-05 01:18:01 +03:00
parent 75468e87d8
commit a415444fc0
18 changed files with 529 additions and 460 deletions

View file

@ -44,19 +44,19 @@ func (pq *PortalQuery) getDB() *Database {
}
func (pq *PortalQuery) GetAll(ctx context.Context) ([]*Portal, error) {
return getAll[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, avatar_id, avatar_mxc, avatar_set, encrypted, in_space FROM portal")
return getAll[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, encrypted, in_space FROM portal")
}
func (pq *PortalQuery) GetAllForUser(ctx context.Context, receiver int) ([]*Portal, error) {
return getAll[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, avatar_id, avatar_mxc, avatar_set, encrypted, in_space FROM portal WHERE receiver=$1", receiver)
return getAll[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, encrypted, in_space FROM portal WHERE receiver=$1", receiver)
}
func (pq *PortalQuery) GetByKey(ctx context.Context, key Key) (*Portal, error) {
return get[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, avatar_id, avatar_mxc, avatar_set, encrypted, in_space FROM portal WHERE id=$1 AND receiver=$2", key.ID, key.Receiver)
return get[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, encrypted, in_space FROM portal WHERE id=$1 AND receiver=$2", key.ID, key.Receiver)
}
func (pq *PortalQuery) GetByMXID(ctx context.Context, mxid id.RoomID) (*Portal, error) {
return get[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, avatar_id, avatar_mxc, avatar_set, encrypted, in_space FROM portal WHERE mxid=$1", mxid)
return get[*Portal](pq, ctx, "SELECT id, receiver, self_user, other_user, type, mxid, name, name_set, encrypted, in_space FROM portal WHERE mxid=$1", mxid)
}
type Key struct {
@ -83,9 +83,6 @@ type Portal struct {
Type gmproto.ConversationType
Name string
NameSet bool
AvatarID string
AvatarMXC id.ContentURI
AvatarSet bool
Encrypted bool
InSpace bool
}
@ -93,7 +90,7 @@ type Portal struct {
func (portal *Portal) Scan(row dbutil.Scannable) (*Portal, error) {
var mxid, selfUserID, otherUserID sql.NullString
var convType int
err := row.Scan(&portal.ID, &portal.Receiver, &selfUserID, &otherUserID, &convType, &mxid, &portal.Name, &portal.NameSet, &portal.AvatarID, &portal.AvatarMXC, &portal.AvatarSet, &portal.Encrypted, &portal.InSpace)
err := row.Scan(&portal.ID, &portal.Receiver, &selfUserID, &otherUserID, &convType, &mxid, &portal.Name, &portal.NameSet, &portal.Encrypted, &portal.InSpace)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
} else if err != nil {
@ -117,13 +114,16 @@ func (portal *Portal) sqlVariables() []any {
if portal.OtherUserID != "" {
otherUserID = &portal.OtherUserID
}
return []any{portal.ID, portal.Receiver, selfUserID, otherUserID, int(portal.Type), mxid, portal.Name, portal.NameSet, portal.AvatarID, &portal.AvatarMXC, portal.AvatarSet, portal.Encrypted, portal.InSpace}
return []any{
portal.ID, portal.Receiver, selfUserID, otherUserID, int(portal.Type), mxid, portal.Name, portal.NameSet,
portal.Encrypted, portal.InSpace,
}
}
func (portal *Portal) Insert(ctx context.Context) error {
_, err := portal.db.Conn(ctx).ExecContext(ctx, `
INSERT INTO portal (id, receiver, self_user, other_user, type, mxid, name, name_set, avatar_id, avatar_mxc, avatar_set, encrypted, in_space)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
INSERT INTO portal (id, receiver, self_user, other_user, type, mxid, name, name_set, encrypted, in_space)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`, portal.sqlVariables()...)
return err
}
@ -131,7 +131,7 @@ func (portal *Portal) Insert(ctx context.Context) error {
func (portal *Portal) Update(ctx context.Context) error {
_, err := portal.db.Conn(ctx).ExecContext(ctx, `
UPDATE portal
SET self_user=$3, other_user=$4, type=$5, mxid=$6, name=$7, name_set=$8, avatar_id=$9, avatar_mxc=$10, avatar_set=$11, encrypted=$12, in_space=$13
SET self_user=$3, other_user=$4, type=$5, mxid=$6, name=$7, name_set=$8, encrypted=$9, in_space=$10
WHERE id=$1 AND receiver=$2
`, portal.sqlVariables()...)
return err

View file

@ -20,6 +20,7 @@ import (
"context"
"database/sql"
"errors"
"time"
"go.mau.fi/util/dbutil"
"maunium.net/go/mautrix/id"
@ -40,7 +41,7 @@ func (pq *PuppetQuery) getDB() *Database {
}
func (pq *PuppetQuery) GetAll(ctx context.Context) ([]*Puppet, error) {
return getAll[*Puppet](pq, ctx, "SELECT id, receiver, phone, name, name_set, avatar_id, avatar_mxc, avatar_set, contact_info_set FROM puppet")
return getAll[*Puppet](pq, ctx, "SELECT id, receiver, phone, contact_id, name, name_set, avatar_hash, avatar_mxc, avatar_set, avatar_update_ts, contact_info_set FROM puppet")
}
func (pq *PuppetQuery) DeleteAllForUser(ctx context.Context, userID int) error {
@ -49,7 +50,7 @@ func (pq *PuppetQuery) DeleteAllForUser(ctx context.Context, userID int) error {
}
func (pq *PuppetQuery) Get(ctx context.Context, key Key) (*Puppet, error) {
return get[*Puppet](pq, ctx, "SELECT id, receiver, phone, name, name_set, avatar_id, avatar_mxc, avatar_set, contact_info_set FROM puppet WHERE id=$1 AND receiver=$2", key.ID, key.Receiver)
return get[*Puppet](pq, ctx, "SELECT id, receiver, phone, contact_id, name, name_set, avatar_hash, avatar_mxc, avatar_set, avatar_update_ts, contact_info_set FROM puppet WHERE id=$1 AND receiver=$2", key.ID, key.Receiver)
}
type Puppet struct {
@ -57,32 +58,40 @@ type Puppet struct {
Key
Phone string
ContactID string
Name string
NameSet bool
AvatarID string
AvatarHash [32]byte
AvatarMXC id.ContentURI
AvatarSet bool
AvatarUpdateTS time.Time
ContactInfoSet bool
}
func (puppet *Puppet) Scan(row dbutil.Scannable) (*Puppet, error) {
err := row.Scan(&puppet.ID, &puppet.Receiver, &puppet.Phone, &puppet.Name, &puppet.NameSet, &puppet.AvatarID, &puppet.AvatarMXC, &puppet.AvatarSet, &puppet.ContactInfoSet)
var avatarHash []byte
var avatarUpdateTS int64
err := row.Scan(&puppet.ID, &puppet.Receiver, &puppet.Phone, &puppet.ContactID, &puppet.Name, &puppet.NameSet, &avatarHash, &puppet.AvatarMXC, &puppet.AvatarSet, &avatarUpdateTS, &puppet.ContactInfoSet)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
} else if err != nil {
return nil, err
}
if len(avatarHash) == 32 {
puppet.AvatarHash = *(*[32]byte)(avatarHash)
}
puppet.AvatarUpdateTS = time.UnixMilli(avatarUpdateTS)
return puppet, nil
}
func (puppet *Puppet) sqlVariables() []any {
return []any{puppet.ID, puppet.Receiver, puppet.Phone, puppet.Name, puppet.NameSet, puppet.AvatarID, &puppet.AvatarMXC, puppet.AvatarSet, puppet.ContactInfoSet}
return []any{puppet.ID, puppet.Receiver, puppet.Phone, puppet.ContactID, puppet.Name, puppet.NameSet, puppet.AvatarHash[:], &puppet.AvatarMXC, puppet.AvatarSet, puppet.AvatarUpdateTS.UnixMilli(), puppet.ContactInfoSet}
}
func (puppet *Puppet) Insert(ctx context.Context) error {
_, err := puppet.db.Conn(ctx).ExecContext(ctx, `
INSERT INTO puppet (id, receiver, phone, name, name_set, avatar_id, avatar_mxc, avatar_set, contact_info_set)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
INSERT INTO puppet (id, receiver, phone, contact_id, name, name_set, avatar_hash, avatar_mxc, avatar_set, avatar_update_ts, contact_info_set)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
`, puppet.sqlVariables()...)
return err
}
@ -90,7 +99,7 @@ func (puppet *Puppet) Insert(ctx context.Context) error {
func (puppet *Puppet) Update(ctx context.Context) error {
_, err := puppet.db.Conn(ctx).ExecContext(ctx, `
UPDATE puppet
SET phone=$3, name=$4, name_set=$5, avatar_id=$6, avatar_mxc=$7, avatar_set=$8, contact_info_set=$9
SET phone=$3, contact_id=$4, name=$5, name_set=$6, avatar_hash=$7, avatar_mxc=$8, avatar_set=$9, avatar_update_ts=$10, contact_info_set=$11
WHERE id=$1 AND receiver=$2
`, puppet.sqlVariables()...)
return err

View file

@ -24,9 +24,11 @@ CREATE TABLE puppet (
id TEXT NOT NULL,
receiver BIGINT NOT NULL,
phone TEXT NOT NULL,
contact_id TEXT NOT NULL,
name TEXT NOT NULL,
name_set BOOLEAN NOT NULL DEFAULT false,
avatar_id TEXT NOT NULL,
avatar_hash bytea CHECK ( LENGTH(avatar_hash) = 32 ),
avatar_update_ts BIGINT NOT NULL,
avatar_mxc TEXT NOT NULL,
avatar_set BOOLEAN NOT NULL DEFAULT false,
contact_info_set BOOLEAN NOT NULL DEFAULT false,
@ -45,9 +47,6 @@ CREATE TABLE portal (
mxid TEXT UNIQUE,
name TEXT NOT NULL,
name_set BOOLEAN NOT NULL DEFAULT false,
avatar_id TEXT NOT NULL,
avatar_mxc TEXT NOT NULL,
avatar_set BOOLEAN NOT NULL DEFAULT false,
encrypted BOOLEAN NOT NULL DEFAULT false,
in_space BOOLEAN NOT NULL DEFAULT false,

View file

@ -0,0 +1,10 @@
-- v7: Fix contact ID field
ALTER TABLE puppet RENAME COLUMN avatar_id TO contact_id;
ALTER TABLE puppet ADD COLUMN avatar_hash bytea CHECK ( LENGTH(avatar_hash) = 32 );
ALTER TABLE puppet ADD COLUMN avatar_update_ts BIGINT NOT NULL DEFAULT 0;
ALTER TABLE portal DROP COLUMN avatar_id;
ALTER TABLE portal DROP COLUMN avatar_mxc;
ALTER TABLE portal DROP COLUMN avatar_set;
-- only: postgres
ALTER TABLE puppet ALTER COLUMN avatar_update_ts DROP DEFAULT;

View file

@ -31,7 +31,8 @@ var responseType = map[gmproto.ActionType]proto.Message{
gmproto.ActionType_SEND_MESSAGE: &gmproto.SendMessageResponse{},
gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionResponse{},
gmproto.ActionType_DELETE_MESSAGE: &gmproto.DeleteMessageResponse{},
gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: &gmproto.GetParticipantThumbnailResponse{},
gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: &gmproto.GetThumbnailResponse{},
gmproto.ActionType_GET_CONTACTS_THUMBNAIL: &gmproto.GetThumbnailResponse{},
gmproto.ActionType_LIST_CONTACTS: &gmproto.ListContactsResponse{},
gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsResponse{},
gmproto.ActionType_GET_OR_CREATE_CONVERSATION: &gmproto.GetOrCreateConversationResponse{},

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -67,27 +67,29 @@ message UploadedMedia {
int64 mediaNumber = 2;
}
message GetParticipantThumbnailRequest {
string conversationID = 1;
message GetThumbnailRequest {
repeated string identifiers = 1;
}
message GetParticipantThumbnailResponse {
repeated ParticipantThumbnail thumbnail = 1;
}
message GetThumbnailResponse {
message Thumbnail {
// ID depends on request, it's always the same as the input.
string identifier = 1;
ThumbnailData data = 2;
}
message ParticipantThumbnail {
string participantID = 1;
ThumbnailData data = 2;
}
message GetContactsThumbnailRequest {
repeated string avatarIDs = 1;
repeated Thumbnail thumbnail = 1;
}
message ThumbnailData {
message MysteriousData {
fixed64 maybeAHash = 13;
}
// 2 -> 13: 16 mysterious bytes
bytes imageBuffer = 3;
int32 someInt = 4;
conversations.Dimensions dimensions = 5;
MysteriousData mysteriousData = 2;
}
message Cursor {

View file

@ -714,12 +714,12 @@ type Contact struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
ParticipantID string `protobuf:"bytes,1,opt,name=participantID,proto3" json:"participantID,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Number *ContactNumber `protobuf:"bytes,3,opt,name=number,proto3" json:"number,omitempty"`
AvatarHexColor string `protobuf:"bytes,7,opt,name=avatarHexColor,proto3" json:"avatarHexColor,omitempty"`
UnknownBool bool `protobuf:"varint,10,opt,name=unknownBool,proto3" json:"unknownBool,omitempty"`
AvatarID string `protobuf:"bytes,11,opt,name=avatarID,proto3" json:"avatarID,omitempty"`
ContactID string `protobuf:"bytes,11,opt,name=contactID,proto3" json:"contactID,omitempty"`
}
func (x *Contact) Reset() {
@ -754,9 +754,9 @@ func (*Contact) Descriptor() ([]byte, []int) {
return file_conversations_proto_rawDescGZIP(), []int{0}
}
func (x *Contact) GetID() string {
func (x *Contact) GetParticipantID() string {
if x != nil {
return x.ID
return x.ParticipantID
}
return ""
}
@ -789,9 +789,9 @@ func (x *Contact) GetUnknownBool() bool {
return false
}
func (x *Contact) GetAvatarID() string {
func (x *Contact) GetContactID() string {
if x != nil {
return x.AvatarID
return x.ContactID
}
return ""
}
@ -1947,7 +1947,7 @@ type Participant struct {
IsMe bool `protobuf:"varint,6,opt,name=isMe,proto3" json:"isMe,omitempty"`
Muted *Muted `protobuf:"bytes,7,opt,name=muted,proto3" json:"muted,omitempty"`
SomeInt int64 `protobuf:"varint,8,opt,name=someInt,proto3" json:"someInt,omitempty"`
AvatarID string `protobuf:"bytes,10,opt,name=avatarID,proto3" json:"avatarID,omitempty"`
ContactID string `protobuf:"bytes,10,opt,name=contactID,proto3" json:"contactID,omitempty"`
Bs int64 `protobuf:"varint,14,opt,name=bs,proto3" json:"bs,omitempty"`
FormattedNumber string `protobuf:"bytes,15,opt,name=formattedNumber,proto3" json:"formattedNumber,omitempty"`
SomeInt1 int64 `protobuf:"varint,19,opt,name=someInt1,proto3" json:"someInt1,omitempty"`
@ -2035,9 +2035,9 @@ func (x *Participant) GetSomeInt() int64 {
return 0
}
func (x *Participant) GetAvatarID() string {
func (x *Participant) GetContactID() string {
if x != nil {
return x.AvatarID
return x.ContactID
}
return ""
}

Binary file not shown.

View file

@ -4,12 +4,12 @@ package conversations;
option go_package = "../gmproto";
message Contact {
string ID = 1;
string participantID = 1;
string name = 2;
ContactNumber number = 3;
string avatarHexColor = 7;
bool unknownBool = 10;
string avatarID = 11;
string contactID = 11;
}
message ContactNumber {
@ -153,7 +153,7 @@ message Participant {
bool isMe = 6;
Muted muted = 7;
int64 someInt = 8;
string avatarID = 10;
string contactID = 10;
int64 bs = 14;
string formattedNumber = 15;
int64 someInt1 = 19;

View file

@ -791,6 +791,7 @@ type OutgoingRPCResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SomeIdentifier *OutgoingRPCResponse_SomeIdentifier `protobuf:"bytes,1,opt,name=someIdentifier,proto3" json:"someIdentifier,omitempty"`
// This is not present for AckMessage responses, only for SendMessage
Timestamp *string `protobuf:"bytes,2,opt,name=timestamp,proto3,oneof" json:"timestamp,omitempty"`
}
@ -827,6 +828,13 @@ func (*OutgoingRPCResponse) Descriptor() ([]byte, []int) {
return file_rpc_proto_rawDescGZIP(), []int{6}
}
func (x *OutgoingRPCResponse) GetSomeIdentifier() *OutgoingRPCResponse_SomeIdentifier {
if x != nil {
return x.SomeIdentifier
}
return nil
}
func (x *OutgoingRPCResponse) GetTimestamp() string {
if x != nil && x.Timestamp != nil {
return *x.Timestamp
@ -1125,16 +1133,17 @@ var file_rpc_proto_depIdxs = []int32{
10, // 11: rpc.OutgoingRPCMessage.auth:type_name -> rpc.OutgoingRPCMessage.Auth
14, // 12: rpc.OutgoingRPCMessage.emptyArr:type_name -> util.EmptyArr
1, // 13: rpc.OutgoingRPCData.action:type_name -> rpc.ActionType
16, // 14: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion
0, // 15: rpc.OutgoingRPCMessage.Data.bugleRoute:type_name -> rpc.BugleRoute
12, // 16: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type
14, // 17: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr
2, // 18: rpc.OutgoingRPCMessage.Data.Type.messageType:type_name -> rpc.MessageType
19, // [19:19] is the sub-list for method output_type
19, // [19:19] is the sub-list for method input_type
19, // [19:19] is the sub-list for extension type_name
19, // [19:19] is the sub-list for extension extendee
0, // [0:19] is the sub-list for field type_name
13, // 14: rpc.OutgoingRPCResponse.someIdentifier:type_name -> rpc.OutgoingRPCResponse.SomeIdentifier
16, // 15: rpc.OutgoingRPCMessage.Auth.configVersion:type_name -> authentication.ConfigVersion
0, // 16: rpc.OutgoingRPCMessage.Data.bugleRoute:type_name -> rpc.BugleRoute
12, // 17: rpc.OutgoingRPCMessage.Data.messageTypeData:type_name -> rpc.OutgoingRPCMessage.Data.Type
14, // 18: rpc.OutgoingRPCMessage.Data.Type.emptyArr:type_name -> util.EmptyArr
2, // 19: rpc.OutgoingRPCMessage.Data.Type.messageType:type_name -> rpc.MessageType
20, // [20:20] is the sub-list for method output_type
20, // [20:20] is the sub-list for method input_type
20, // [20:20] is the sub-list for extension type_name
20, // [20:20] is the sub-list for extension extendee
0, // [0:20] is the sub-list for field type_name
}
func init() { file_rpc_proto_init() }

Binary file not shown.

View file

@ -91,6 +91,7 @@ message OutgoingRPCResponse {
string someNumber = 2;
}
SomeIdentifier someIdentifier = 1;
// This is not present for AckMessage responses, only for SendMessage
optional string timestamp = 2;
}

View file

@ -40,7 +40,8 @@ var requestType = map[gmproto.ActionType]proto.Message{
gmproto.ActionType_SEND_MESSAGE: &gmproto.SendMessageRequest{},
gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionRequest{},
gmproto.ActionType_DELETE_MESSAGE: &gmproto.DeleteMessageRequest{},
gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: &gmproto.GetParticipantThumbnailRequest{},
gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: &gmproto.GetThumbnailRequest{},
gmproto.ActionType_GET_CONTACTS_THUMBNAIL: &gmproto.GetThumbnailRequest{},
gmproto.ActionType_LIST_CONTACTS: &gmproto.ListContactsRequest{},
gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsRequest{},
gmproto.ActionType_GET_OR_CREATE_CONVERSATION: &gmproto.GetOrCreateConversationRequest{},

View file

@ -70,10 +70,16 @@ func (c *Client) SendMessage(payload *gmproto.SendMessageRequest) (*gmproto.Send
return typedResponse[*gmproto.SendMessageResponse](c.sessionHandler.sendMessage(actionType, payload))
}
func (c *Client) GetParticipantThumbnail(convID string) (*gmproto.GetParticipantThumbnailResponse, error) {
payload := &gmproto.GetParticipantThumbnailRequest{ConversationID: convID}
func (c *Client) GetParticipantThumbnail(participantIDs ...string) (*gmproto.GetThumbnailResponse, error) {
payload := &gmproto.GetThumbnailRequest{Identifiers: participantIDs}
actionType := gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL
return typedResponse[*gmproto.GetParticipantThumbnailResponse](c.sessionHandler.sendMessage(actionType, payload))
return typedResponse[*gmproto.GetThumbnailResponse](c.sessionHandler.sendMessage(actionType, payload))
}
func (c *Client) GetContactThumbnail(contactIDs ...string) (*gmproto.GetThumbnailResponse, error) {
payload := &gmproto.GetThumbnailRequest{Identifiers: contactIDs}
actionType := gmproto.ActionType_GET_CONTACTS_THUMBNAIL
return typedResponse[*gmproto.GetThumbnailResponse](c.sessionHandler.sendMessage(actionType, payload))
}
func (c *Client) UpdateConversation(payload *gmproto.UpdateConversationRequest) (*gmproto.UpdateConversationResponse, error) {

View file

@ -1423,7 +1423,6 @@ func (portal *Portal) getBridgeInfo() (string, event.BridgeEventContent) {
Channel: event.BridgeInfoSection{
ID: portal.ID,
DisplayName: portal.Name,
AvatarURL: portal.AvatarMXC.CUString(),
},
}
if portal.Type == gmproto.ConversationType_SMS {
@ -1531,17 +1530,6 @@ func (portal *Portal) CreateMatrixRoom(user *User, conv *gmproto.Conversation, i
invite = append(invite, portal.bridge.Bot.UserID)
}
}
if !portal.AvatarMXC.IsEmpty() && portal.shouldSetDMRoomMetadata() {
initialState = append(initialState, &event.Event{
Type: event.StateRoomAvatar,
Content: event.Content{
Parsed: event.RoomAvatarEventContent{URL: portal.AvatarMXC},
},
})
portal.AvatarSet = true
} else {
portal.AvatarSet = false
}
creationContent := make(map[string]interface{})
if !portal.bridge.Config.Bridge.FederateRooms {
@ -2103,7 +2091,6 @@ func (portal *Portal) RemoveMXID(ctx context.Context) {
delete(portal.bridge.portalsByMXID, portal.MXID)
portal.MXID = ""
portal.NameSet = false
portal.AvatarSet = false
portal.InSpace = false
portal.Encrypted = false
portal.bridge.portalsLock.Unlock()

View file

@ -18,9 +18,12 @@ package main
import (
"context"
"crypto/sha256"
"fmt"
"net/http"
"regexp"
"strconv"
"time"
"github.com/rs/zerolog"
"maunium.net/go/mautrix"
@ -198,20 +201,47 @@ func (puppet *Puppet) DefaultIntent() *appservice.IntentAPI {
return puppet.bridge.AS.Intent(puppet.MXID)
}
func (puppet *Puppet) UpdateAvatar(source *User, avatarID string) bool {
if puppet.AvatarID == avatarID && puppet.AvatarSet {
const MinAvatarUpdateInterval = 24 * time.Hour
func (puppet *Puppet) UpdateAvatar(source *User) bool {
if (puppet.AvatarSet && time.Since(puppet.AvatarUpdateTS) < MinAvatarUpdateInterval) || puppet.ContactID == "" {
return false
}
puppet.AvatarID = avatarID
puppet.AvatarMXC = id.ContentURI{}
puppet.AvatarSet = false
// TODO bridge avatar
if puppet.AvatarMXC.IsEmpty() {
return false
}
err := puppet.DefaultIntent().SetAvatarURL(puppet.AvatarMXC)
resp, err := source.Client.GetParticipantThumbnail(puppet.ID)
if err != nil {
puppet.log.Warn().Err(err).Msg("Failed to set avatar")
puppet.log.Err(err).Msg("Failed to get avatar thumbnail")
return false
}
puppet.AvatarUpdateTS = time.Now()
if len(resp.Thumbnail) == 0 {
if puppet.AvatarHash == [32]byte{} {
return true
}
puppet.AvatarHash = [32]byte{}
puppet.AvatarMXC = id.ContentURI{}
puppet.AvatarSet = false
} else {
thumbData := resp.Thumbnail[0].GetData()
hash := sha256.Sum256(thumbData.GetImageBuffer())
if hash == puppet.AvatarHash {
return true
}
puppet.AvatarHash = hash
puppet.AvatarSet = false
avatarBytes := thumbData.GetImageBuffer()
uploadResp, err := puppet.DefaultIntent().UploadMedia(mautrix.ReqUploadMedia{
ContentBytes: avatarBytes,
ContentType: http.DetectContentType(avatarBytes),
})
if err != nil {
puppet.log.Err(err).Msg("Failed to upload avatar")
return true
}
puppet.AvatarMXC = uploadResp.ContentURI
}
err = puppet.DefaultIntent().SetAvatarURL(puppet.AvatarMXC)
if err != nil {
puppet.log.Err(err).Msg("Failed to set avatar")
} else {
puppet.AvatarSet = true
}
@ -250,6 +280,7 @@ func (puppet *Puppet) UpdateContactInfo() bool {
contactInfo := map[string]any{
"com.beeper.bridge.identifiers": []string{
fmt.Sprintf("tel:%s", puppet.Phone),
fmt.Sprintf("gmsg-contact:%s", puppet.ContactID),
},
"com.beeper.bridge.remote_id": puppet.Key.String(),
"com.beeper.bridge.service": "gmessages",
@ -280,14 +311,16 @@ func (puppet *Puppet) Sync(source *User, contact *gmproto.Participant) {
}
update := false
if contact != nil {
if contact.ID.Number != "" && puppet.Phone != contact.ID.Number {
puppet.Phone = contact.ID.Number
update = true
}
update = puppet.UpdateName(contact.GetFormattedNumber(), contact.GetFullName(), contact.GetFirstName()) || update
update = puppet.UpdateAvatar(source, contact.GetAvatarID()) || update
if contact.ID.Number != "" && puppet.Phone != contact.ID.Number {
puppet.Phone = contact.ID.Number
update = true
}
if contact.ContactID != puppet.ContactID {
puppet.ContactID = contact.ContactID
update = true
}
update = puppet.UpdateName(contact.GetFormattedNumber(), contact.GetFullName(), contact.GetFirstName()) || update
update = puppet.UpdateAvatar(source) || update
update = puppet.UpdateContactInfo() || update
if update {
err = puppet.Update(context.TODO())