feat: add conversation system action messages

This commit is contained in:
richjohnrambo
2026-09-01 13:12:24 +08:00
parent 39e7e019c7
commit edced2675b
8 changed files with 213 additions and 41 deletions
+116
View File
@@ -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)