package communities import ( "bytes" "encoding/json" "errors" "sort" "github.com/golang/protobuf/proto" "github.com/status-im/status-go/protocol/protobuf" ) type CommunityEvent struct { CommunityEventClock uint64 `json:"communityEventClock"` Type protobuf.CommunityEvent_EventType `json:"type"` CommunityConfig *protobuf.CommunityConfig `json:"communityConfig,omitempty"` TokenPermission *protobuf.CommunityTokenPermission `json:"tokenPermissions,omitempty"` CategoryData *protobuf.CategoryData `json:"categoryData,omitempty"` ChannelData *protobuf.ChannelData `json:"channelData,omitempty"` MemberToAction string `json:"memberToAction,omitempty"` MembersAdded map[string]*protobuf.CommunityMember `json:"membersAdded,omitempty"` RejectedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin `json:"rejectedRequestsToJoin,omitempty"` AcceptedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin `json:"acceptedRequestsToJoin,omitempty"` TokenMetadata *protobuf.CommunityTokenMetadata `json:"tokenMetadata,omitempty"` RawPayload []byte `json:"rawPayload"` } func (e *CommunityEvent) ToProtobuf() *protobuf.CommunityEvent { return &protobuf.CommunityEvent{ CommunityEventClock: e.CommunityEventClock, Type: e.Type, CommunityConfig: e.CommunityConfig, TokenPermission: e.TokenPermission, CategoryData: e.CategoryData, ChannelData: e.ChannelData, MemberToAction: e.MemberToAction, MembersAdded: e.MembersAdded, RejectedRequestsToJoin: e.RejectedRequestsToJoin, AcceptedRequestsToJoin: e.AcceptedRequestsToJoin, TokenMetadata: e.TokenMetadata, } } func CommunityEventFromProtobuf(raw []byte) (*CommunityEvent, error) { decodedEvent := protobuf.CommunityEvent{} encodedEvent := raw err := proto.Unmarshal(encodedEvent, &decodedEvent) if err != nil { return nil, err } return &CommunityEvent{ CommunityEventClock: decodedEvent.CommunityEventClock, Type: decodedEvent.Type, CommunityConfig: decodedEvent.CommunityConfig, TokenPermission: decodedEvent.TokenPermission, CategoryData: decodedEvent.CategoryData, ChannelData: decodedEvent.ChannelData, MemberToAction: decodedEvent.MemberToAction, MembersAdded: decodedEvent.MembersAdded, RejectedRequestsToJoin: decodedEvent.RejectedRequestsToJoin, AcceptedRequestsToJoin: decodedEvent.AcceptedRequestsToJoin, TokenMetadata: decodedEvent.TokenMetadata, RawPayload: encodedEvent, }, nil } type CommunityEventsMessage struct { CommunityID []byte `json:"communityId"` EventsBaseCommunityDescription []byte `json:"eventsBaseCommunityDescription"` Events []CommunityEvent `json:"events,omitempty"` } func (m *CommunityEventsMessage) ToProtobuf() protobuf.CommunityEventsMessage { rawEvents := communityEventsToBytes(m.Events) return protobuf.CommunityEventsMessage{ CommunityId: m.CommunityID, EventsBaseCommunityDescription: m.EventsBaseCommunityDescription, Events: rawEvents, } } func CommunityEventsMessageFromProtobuf(raw *protobuf.CommunityEventsMessage) (*CommunityEventsMessage, error) { events, err := communityEventsFromBytes(raw.Events) if err != nil { return nil, err } return &CommunityEventsMessage{ CommunityID: raw.CommunityId, EventsBaseCommunityDescription: raw.EventsBaseCommunityDescription, Events: events, }, nil } func (m *CommunityEventsMessage) Marshal() ([]byte, error) { pb := m.ToProtobuf() return proto.Marshal(&pb) } func (c *Community) mergeCommunityEvents(communityEventMessage *CommunityEventsMessage) { if c.config.EventsData == nil { c.config.EventsData = &EventsData{ EventsBaseCommunityDescription: communityEventMessage.EventsBaseCommunityDescription, Events: communityEventMessage.Events, } return } for _, update := range communityEventMessage.Events { var exists bool for _, existing := range c.config.EventsData.Events { if isCommunityEventsEqual(update, existing) { exists = true break } } if !exists { c.config.EventsData.Events = append(c.config.EventsData.Events, update) } } c.sortCommunityEvents() } func (c *Community) sortCommunityEvents() { sort.Slice(c.config.EventsData.Events, func(i, j int) bool { return c.config.EventsData.Events[i].CommunityEventClock < c.config.EventsData.Events[j].CommunityEventClock }) } func validateCommunityEvent(communityEvent *CommunityEvent) error { switch communityEvent.Type { case protobuf.CommunityEvent_COMMUNITY_EDIT: if communityEvent.CommunityConfig == nil || communityEvent.CommunityConfig.Identity == nil || communityEvent.CommunityConfig.Permissions == nil || communityEvent.CommunityConfig.AdminSettings == nil { return errors.New("invalid config change admin event") } case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE: if communityEvent.TokenPermission == nil || len(communityEvent.TokenPermission.Id) == 0 { return errors.New("invalid token permission change event") } case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE: if communityEvent.TokenPermission == nil || len(communityEvent.TokenPermission.Id) == 0 { return errors.New("invalid token permission delete event") } case protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE: if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 { return errors.New("invalid community category create event") } case protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE: if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 { return errors.New("invalid community category delete event") } case protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT: if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 { return errors.New("invalid community category edit event") } case protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE: if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 || communityEvent.ChannelData.Channel == nil { return errors.New("invalid community channel create event") } case protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE: if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 { return errors.New("invalid community channel delete event") } case protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT: if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 || communityEvent.ChannelData.Channel == nil { return errors.New("invalid community channel edit event") } case protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER: if communityEvent.ChannelData == nil || len(communityEvent.ChannelData.ChannelId) == 0 { return errors.New("invalid community channel reorder event") } case protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER: if communityEvent.CategoryData == nil || len(communityEvent.CategoryData.CategoryId) == 0 { return errors.New("invalid community category reorder event") } case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT: if communityEvent.AcceptedRequestsToJoin == nil { return errors.New("invalid community request to join accepted event") } case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT: if communityEvent.RejectedRequestsToJoin == nil { return errors.New("invalid community request to join reject event") } case protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK: if len(communityEvent.MemberToAction) == 0 { return errors.New("invalid community member kick event") } case protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN: if len(communityEvent.MemberToAction) == 0 { return errors.New("invalid community member ban event") } case protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN: if len(communityEvent.MemberToAction) == 0 { return errors.New("invalid community member unban event") } case protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD: if communityEvent.TokenMetadata == nil || len(communityEvent.TokenMetadata.ContractAddresses) == 0 { return errors.New("invalid add community token event") } } return nil } func isCommunityEventsEqual(left CommunityEvent, right CommunityEvent) bool { return bytes.Equal(left.RawPayload, right.RawPayload) } func communityEventsToBytes(communityEvents []CommunityEvent) [][]byte { var rawEvents [][]byte for _, e := range communityEvents { var encodedEvent []byte encodedEvent = append(encodedEvent, e.RawPayload...) rawEvents = append(rawEvents, encodedEvent) } return rawEvents } func communityEventsFromBytes(rawEvents [][]byte) ([]CommunityEvent, error) { var events []CommunityEvent for _, e := range rawEvents { verifiedEvent, err := CommunityEventFromProtobuf(e) if err != nil { return nil, err } events = append(events, *verifiedEvent) } return events, nil } func communityEventsToJSONEncodedBytes(communityEvents []CommunityEvent) ([]byte, error) { return json.Marshal(communityEvents) } func communityEventsFromJSONEncodedBytes(jsonEncodedRawEvents []byte) ([]CommunityEvent, error) { var events []CommunityEvent err := json.Unmarshal(jsonEncodedRawEvents, &events) if err != nil { return nil, err } return events, nil }