feat: add conversation system action messages
This commit is contained in:
@@ -551,6 +551,18 @@ type imGroupMuteBatchRequest struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type imSystemMessageActor struct {
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Click map[string]any `json:"click,omitempty"`
|
||||
}
|
||||
|
||||
type imSystemMessageAction struct {
|
||||
Type string `json:"type"`
|
||||
Label string `json:"label,omitempty"`
|
||||
TargetID string `json:"target_id,omitempty"`
|
||||
}
|
||||
|
||||
func serveIMTenantResolve(wrt http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodPost {
|
||||
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
|
||||
@@ -4169,6 +4181,9 @@ func serveIMFriends(wrt http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
payload := make([]map[string]any, 0, len(items))
|
||||
for _, item := range items {
|
||||
if item.Friendship.PeerID == principal.Uid {
|
||||
continue
|
||||
}
|
||||
payload = append(payload, friendListItemPayload(item))
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{"items": payload, "next_cursor": nextCursor})
|
||||
@@ -4934,6 +4949,16 @@ func serveIMGroupInvitationResource(wrt http.ResponseWriter, req *http.Request)
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if imReliableHTTPEventsEnabled(req) {
|
||||
publishIMConversationSystemNotice(principal.TenantID, inv.Topic,
|
||||
"system_action", "group_member_joined", "有成员通过邀请加入了群聊。",
|
||||
[]imSystemMessageActor{
|
||||
{UserID: inv.InviterUserID.UserId(), Click: map[string]any{"type": "user_profile", "target_id": inv.InviterUserID.UserId()}},
|
||||
{UserID: inv.TargetUserID.UserId(), Click: map[string]any{"type": "user_profile", "target_id": inv.TargetUserID.UserId()}},
|
||||
},
|
||||
[]imSystemMessageAction{{Type: "group_profile", Label: "查看群聊", TargetID: inv.Topic}},
|
||||
map[string]any{"invitation_id": inv.InvitationID, "source": "invitation"})
|
||||
}
|
||||
dispatchIMGroupEvents(principal.TenantID)
|
||||
writeIMSuccess(wrt, req, groupInvitationPayload(inv))
|
||||
}
|
||||
@@ -5048,6 +5073,16 @@ func serveIMGroupJoinRequestAction(wrt http.ResponseWriter, req *http.Request, t
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if updated.Status == types.GroupJoinRequestStatusAccepted && imReliableHTTPEventsEnabled(req) {
|
||||
publishIMConversationSystemNotice(principal.TenantID, topic,
|
||||
"system_action", "group_member_joined", "有成员通过审批加入了群聊。",
|
||||
[]imSystemMessageActor{
|
||||
{UserID: updated.ApplicantUserID.UserId(), Click: map[string]any{"type": "user_profile", "target_id": updated.ApplicantUserID.UserId()}},
|
||||
{UserID: principal.Uid.UserId(), Click: map[string]any{"type": "user_profile", "target_id": principal.Uid.UserId()}},
|
||||
},
|
||||
[]imSystemMessageAction{{Type: "group_profile", Label: "查看群聊", TargetID: topic}},
|
||||
map[string]any{"request_id": updated.RequestID, "source": "approval"})
|
||||
}
|
||||
dispatchIMGroupEvents(principal.TenantID)
|
||||
writeIMSuccess(wrt, req, groupJoinRequestPayload(updated))
|
||||
}
|
||||
@@ -5176,6 +5211,21 @@ func serveIMGroupGovernance(wrt http.ResponseWriter, req *http.Request, topic st
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if body.JoinApproval != nil && imReliableHTTPEventsEnabled(req) {
|
||||
template := "group_invite_confirm_disabled"
|
||||
text := "群聊邀请确认已关闭,成员邀请好友可直接进群。"
|
||||
if *body.JoinApproval {
|
||||
template = "group_invite_confirm_enabled"
|
||||
text = "群聊邀请确认已启用,群成员邀请好友进群需要群主或管理员确认。"
|
||||
}
|
||||
publishIMConversationSystemNotice(principal.TenantID, topic,
|
||||
"system_action", template, text,
|
||||
[]imSystemMessageActor{
|
||||
{UserID: principal.Uid.UserId(), Click: map[string]any{"type": "user_profile", "target_id": principal.Uid.UserId()}},
|
||||
},
|
||||
[]imSystemMessageAction{{Type: "open_group_invite_settings", Label: "群聊邀请确认", TargetID: topic}},
|
||||
map[string]any{"join_approval": *body.JoinApproval, "version": settings.Version})
|
||||
}
|
||||
dispatchIMGroupEvents(principal.TenantID)
|
||||
writeIMSuccess(wrt, req, groupGovernancePayloadFromSettings(settings, selfSub))
|
||||
default:
|
||||
@@ -5794,6 +5844,14 @@ func serveIMFriendRequestAction(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
if status == types.FriendRequestAccepted && imReliableHTTPEventsEnabled(req) {
|
||||
publishIMConversationSystemNotice(principal.TenantID, updated.ApplicantID.P2PName(updated.TargetID),
|
||||
"system_notice", "friend_added", "你们已成为好友,现在可以开始聊天了。",
|
||||
[]imSystemMessageActor{
|
||||
{UserID: updated.ApplicantID.UserId(), Click: map[string]any{"type": "user_profile", "target_id": updated.ApplicantID.UserId()}},
|
||||
{UserID: updated.TargetID.UserId(), Click: map[string]any{"type": "user_profile", "target_id": updated.TargetID.UserId()}},
|
||||
}, nil, map[string]any{"request_id": updated.RequestID})
|
||||
}
|
||||
dispatchIMBusinessEvents(principal.TenantID)
|
||||
if imReliableHTTPEventsEnabled(req) {
|
||||
eventType := "friend.request." + status
|
||||
@@ -9248,6 +9306,64 @@ func requireIMTopicReadable(tenantID types.TenantID, uid types.Uid, topic string
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishIMConversationSystemNotice(tenantID types.TenantID, topic, msgType, template, text string, actors []imSystemMessageActor, actions []imSystemMessageAction, extra map[string]any) {
|
||||
if tenantID == types.ZeroTenantID || strings.TrimSpace(topic) == "" || store.Store == nil || !store.Store.IsOpen() || store.Topics == nil || store.Messages == nil {
|
||||
return
|
||||
}
|
||||
msgType = strings.TrimSpace(msgType)
|
||||
if msgType == "" {
|
||||
msgType = "system_notice"
|
||||
}
|
||||
if msgType != "system_notice" && msgType != "system_action" {
|
||||
logs.Warn.Printf("im system message: unsupported type %q for topic %s", msgType, topic)
|
||||
return
|
||||
}
|
||||
template = strings.TrimSpace(template)
|
||||
text = strings.TrimSpace(text)
|
||||
if template == "" || text == "" {
|
||||
return
|
||||
}
|
||||
topicRecord, err := store.Topics.Get(tenantID, topic)
|
||||
if err != nil || topicRecord == nil {
|
||||
if err != nil {
|
||||
logs.Warn.Printf("im system message: failed to load topic %s: %v", topic, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
content := map[string]any{
|
||||
"text": text,
|
||||
"template": template,
|
||||
"level": "normal",
|
||||
}
|
||||
if len(actors) > 0 {
|
||||
content["actors"] = actors
|
||||
}
|
||||
if len(actions) > 0 {
|
||||
content["actions"] = actions
|
||||
}
|
||||
if len(extra) > 0 {
|
||||
content["extra"] = extra
|
||||
}
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
_, err = store.Messages.Save(tenantID, &types.Message{
|
||||
ObjHeader: types.ObjHeader{CreatedAt: now, UpdatedAt: now},
|
||||
TenantID: tenantID,
|
||||
SeqId: topicRecord.SeqId + 1,
|
||||
Topic: topic,
|
||||
From: types.ZeroUid.String(),
|
||||
Head: types.KVMap{
|
||||
"x-im-schema": 1,
|
||||
"x-im-type": msgType,
|
||||
"x-im-template": template,
|
||||
"x-im-from": "system",
|
||||
},
|
||||
Content: content,
|
||||
}, nil, false)
|
||||
if err != nil && !errors.Is(err, types.ErrDuplicate) {
|
||||
logs.Warn.Printf("im system message: failed to save %s for topic %s: %v", template, topic, err)
|
||||
}
|
||||
}
|
||||
|
||||
func systemNotificationIDFromPath(path string) string {
|
||||
const marker = "/v1/im/system-notifications/"
|
||||
idx := strings.Index(path, marker)
|
||||
|
||||
Reference in New Issue
Block a user