Implement avatars
This commit is contained in:
parent
75468e87d8
commit
a415444fc0
18 changed files with 529 additions and 460 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
||||||
|
|
10
database/upgrades/07-contact-id-fix.sql
Normal file
10
database/upgrades/07-contact-id-fix.sql
Normal 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;
|
|
@ -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.
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
@ -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;
|
||||||
|
|
|
@ -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.
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{},
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
13
portal.go
13
portal.go
|
@ -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()
|
||||||
|
|
55
puppet.go
55
puppet.go
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue