2022-08-26 12:58:41 +00:00
|
|
|
package protocol
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-08-09 17:07:28 +00:00
|
|
|
"crypto/ecdsa"
|
2022-08-26 12:58:41 +00:00
|
|
|
"encoding/hex"
|
2022-08-26 09:25:54 +00:00
|
|
|
"errors"
|
2022-08-26 12:58:41 +00:00
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2023-02-02 17:59:48 +00:00
|
|
|
"github.com/status-im/status-go/images"
|
2022-08-26 12:58:41 +00:00
|
|
|
"github.com/status-im/status-go/protocol/common"
|
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
2022-10-14 08:50:36 +00:00
|
|
|
"github.com/status-im/status-go/protocol/requests"
|
2022-08-26 12:58:41 +00:00
|
|
|
v1protocol "github.com/status-im/status-go/protocol/v1"
|
|
|
|
)
|
|
|
|
|
2022-08-26 09:25:54 +00:00
|
|
|
var ErrGroupChatAddedContacts = errors.New("group-chat: can't add members who are not mutual contacts")
|
|
|
|
|
|
|
|
func (m *Messenger) validateAddedGroupMembers(members []string) error {
|
|
|
|
for _, memberPubkey := range members {
|
|
|
|
contactID, err := contactIDFromPublicKeyString(memberPubkey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
contact, _ := m.allContacts.Load(contactID)
|
2023-01-20 14:28:30 +00:00
|
|
|
if contact == nil || !contact.mutual() {
|
2022-08-26 09:25:54 +00:00
|
|
|
return ErrGroupChatAddedContacts
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-26 12:58:41 +00:00
|
|
|
func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string, members []string) (*MessengerResponse, error) {
|
2022-10-14 08:50:36 +00:00
|
|
|
var convertedKeyMembers []string
|
|
|
|
for _, m := range members {
|
|
|
|
k, err := requests.ConvertCompressedToLegacyKey(m)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
convertedKeyMembers = append(convertedKeyMembers, k)
|
|
|
|
|
|
|
|
}
|
|
|
|
if err := m.validateAddedGroupMembers(convertedKeyMembers); err != nil {
|
2022-08-26 09:25:54 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-08-26 12:58:41 +00:00
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "CreateGroupChatWithMembers"))
|
2022-10-14 08:50:36 +00:00
|
|
|
logger.Info("Creating group chat", zap.String("name", name), zap.Any("members", convertedKeyMembers))
|
2022-08-26 12:58:41 +00:00
|
|
|
chat := CreateGroupChat(m.getTimesource())
|
|
|
|
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
|
|
|
group, err := v1protocol.NewGroupWithCreator(name, chat.Color, clock, m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
chat.LastClockValue = clock
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
chat.Joined = int64(m.getTimesource().GetCurrentTime())
|
|
|
|
|
|
|
|
clock, _ = chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
|
|
|
// Add members
|
2022-10-14 08:50:36 +00:00
|
|
|
if len(convertedKeyMembers) > 0 {
|
|
|
|
event := v1protocol.NewMembersAddedEvent(convertedKeyMembers, clock)
|
2022-08-26 12:58:41 +00:00
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.allChats.Store(chat.ID, &chat)
|
|
|
|
|
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
|
|
|
|
return m.addMessagesAndChat(&chat, buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations), &response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) CreateGroupChatFromInvitation(name string, chatID string, adminPK string) (*MessengerResponse, error) {
|
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "CreateGroupChatFromInvitation"))
|
|
|
|
logger.Info("Creating group chat from invitation", zap.String("name", name))
|
|
|
|
chat := CreateGroupChat(m.getTimesource())
|
|
|
|
chat.ID = chatID
|
|
|
|
chat.Name = name
|
|
|
|
chat.InvitationAdmin = adminPK
|
|
|
|
|
|
|
|
response.AddChat(&chat)
|
|
|
|
|
|
|
|
return &response, m.saveChat(&chat)
|
|
|
|
}
|
|
|
|
|
2023-08-09 17:07:28 +00:00
|
|
|
type removeMembersFromGroupChatResponse struct {
|
|
|
|
oldRecipients []*ecdsa.PublicKey
|
|
|
|
group *v1protocol.Group
|
|
|
|
encodedProtobuf []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) removeMembersFromGroupChat(ctx context.Context, chat *Chat, members []string) (*removeMembersFromGroupChatResponse, error) {
|
|
|
|
chatID := chat.ID
|
2022-12-10 04:26:51 +00:00
|
|
|
logger := m.logger.With(zap.String("site", "RemoveMembersFromGroupChat"))
|
|
|
|
logger.Info("Removing members form group chat", zap.String("chatID", chatID), zap.Any("members", members))
|
2022-08-26 12:58:41 +00:00
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We save the initial recipients as we want to send updates to also
|
|
|
|
// the members kicked out
|
|
|
|
oldRecipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
2022-12-10 04:26:51 +00:00
|
|
|
for _, member := range members {
|
|
|
|
// Remove member
|
|
|
|
event := v1protocol.NewMemberRemovedEvent(member, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-08-26 12:58:41 +00:00
|
|
|
}
|
|
|
|
|
2023-08-09 17:07:28 +00:00
|
|
|
encoded, err := m.sender.EncodeMembershipUpdate(group, nil)
|
2022-08-26 12:58:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-08-09 17:07:28 +00:00
|
|
|
|
|
|
|
return &removeMembersFromGroupChatResponse{
|
|
|
|
oldRecipients: oldRecipients,
|
|
|
|
group: group,
|
|
|
|
encodedProtobuf: encoded,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) RemoveMembersFromGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
removeMembersResponse, err := m.removeMembersFromGroupChat(ctx, chat, members)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-08-26 12:58:41 +00:00
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
2023-08-09 17:07:28 +00:00
|
|
|
Payload: removeMembersResponse.encodedProtobuf,
|
2022-08-26 12:58:41 +00:00
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
2023-08-09 17:07:28 +00:00
|
|
|
Recipients: removeMembersResponse.oldRecipients,
|
2022-08-26 12:58:41 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-08-09 17:07:28 +00:00
|
|
|
chat.updateChatFromGroupMembershipChanges(removeMembersResponse.group)
|
2022-08-26 12:58:41 +00:00
|
|
|
|
|
|
|
return m.addMessagesAndChat(chat, buildSystemMessages(chat.MembershipUpdates, m.systemMessagesTranslations), &response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
|
2022-08-26 09:25:54 +00:00
|
|
|
if err := m.validateAddedGroupMembers(members); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-08-26 12:58:41 +00:00
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "AddMembersFromGroupChat"))
|
|
|
|
logger.Info("Adding members form group chat", zap.String("chatID", chatID), zap.Any("members", members))
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
// Add members
|
|
|
|
event := v1protocol.NewMembersAddedEvent(members, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//approve invitations
|
|
|
|
for _, member := range members {
|
|
|
|
logger.Info("ApproveInvitationByChatIdAndFrom", zap.String("chatID", chatID), zap.Any("member", member))
|
|
|
|
|
|
|
|
groupChatInvitation := &GroupChatInvitation{
|
2023-08-18 11:39:59 +00:00
|
|
|
GroupChatInvitation: &protobuf.GroupChatInvitation{
|
2022-08-26 12:58:41 +00:00
|
|
|
ChatId: chat.ID,
|
|
|
|
},
|
|
|
|
From: member,
|
|
|
|
}
|
|
|
|
|
|
|
|
groupChatInvitation, err = m.persistence.InvitationByID(groupChatInvitation.ID())
|
|
|
|
if err != nil && err != common.ErrRecordNotFound {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if groupChatInvitation != nil {
|
|
|
|
groupChatInvitation.State = protobuf.GroupChatInvitation_APPROVED
|
|
|
|
|
|
|
|
err := m.persistence.SaveInvitation(groupChatInvitation)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
response.Invitations = append(response.Invitations, groupChatInvitation)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
|
|
|
|
return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) ChangeGroupChatName(ctx context.Context, chatID string, name string) (*MessengerResponse, error) {
|
|
|
|
logger := m.logger.With(zap.String("site", "ChangeGroupChatName"))
|
|
|
|
logger.Info("Changing group chat name", zap.String("chatID", chatID), zap.String("name", name))
|
|
|
|
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
// Change name
|
|
|
|
event := v1protocol.NewNameChangedEvent(name, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update in-memory group
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
|
|
|
|
}
|
|
|
|
|
2023-02-02 17:59:48 +00:00
|
|
|
func (m *Messenger) EditGroupChat(ctx context.Context, chatID string, name string, color string, image images.CroppedImage) (*MessengerResponse, error) {
|
2022-08-26 12:58:41 +00:00
|
|
|
logger := m.logger.With(zap.String("site", "EditGroupChat"))
|
|
|
|
logger.Info("Editing group chat details", zap.String("chatID", chatID), zap.String("name", name), zap.String("color", color))
|
|
|
|
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
signAndProcessEvent := func(m *Messenger, event *v1protocol.MembershipUpdateEvent) error {
|
|
|
|
err := event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(*event)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var events []v1protocol.MembershipUpdateEvent
|
|
|
|
|
|
|
|
if chat.Name != name {
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
event := v1protocol.NewNameChangedEvent(name, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = signAndProcessEvent(m, &event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
events = append(events, event)
|
|
|
|
}
|
|
|
|
|
|
|
|
if chat.Color != color {
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
event := v1protocol.NewColorChangedEvent(color, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = signAndProcessEvent(m, &event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
events = append(events, event)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(image.ImagePath) > 0 {
|
2023-02-02 17:59:48 +00:00
|
|
|
payload, err := images.OpenAndAdjustImage(image, true)
|
2022-08-26 12:58:41 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare event
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
event := v1protocol.NewImageChangedEvent(payload, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = signAndProcessEvent(m, &event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
events = append(events, event)
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
return m.addMessagesAndChat(chat, buildSystemMessages(events, m.systemMessagesTranslations), &response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) SendGroupChatInvitationRequest(ctx context.Context, chatID string, adminPK string,
|
|
|
|
message string) (*MessengerResponse, error) {
|
|
|
|
logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRequest"))
|
|
|
|
logger.Info("Sending group chat invitation request", zap.String("chatID", chatID),
|
|
|
|
zap.String("adminPK", adminPK), zap.String("message", message))
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
// Get chat and clock
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
|
|
|
invitationR := &GroupChatInvitation{
|
2023-08-18 11:39:59 +00:00
|
|
|
GroupChatInvitation: &protobuf.GroupChatInvitation{
|
2022-08-26 12:58:41 +00:00
|
|
|
Clock: clock,
|
|
|
|
ChatId: chatID,
|
|
|
|
IntroductionMessage: message,
|
|
|
|
State: protobuf.GroupChatInvitation_REQUEST,
|
|
|
|
},
|
|
|
|
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec := common.RawMessage{
|
2024-03-22 10:55:09 +00:00
|
|
|
LocalChatID: adminPK,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
|
|
|
|
ResendType: common.ResendTypeDataSync,
|
2022-08-26 12:58:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pkey, err := hex.DecodeString(adminPK[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Safety check, make sure is well formed
|
|
|
|
adminpk, err := crypto.UnmarshalPubkey(pkey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
id, err := m.sender.SendPrivate(ctx, adminpk, &spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec.ID = types.EncodeHex(id)
|
|
|
|
spec.SendCount++
|
|
|
|
err = m.persistence.SaveRawMessage(&spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Invitations = []*GroupChatInvitation{invitationR}
|
|
|
|
|
|
|
|
err = m.persistence.SaveInvitation(invitationR)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) GetGroupChatInvitations() ([]*GroupChatInvitation, error) {
|
|
|
|
return m.persistence.GetGroupChatInvitations()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) SendGroupChatInvitationRejection(ctx context.Context, invitationRequestID string) (*MessengerResponse, error) {
|
|
|
|
logger := m.logger.With(zap.String("site", "SendGroupChatInvitationRejection"))
|
|
|
|
logger.Info("Sending group chat invitation reject", zap.String("invitationRequestID", invitationRequestID))
|
|
|
|
|
|
|
|
invitationR, err := m.persistence.InvitationByID(invitationRequestID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
invitationR.State = protobuf.GroupChatInvitation_REJECTED
|
|
|
|
|
|
|
|
// Get chat and clock
|
|
|
|
chat, ok := m.allChats.Load(invitationR.ChatId)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
|
|
|
|
invitationR.Clock = clock
|
|
|
|
|
|
|
|
encodedMessage, err := proto.Marshal(invitationR.GetProtobuf())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec := common.RawMessage{
|
2024-03-22 10:55:09 +00:00
|
|
|
LocalChatID: invitationR.From,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_GROUP_CHAT_INVITATION,
|
|
|
|
ResendType: common.ResendTypeDataSync,
|
2022-08-26 12:58:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pkey, err := hex.DecodeString(invitationR.From[2:])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Safety check, make sure is well formed
|
|
|
|
userpk, err := crypto.UnmarshalPubkey(pkey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
id, err := m.sender.SendPrivate(ctx, userpk, &spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec.ID = types.EncodeHex(id)
|
|
|
|
spec.SendCount++
|
|
|
|
err = m.persistence.SaveRawMessage(&spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
response.Invitations = []*GroupChatInvitation{invitationR}
|
|
|
|
|
|
|
|
err = m.persistence.SaveInvitation(invitationR)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
|
|
|
|
var response MessengerResponse
|
|
|
|
logger := m.logger.With(zap.String("site", "AddAdminsToGroupChat"))
|
|
|
|
logger.Info("Add admins to group chat", zap.String("chatID", chatID), zap.Any("members", members))
|
|
|
|
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
// Add members
|
|
|
|
event := v1protocol.NewAdminsAddedEvent(members, clock)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kept only for backward compatibility (auto-join), explicit join has been removed
|
|
|
|
func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*MessengerResponse, error) {
|
|
|
|
var response MessengerResponse
|
|
|
|
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := m.Join(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
event := v1protocol.NewMemberJoinedEvent(
|
|
|
|
clock,
|
|
|
|
)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
chat.Joined = int64(m.getTimesource().GetCurrentTime())
|
|
|
|
|
|
|
|
return m.addMessagesAndChat(chat, buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations), &response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) leaveGroupChat(ctx context.Context, response *MessengerResponse, chatID string, remove bool, shouldBeSynced bool) (*MessengerResponse, error) {
|
|
|
|
chat, ok := m.allChats.Load(chatID)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrChatNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
amIMember := chat.HasMember(common.PubkeyToHex(&m.identity.PublicKey))
|
|
|
|
|
|
|
|
if amIMember {
|
|
|
|
chat.RemoveMember(common.PubkeyToHex(&m.identity.PublicKey))
|
|
|
|
|
|
|
|
group, err := newProtocolGroupFromChat(chat)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
|
|
|
|
event := v1protocol.NewMemberRemovedEvent(
|
|
|
|
contactIDFromPublicKey(&m.identity.PublicKey),
|
|
|
|
clock,
|
|
|
|
)
|
|
|
|
event.ChatID = chat.ID
|
|
|
|
err = event.Sign(m.identity)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = group.ProcessEvent(event)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
recipients, err := stringSliceToPublicKeys(group.Members())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
encodedMessage, err := m.sender.EncodeMembershipUpdate(group, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// shouldBeSynced is false if we got here because a synced client has already
|
|
|
|
// sent the leave group message. In that case we don't need to send it again.
|
|
|
|
if shouldBeSynced {
|
|
|
|
_, err = m.dispatchMessage(ctx, common.RawMessage{
|
|
|
|
LocalChatID: chat.ID,
|
|
|
|
Payload: encodedMessage,
|
|
|
|
MessageType: protobuf.ApplicationMetadataMessage_MEMBERSHIP_UPDATE_MESSAGE,
|
|
|
|
Recipients: recipients,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
chat.updateChatFromGroupMembershipChanges(group)
|
|
|
|
response.AddMessages(buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations))
|
|
|
|
err = m.persistence.SaveMessages(response.Messages())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if remove {
|
|
|
|
chat.Active = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if remove && shouldBeSynced {
|
2023-01-06 12:21:14 +00:00
|
|
|
err := m.syncChatRemoving(ctx, chat.ID, m.dispatchMessage)
|
2022-08-26 12:58:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
response.AddChat(chat)
|
|
|
|
|
|
|
|
return response, m.saveChat(chat)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string, remove bool) (*MessengerResponse, error) {
|
2023-10-22 09:41:20 +00:00
|
|
|
_, err := m.DismissAllActivityCenterNotificationsFromChatID(ctx, chatID, m.GetCurrentTimeInMillis())
|
2022-08-26 12:58:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var response MessengerResponse
|
|
|
|
return m.leaveGroupChat(ctx, &response, chatID, remove, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decline all pending group invites from a user
|
2023-10-22 09:41:20 +00:00
|
|
|
func (m *Messenger) DeclineAllPendingGroupInvitesFromUser(ctx context.Context, response *MessengerResponse, userPublicKey string) (*MessengerResponse, error) {
|
2022-08-26 12:58:41 +00:00
|
|
|
|
|
|
|
// Decline group invites from active chats
|
|
|
|
chats, err := m.persistence.Chats()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, chat := range chats {
|
|
|
|
if chat.ChatType == ChatTypePrivateGroupChat &&
|
|
|
|
chat.ReceivedInvitationAdmin == userPublicKey &&
|
|
|
|
chat.Joined == 0 && chat.Active {
|
|
|
|
response, err = m.leaveGroupChat(ctx, response, chat.ID, true, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decline group invites from activity center notifications
|
2023-10-22 09:41:20 +00:00
|
|
|
notifications, err := m.AcceptActivityCenterNotificationsForInvitesFromUser(ctx, userPublicKey, m.GetCurrentTimeInMillis())
|
2022-08-26 12:58:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, notification := range notifications {
|
|
|
|
response, err = m.leaveGroupChat(ctx, response, notification.ChatID, true, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|