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) { 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) { 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) { 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) { 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 { type Key struct {
@ -83,9 +83,6 @@ type Portal struct {
Type gmproto.ConversationType Type gmproto.ConversationType
Name string Name string
NameSet bool NameSet bool
AvatarID string
AvatarMXC id.ContentURI
AvatarSet bool
Encrypted bool Encrypted bool
InSpace bool InSpace bool
} }
@ -93,7 +90,7 @@ type Portal struct {
func (portal *Portal) Scan(row dbutil.Scannable) (*Portal, error) { func (portal *Portal) Scan(row dbutil.Scannable) (*Portal, error) {
var mxid, selfUserID, otherUserID sql.NullString var mxid, selfUserID, otherUserID sql.NullString
var convType int 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) { if errors.Is(err, sql.ErrNoRows) {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
@ -117,13 +114,16 @@ func (portal *Portal) sqlVariables() []any {
if portal.OtherUserID != "" { if portal.OtherUserID != "" {
otherUserID = &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 { func (portal *Portal) Insert(ctx context.Context) error {
_, err := portal.db.Conn(ctx).ExecContext(ctx, ` _, 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) 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, $11, $12, $13) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`, portal.sqlVariables()...) `, portal.sqlVariables()...)
return err return err
} }
@ -131,7 +131,7 @@ func (portal *Portal) Insert(ctx context.Context) error {
func (portal *Portal) Update(ctx context.Context) error { func (portal *Portal) Update(ctx context.Context) error {
_, err := portal.db.Conn(ctx).ExecContext(ctx, ` _, err := portal.db.Conn(ctx).ExecContext(ctx, `
UPDATE portal 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 WHERE id=$1 AND receiver=$2
`, portal.sqlVariables()...) `, portal.sqlVariables()...)
return err return err

View file

@ -20,6 +20,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"time"
"go.mau.fi/util/dbutil" "go.mau.fi/util/dbutil"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
@ -40,7 +41,7 @@ func (pq *PuppetQuery) getDB() *Database {
} }
func (pq *PuppetQuery) GetAll(ctx context.Context) ([]*Puppet, error) { 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 { 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) { 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 { type Puppet struct {
@ -57,32 +58,40 @@ type Puppet struct {
Key Key
Phone string Phone string
ContactID string
Name string Name string
NameSet bool NameSet bool
AvatarID string AvatarHash [32]byte
AvatarMXC id.ContentURI AvatarMXC id.ContentURI
AvatarSet bool AvatarSet bool
AvatarUpdateTS time.Time
ContactInfoSet bool ContactInfoSet bool
} }
func (puppet *Puppet) Scan(row dbutil.Scannable) (*Puppet, error) { 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) { if errors.Is(err, sql.ErrNoRows) {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
if len(avatarHash) == 32 {
puppet.AvatarHash = *(*[32]byte)(avatarHash)
}
puppet.AvatarUpdateTS = time.UnixMilli(avatarUpdateTS)
return puppet, nil return puppet, nil
} }
func (puppet *Puppet) sqlVariables() []any { 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 { func (puppet *Puppet) Insert(ctx context.Context) error {
_, err := puppet.db.Conn(ctx).ExecContext(ctx, ` _, 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) 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) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
`, puppet.sqlVariables()...) `, puppet.sqlVariables()...)
return err return err
} }
@ -90,7 +99,7 @@ func (puppet *Puppet) Insert(ctx context.Context) error {
func (puppet *Puppet) Update(ctx context.Context) error { func (puppet *Puppet) Update(ctx context.Context) error {
_, err := puppet.db.Conn(ctx).ExecContext(ctx, ` _, err := puppet.db.Conn(ctx).ExecContext(ctx, `
UPDATE puppet 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 WHERE id=$1 AND receiver=$2
`, puppet.sqlVariables()...) `, puppet.sqlVariables()...)
return err return err

View file

@ -24,9 +24,11 @@ CREATE TABLE puppet (
id TEXT NOT NULL, id TEXT NOT NULL,
receiver BIGINT NOT NULL, receiver BIGINT NOT NULL,
phone TEXT NOT NULL, phone TEXT NOT NULL,
contact_id TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
name_set BOOLEAN NOT NULL DEFAULT false, 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_mxc TEXT NOT NULL,
avatar_set BOOLEAN NOT NULL DEFAULT false, avatar_set BOOLEAN NOT NULL DEFAULT false,
contact_info_set BOOLEAN NOT NULL DEFAULT false, contact_info_set BOOLEAN NOT NULL DEFAULT false,
@ -45,9 +47,6 @@ CREATE TABLE portal (
mxid TEXT UNIQUE, mxid TEXT UNIQUE,
name TEXT NOT NULL, name TEXT NOT NULL,
name_set BOOLEAN NOT NULL DEFAULT false, 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, encrypted BOOLEAN NOT NULL DEFAULT false,
in_space 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_MESSAGE: &gmproto.SendMessageResponse{},
gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionResponse{}, gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionResponse{},
gmproto.ActionType_DELETE_MESSAGE: &gmproto.DeleteMessageResponse{}, 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_CONTACTS: &gmproto.ListContactsResponse{},
gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsResponse{}, gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsResponse{},
gmproto.ActionType_GET_OR_CREATE_CONVERSATION: &gmproto.GetOrCreateConversationResponse{}, 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; int64 mediaNumber = 2;
} }
message GetParticipantThumbnailRequest { message GetThumbnailRequest {
string conversationID = 1; repeated string identifiers = 1;
} }
message GetParticipantThumbnailResponse { message GetThumbnailResponse {
repeated ParticipantThumbnail thumbnail = 1; message Thumbnail {
} // ID depends on request, it's always the same as the input.
string identifier = 1;
message ParticipantThumbnail {
string participantID = 1;
ThumbnailData data = 2; ThumbnailData data = 2;
} }
message GetContactsThumbnailRequest { repeated Thumbnail thumbnail = 1;
repeated string avatarIDs = 1;
} }
message ThumbnailData { message ThumbnailData {
message MysteriousData {
fixed64 maybeAHash = 13;
}
// 2 -> 13: 16 mysterious bytes
bytes imageBuffer = 3; bytes imageBuffer = 3;
int32 someInt = 4; int32 someInt = 4;
conversations.Dimensions dimensions = 5; conversations.Dimensions dimensions = 5;
MysteriousData mysteriousData = 2;
} }
message Cursor { message Cursor {

View file

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

Binary file not shown.

View file

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

View file

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

Binary file not shown.

View file

@ -91,6 +91,7 @@ message OutgoingRPCResponse {
string someNumber = 2; string someNumber = 2;
} }
SomeIdentifier someIdentifier = 1;
// This is not present for AckMessage responses, only for SendMessage // This is not present for AckMessage responses, only for SendMessage
optional string timestamp = 2; 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_MESSAGE: &gmproto.SendMessageRequest{},
gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionRequest{}, gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionRequest{},
gmproto.ActionType_DELETE_MESSAGE: &gmproto.DeleteMessageRequest{}, 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_CONTACTS: &gmproto.ListContactsRequest{},
gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsRequest{}, gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsRequest{},
gmproto.ActionType_GET_OR_CREATE_CONVERSATION: &gmproto.GetOrCreateConversationRequest{}, 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)) return typedResponse[*gmproto.SendMessageResponse](c.sessionHandler.sendMessage(actionType, payload))
} }
func (c *Client) GetParticipantThumbnail(convID string) (*gmproto.GetParticipantThumbnailResponse, error) { func (c *Client) GetParticipantThumbnail(participantIDs ...string) (*gmproto.GetThumbnailResponse, error) {
payload := &gmproto.GetParticipantThumbnailRequest{ConversationID: convID} payload := &gmproto.GetThumbnailRequest{Identifiers: participantIDs}
actionType := gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL 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) { 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{ Channel: event.BridgeInfoSection{
ID: portal.ID, ID: portal.ID,
DisplayName: portal.Name, DisplayName: portal.Name,
AvatarURL: portal.AvatarMXC.CUString(),
}, },
} }
if portal.Type == gmproto.ConversationType_SMS { 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) 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{}) creationContent := make(map[string]interface{})
if !portal.bridge.Config.Bridge.FederateRooms { if !portal.bridge.Config.Bridge.FederateRooms {
@ -2103,7 +2091,6 @@ func (portal *Portal) RemoveMXID(ctx context.Context) {
delete(portal.bridge.portalsByMXID, portal.MXID) delete(portal.bridge.portalsByMXID, portal.MXID)
portal.MXID = "" portal.MXID = ""
portal.NameSet = false portal.NameSet = false
portal.AvatarSet = false
portal.InSpace = false portal.InSpace = false
portal.Encrypted = false portal.Encrypted = false
portal.bridge.portalsLock.Unlock() portal.bridge.portalsLock.Unlock()

View file

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