feat: improve group member events and push delivery
This commit is contained in:
@@ -2,10 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tinode/chat/server/logs"
|
||||
"github.com/tinode/chat/server/store"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
)
|
||||
|
||||
@@ -15,6 +17,7 @@ func publishIMGroupRealtimeEvent(tenantID types.TenantID, topic string, actor ty
|
||||
if tenantID.IsZero() || topic == "" || eventType == "" {
|
||||
return
|
||||
}
|
||||
payload = enrichIMGroupMemberEventPayload(eventType, payload)
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
logs.Warn.Printf("group realtime: encode event topic=%s event=%s: %v", topic, eventType, err)
|
||||
@@ -24,6 +27,10 @@ func publishIMGroupRealtimeEvent(tenantID types.TenantID, topic string, actor ty
|
||||
if !actor.IsZero() {
|
||||
from = actor.UserId()
|
||||
}
|
||||
if isAdminOnlyIMGroupEvent(eventType) {
|
||||
publishIMGroupEventToAdmins(tenantID, topic, actor, eventType, encoded, payload)
|
||||
return
|
||||
}
|
||||
message := &ServerComMessage{
|
||||
TenantID: tenantID,
|
||||
RcptTo: topic,
|
||||
@@ -47,6 +54,129 @@ func publishIMGroupRealtimeEvent(tenantID types.TenantID, topic string, actor ty
|
||||
publishRealtimeTopicMessage(topic, message)
|
||||
}
|
||||
|
||||
func isAdminOnlyIMGroupEvent(eventType string) bool {
|
||||
switch eventType {
|
||||
case "group.member.updated", "group.member.left", "group.role.updated", "group.mute.updated",
|
||||
"group.invitations.updated", "group.join_request.created", "group.join_request.accepted",
|
||||
"group.join_request.rejected":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func publishIMGroupEventToAdmins(tenantID types.TenantID, topic string, actor types.Uid, eventType string,
|
||||
encoded json.RawMessage, payload any) {
|
||||
recipients := map[types.Uid]struct{}{}
|
||||
if !actor.IsZero() {
|
||||
recipients[actor] = struct{}{}
|
||||
}
|
||||
collectIMGroupEventUserIDs(payload, recipients)
|
||||
for _, role := range []string{types.GroupMemberRoleOwner, types.GroupMemberRoleAdmin} {
|
||||
if store.GroupBusiness == nil {
|
||||
break
|
||||
}
|
||||
cursor := ""
|
||||
for {
|
||||
members, next, err := store.GroupBusiness.ListMembers(tenantID, topic, types.GroupMemberListOptions{
|
||||
Role: role, Cursor: cursor, Limit: 100,
|
||||
})
|
||||
if err != nil {
|
||||
logs.Warn.Printf("group realtime: list %s recipients topic=%s event=%s: %v", role, topic, eventType, err)
|
||||
break
|
||||
}
|
||||
for _, member := range members {
|
||||
if !member.UserID.IsZero() {
|
||||
recipients[member.UserID] = struct{}{}
|
||||
}
|
||||
}
|
||||
if next == "" || next == cursor {
|
||||
break
|
||||
}
|
||||
cursor = next
|
||||
}
|
||||
}
|
||||
for uid := range recipients {
|
||||
message := &ServerComMessage{
|
||||
TenantID: tenantID, RcptTo: uid.UserId(), AsUser: actor.UserId(),
|
||||
Timestamp: time.Now().UTC().Round(time.Millisecond),
|
||||
Info: &MsgServerInfo{Topic: topic, From: actor.UserId(), What: "group", Event: eventType, Payload: encoded},
|
||||
}
|
||||
if globals.sessionStore != nil {
|
||||
globals.sessionStore.DeliverToUser(tenantID, uid, "", message)
|
||||
}
|
||||
publishRealtimeUserMessage(uid, message)
|
||||
}
|
||||
}
|
||||
|
||||
func collectIMGroupEventUserIDs(value any, out map[types.Uid]struct{}) {
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
if ok, exists := typed["ok"].(bool); exists && !ok {
|
||||
return
|
||||
}
|
||||
for key, item := range typed {
|
||||
switch key {
|
||||
case "user_id", "target_id", "target_user_id", "applicant_id", "applicant_user_id", "owner_id":
|
||||
if uid := imGroupEventUID(item); !uid.IsZero() {
|
||||
out[uid] = struct{}{}
|
||||
}
|
||||
}
|
||||
collectIMGroupEventUserIDs(item, out)
|
||||
}
|
||||
case []map[string]any:
|
||||
for _, item := range typed {
|
||||
collectIMGroupEventUserIDs(item, out)
|
||||
}
|
||||
case []any:
|
||||
for _, item := range typed {
|
||||
collectIMGroupEventUserIDs(item, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func imGroupEventUID(value any) types.Uid {
|
||||
if uid := types.ParseUserId(strings.TrimSpace(fmt.Sprint(value))); !uid.IsZero() {
|
||||
return uid
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case int64:
|
||||
return store.EncodeUid(typed)
|
||||
case int:
|
||||
return store.EncodeUid(int64(typed))
|
||||
case float64:
|
||||
return store.EncodeUid(int64(typed))
|
||||
}
|
||||
return types.ZeroUid
|
||||
}
|
||||
|
||||
func enrichIMGroupMemberEventPayload(eventType string, payload any) any {
|
||||
data, ok := payload.(map[string]any)
|
||||
if !ok {
|
||||
return payload
|
||||
}
|
||||
copyPayload := make(map[string]any, len(data)+2)
|
||||
for key, value := range data {
|
||||
copyPayload[key] = value
|
||||
}
|
||||
switch eventType {
|
||||
case "group.member.left":
|
||||
copyPayload["member_sync"] = "remove"
|
||||
case "group.member.joined", "group.member.updated", "group.role.updated", "group.mute.updated":
|
||||
if _, ok := copyPayload["user_id"]; ok {
|
||||
copyPayload["member_sync"] = "single"
|
||||
} else {
|
||||
copyPayload["member_sync"] = "full"
|
||||
}
|
||||
}
|
||||
if isAdminOnlyIMGroupEvent(eventType) {
|
||||
copyPayload["visibility"] = "admins_and_affected"
|
||||
} else if eventType == "group.member.joined" {
|
||||
copyPayload["visibility"] = "all_members"
|
||||
}
|
||||
return copyPayload
|
||||
}
|
||||
|
||||
func userIDs(uids []types.Uid) []string {
|
||||
ids := make([]string, 0, len(uids))
|
||||
for _, uid := range uids {
|
||||
|
||||
Reference in New Issue
Block a user