Skip to content

Commit aa27354

Browse files
fix(bitrix24): label quoted-media reply note from downloaded MIME [B24:2794] (#1444)
Follow-up to #1443. For a media-only quoted message, im.dialog.messages.get frequently returns a blank type/name (observed live for a voice note), so the reply note fell back to the generic "một tệp" even though the file was correctly downloaded and tagged <media:audio>. Build the note AFTER downloading and derive the kind label from the downloaded file's MIME (reusing classifyMediaType), so it reads "một tin nhắn thoại" / "một hình ảnh" / "một video" to match the <media:*> tag the agent receives. Falls back to the Bitrix file type, then "tệp", when no download is available (e.g. oversized). Also adopts the downloaded filename when messages.get omitted it. Adds tests for the MIME-preference path and mediaKindVN. Co-authored-by: DangTinh311 <dangtinh31193@gmail.com>
1 parent 2a23ad0 commit aa27354

2 files changed

Lines changed: 80 additions & 11 deletions

File tree

‎internal/channels/bitrix24/reply_context.go‎

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212

1313
"github.com/nextlevelbuilder/goclaw/internal/bus"
14+
"github.com/nextlevelbuilder/goclaw/internal/channels/media"
1415
)
1516

1617
// quotedTextMaxRunes caps how much of a quoted text message we inline into the
@@ -55,26 +56,59 @@ func (c *Channel) resolveReplyContext(ctx context.Context, evt *Event) (string,
5556
return fmt.Sprintf("[Đang trả lời một tin nhắn của %s]\n", author), nil
5657
}
5758

58-
note := quotedMediaNote(files, author)
5959
// Re-inject through the SAME pipeline as inbound files: oversized files are
6060
// skipped (note-only fallback) and the rest flow into the media tags /
6161
// read_* tools exactly like a directly-attached upload. DRY — no new size
62-
// or MIME logic here.
62+
// or MIME logic here. Download BEFORE building the note so the note's kind
63+
// label can use the authoritative downloaded MIME (im.dialog.messages.get
64+
// often returns a blank type/name for voice notes).
6365
extra := c.downloadEventFiles(ctx, c.BotID(), files)
66+
note := quotedMediaNote(files, extra, author)
6467
return note, extra
6568
}
6669

6770
// quotedMediaNote builds the "[Đang trả lời một <loại> ...]" prefix for a
6871
// media-only quoted message. Single file → name + kind; multiple → a count.
69-
func quotedMediaNote(files []EventFile, author string) string {
72+
// Prefers the downloaded file's MIME for the kind label + its filename, since
73+
// im.dialog.messages.get metadata (type/name) is frequently blank for voice
74+
// notes; falls back to the Bitrix file type from the message payload.
75+
func quotedMediaNote(files []EventFile, downloaded []bus.MediaFile, author string) string {
76+
if len(files) > 1 {
77+
return fmt.Sprintf("[Đang trả lời %d tệp đính kèm của %s]\n", len(files), author)
78+
}
79+
kind := "tệp"
80+
name := ""
7081
if len(files) == 1 {
71-
f := files[0]
72-
if f.Name != "" {
73-
return fmt.Sprintf("[Đang trả lời một %s \"%s\" của %s]\n", quotedKindVN(f.Type), f.Name, author)
82+
name = files[0].Name
83+
if files[0].Type != "" {
84+
kind = quotedKindVN(files[0].Type)
85+
}
86+
}
87+
if len(downloaded) >= 1 {
88+
kind = mediaKindVN(downloaded[0].MimeType) // authoritative
89+
if name == "" {
90+
name = downloaded[0].Filename
7491
}
75-
return fmt.Sprintf("[Đang trả lời một %s của %s]\n", quotedKindVN(f.Type), author)
7692
}
77-
return fmt.Sprintf("[Đang trả lời %d tệp đính kèm của %s]\n", len(files), author)
93+
if name != "" {
94+
return fmt.Sprintf("[Đang trả lời một %s \"%s\" của %s]\n", kind, name, author)
95+
}
96+
return fmt.Sprintf("[Đang trả lời một %s của %s]\n", kind, author)
97+
}
98+
99+
// mediaKindVN maps a MIME type to a Vietnamese noun for the reply note, reusing
100+
// classifyMediaType so the label matches the <media:*> tag the agent receives.
101+
func mediaKindVN(mime string) string {
102+
switch classifyMediaType(mime) {
103+
case media.TypeImage:
104+
return "hình ảnh"
105+
case media.TypeVideo:
106+
return "video"
107+
case media.TypeAudio:
108+
return "tin nhắn thoại"
109+
default:
110+
return "tệp"
111+
}
78112
}
79113

80114
// quotedAuthorName resolves a Bitrix user id to a display name for the reply

‎internal/channels/bitrix24/reply_context_test.go‎

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"strings"
99
"sync/atomic"
1010
"testing"
11+
12+
"github.com/nextlevelbuilder/goclaw/internal/bus"
1113
)
1214

1315
func TestSanitizeQuotedText(t *testing.T) {
@@ -72,20 +74,53 @@ func TestQuotedKindVN(t *testing.T) {
7274
}
7375

7476
func TestQuotedMediaNote(t *testing.T) {
75-
single := quotedMediaNote([]EventFile{{Name: "voice.ogg", Type: "audio"}}, "Tình")
77+
single := quotedMediaNote([]EventFile{{Name: "voice.ogg", Type: "audio"}}, nil, "Tình")
7678
if want := "[Đang trả lời một tin nhắn thoại \"voice.ogg\" của Tình]\n"; single != want {
7779
t.Errorf("single = %q; want %q", single, want)
7880
}
79-
noName := quotedMediaNote([]EventFile{{Type: "image"}}, "Tình")
81+
noName := quotedMediaNote([]EventFile{{Type: "image"}}, nil, "Tình")
8082
if want := "[Đang trả lời một hình ảnh của Tình]\n"; noName != want {
8183
t.Errorf("noName = %q; want %q", noName, want)
8284
}
83-
multi := quotedMediaNote([]EventFile{{Name: "a"}, {Name: "b"}}, "Tình")
85+
multi := quotedMediaNote([]EventFile{{Name: "a"}, {Name: "b"}}, nil, "Tình")
8486
if want := "[Đang trả lời 2 tệp đính kèm của Tình]\n"; multi != want {
8587
t.Errorf("multi = %q; want %q", multi, want)
8688
}
8789
}
8890

91+
// Regression: im.dialog.messages.get returns blank type/name for voice notes,
92+
// so the note must derive the kind (and filename) from the downloaded MIME
93+
// instead of falling back to the generic "tệp".
94+
func TestQuotedMediaNote_PrefersDownloadedMIME(t *testing.T) {
95+
files := []EventFile{{ID: "32728"}} // blank Type + Name (as messages.get returned)
96+
downloaded := []bus.MediaFile{{MimeType: "audio/x-m4a", Filename: "voice.m4a"}}
97+
got := quotedMediaNote(files, downloaded, "Đình Chinh")
98+
if want := "[Đang trả lời một tin nhắn thoại \"voice.m4a\" của Đình Chinh]\n"; got != want {
99+
t.Errorf("got %q; want %q", got, want)
100+
}
101+
102+
// Blank downloaded filename → kind still upgraded, no filename shown.
103+
got2 := quotedMediaNote([]EventFile{{ID: "1"}}, []bus.MediaFile{{MimeType: "image/png"}}, "Tình")
104+
if want := "[Đang trả lời một hình ảnh của Tình]\n"; got2 != want {
105+
t.Errorf("got2 %q; want %q", got2, want)
106+
}
107+
}
108+
109+
func TestMediaKindVN(t *testing.T) {
110+
cases := map[string]string{
111+
"audio/x-m4a": "tin nhắn thoại",
112+
"image/png": "hình ảnh",
113+
"video/mp4": "video",
114+
"application/pdf": "tệp",
115+
"": "tệp",
116+
}
117+
for mime, want := range cases {
118+
if got := mediaKindVN(mime); got != want {
119+
t.Errorf("mediaKindVN(%q) = %q; want %q", mime, got, want)
120+
}
121+
}
122+
}
123+
89124
func TestExtractFileIDs(t *testing.T) {
90125
cases := []struct {
91126
name string

0 commit comments

Comments
 (0)