From 8ff91601137e2131c95a79330f67aed82523d954 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Mon, 7 Sep 2026 19:57:36 +0800 Subject: [PATCH] Customize forward bundle titles --- server/im_http.go | 78 +++++++++++++++++++++++++++++++++++++++--- server/im_http_test.go | 35 +++++++++++++++++++ 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/server/im_http.go b/server/im_http.go index 004275a..85f8adf 100644 --- a/server/im_http.go +++ b/server/im_http.go @@ -9439,7 +9439,7 @@ func buildForwardBundle(principal *imPrincipal, sources []struct { if len(failures) > 0 { return nil, failures } - userSummaries := forwardMessageUserSummaries(principal.TenantID, sourceMessages) + userSummaries := forwardMessageUserSummaries(principal.TenantID, sourceMessages, forwardBundleTitleUIDs(sourceTopics)...) items := make([]map[string]any, 0, len(sourceMessages)) preview := make([]map[string]any, 0, min(len(sourceMessages), 3)) sourceRefs := make([]map[string]any, 0, len(sourceMessages)) @@ -9467,7 +9467,7 @@ func buildForwardBundle(principal *imPrincipal, sources []struct { bundleID := "fbundle_" + store.Store.GetUidString() return map[string]any{ "bundle_id": bundleID, - "title": "聊天记录", + "title": forwardBundleTitle(principal.TenantID, sourceTopics, userSummaries), "item_count": len(items), "preview": preview, "items": items, @@ -9477,12 +9477,80 @@ func buildForwardBundle(principal *imPrincipal, sources []struct { }, nil } -func forwardMessageUserSummaries(tenantID types.TenantID, messages []*types.Message) map[types.Uid]map[string]any { - if store.Users == nil || len(messages) == 0 { +func forwardBundleTitleUIDs(sourceTopics []string) []types.Uid { + seen := make(map[types.Uid]bool) + uids := make([]types.Uid, 0, 2) + for _, topic := range sourceTopics { + if types.GetTopicCat(topic) != types.TopicCatP2P { + continue + } + uid1, uid2, err := types.ParseP2P(topic) + if err != nil { + continue + } + for _, uid := range []types.Uid{uid1, uid2} { + if uid.IsZero() || seen[uid] { + continue + } + seen[uid] = true + uids = append(uids, uid) + } + } + return uids +} + +func forwardBundleTitle(tenantID types.TenantID, sourceTopics []string, userSummaries map[types.Uid]map[string]any) string { + if len(sourceTopics) == 0 { + return "聊天记录" + } + topic := sourceTopics[0] + switch types.GetTopicCat(topic) { + case types.TopicCatP2P: + uid1, uid2, err := types.ParseP2P(topic) + if err != nil { + return "聊天记录" + } + return forwardBundleDisplayName(uid1, userSummaries) + "和" + forwardBundleDisplayName(uid2, userSummaries) + "的聊天记录" + case types.TopicCatGrp: + groupName := topic + if store.Topics != nil { + if group, err := store.Topics.Get(tenantID, topic); err == nil && group != nil { + groupName = imDisplayNameFromPublic(group.Public, topic) + } else if err != nil { + logs.Warn.Printf("im message forward: failed to load group title topic=%s: %v", topic, err) + } + } + if strings.HasSuffix(groupName, "群") { + return groupName + "的聊天记录" + } + return groupName + "群的聊天记录" + default: + return "聊天记录" + } +} + +func forwardBundleDisplayName(uid types.Uid, summaries map[types.Uid]map[string]any) string { + if summary := forwardMessageUserSummary(uid.String(), summaries); summary != nil { + if name := strings.TrimSpace(fmt.Sprint(summary["display_name"])); name != "" { + return name + } + } + return uid.UserId() +} + +func forwardMessageUserSummaries(tenantID types.TenantID, messages []*types.Message, extraUIDs ...types.Uid) map[types.Uid]map[string]any { + if store.Users == nil { return nil } seen := make(map[types.Uid]bool) - uids := make([]types.Uid, 0, len(messages)) + uids := make([]types.Uid, 0, len(messages)+len(extraUIDs)) + for _, uid := range extraUIDs { + if uid.IsZero() || seen[uid] { + continue + } + seen[uid] = true + uids = append(uids, uid) + } for _, msg := range messages { if msg == nil { continue diff --git a/server/im_http_test.go b/server/im_http_test.go index 1470171..14c6955 100644 --- a/server/im_http_test.go +++ b/server/im_http_test.go @@ -5439,16 +5439,20 @@ func TestServeIMMessageForwardBundleSavesForwardBundle(t *testing.T) { oldSubs := store.Subs oldMessages := store.Messages oldUsers := store.Users + oldTopics := store.Topics subs := mock_store.NewMockSubsPersistenceInterface(ctrl) messages := mock_store.NewMockMessagesPersistenceInterface(ctrl) users := mock_store.NewMockUsersPersistenceInterface(ctrl) + topics := mock_store.NewMockTopicsPersistenceInterface(ctrl) store.Subs = subs store.Messages = messages store.Users = users + store.Topics = topics t.Cleanup(func() { store.Subs = oldSubs store.Messages = oldMessages store.Users = oldUsers + store.Topics = oldTopics }) subs.EXPECT().Get(types.TenantID(7), "grpTarget", types.Uid(42), false).Return(&types.Subscription{ @@ -5481,6 +5485,7 @@ func TestServeIMMessageForwardBundleSavesForwardBundle(t *testing.T) { TenantID: 7, Public: types.KVMap{"display_name": "后端-potter", "avatar_file_id": "avatar-99"}, }}, nil) + topics.EXPECT().Get(types.TenantID(7), "grpSource").Return(&types.Topic{Public: map[string]any{"fn": "研发群"}}, nil) messages.EXPECT().Save(types.TenantID(7), gomock.Any(), gomock.Nil(), true).DoAndReturn( func(_ types.TenantID, msg *types.Message, _ []string, _ bool) (*types.MessageSaveResult, error) { if msg.Topic != "grpTarget" || msg.Head["x-im-type"] != "forward_bundle" { @@ -5490,6 +5495,9 @@ func TestServeIMMessageForwardBundleSavesForwardBundle(t *testing.T) { if content["bundle_id"] != "fbundle_bundle-1" || content["item_count"] != 1 { t.Fatalf("unexpected bundle content: %#v", content) } + if content["title"] != "研发群的聊天记录" { + t.Fatalf("unexpected bundle title: %#v", content) + } bundleItems := content["items"].([]map[string]any) itemUser := bundleItems[0]["from_user"].(map[string]any) if itemUser["user_id"] != types.Uid(99).UserId() || itemUser["display_name"] != "后端-potter" || itemUser["avatar_file_id"] != "avatar-99" { @@ -5521,6 +5529,33 @@ func TestServeIMMessageForwardBundleSavesForwardBundle(t *testing.T) { } } +func TestForwardBundleTitleUsesP2PUsers(t *testing.T) { + uid1 := types.Uid(42) + uid2 := types.Uid(99) + title := forwardBundleTitle(types.TenantID(7), []string{uid1.P2PName(uid2)}, map[types.Uid]map[string]any{ + uid1: {"display_name": "Alice"}, + uid2: {"display_name": "Bob"}, + }) + if title != "Alice和Bob的聊天记录" { + t.Fatalf("unexpected p2p title: %s", title) + } +} + +func TestForwardBundleTitleUsesGroupName(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + oldTopics := store.Topics + topics := mock_store.NewMockTopicsPersistenceInterface(ctrl) + store.Topics = topics + t.Cleanup(func() { store.Topics = oldTopics }) + + topics.EXPECT().Get(types.TenantID(7), "grpSource").Return(&types.Topic{Public: map[string]any{"fn": "项目群"}}, nil) + title := forwardBundleTitle(types.TenantID(7), []string{"grpSource"}, nil) + if title != "项目群的聊天记录" { + t.Fatalf("unexpected group title: %s", title) + } +} + func TestServeIMSecurityURLCheck(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish()