fix(im): parse Drafty entities for message type classification (#31)

Favorites from Drafty messages (link/file/image/video/voice) always fell back to text because imMessageType only recognized flat file_id/url content. Derive the type from Drafty ent[] entities: LN->link, IM->image, VD->video, AU->voice, EX refined by mime. Attachments win over inline links.
This commit is contained in:
Austin
2026-09-20 16:38:51 +08:00
parent 48b3369799
commit 532ff49737
2 changed files with 167 additions and 0 deletions
+107
View File
@@ -8002,3 +8002,110 @@ func ptrTime(t time.Time) *time.Time {
func ptrString(s string) *string {
return &s
}
func TestIMMessageTypeDraftyEntities(t *testing.T) {
cases := []struct {
name string
msg *types.Message
want string
}{
{
name: "nil message",
msg: nil,
want: "text",
},
{
name: "drafty link entity",
msg: &types.Message{Content: map[string]any{
"txt": "check this https://example.com",
"fmt": []any{map[string]any{"at": 10, "len": 19, "key": 0}},
"ent": []any{map[string]any{"tp": "LN", "data": map[string]any{"url": "https://example.com"}}},
}},
want: "link",
},
{
name: "drafty image entity",
msg: &types.Message{Content: map[string]any{
"txt": "a photo",
"fmt": []any{map[string]any{"at": 7, "len": 1, "key": 0}},
"ent": []any{map[string]any{"tp": "IM", "data": map[string]any{"mime": "image/png", "name": "pic.png"}}},
}},
want: "image",
},
{
name: "drafty video entity",
msg: &types.Message{Content: map[string]any{
"txt": "clip",
"ent": []any{map[string]any{"tp": "VD", "data": map[string]any{"mime": "video/mp4"}}},
}},
want: "video",
},
{
name: "drafty audio entity",
msg: &types.Message{Content: map[string]any{
"txt": "",
"ent": []any{map[string]any{"tp": "AU", "data": map[string]any{"mime": "audio/mp4"}}},
}},
want: "voice",
},
{
name: "drafty generic file entity",
msg: &types.Message{Content: map[string]any{
"txt": "doc",
"ent": []any{map[string]any{"tp": "EX", "data": map[string]any{"mime": "application/pdf", "name": "a.pdf"}}},
}},
want: "file",
},
{
name: "drafty image file entity refines by mime",
msg: &types.Message{Content: map[string]any{
"ent": []any{map[string]any{"tp": "EX", "data": map[string]any{"mime": "image/jpeg"}}},
}},
want: "image",
},
{
name: "drafty attachment wins over inline link",
msg: &types.Message{Content: map[string]any{
"txt": "see https://example.com and",
"fmt": []any{map[string]any{"at": 4, "len": 18, "key": 1}, map[string]any{"at": 26, "len": 1, "key": 0}},
"ent": []any{map[string]any{"tp": "EX", "data": map[string]any{"mime": "application/zip"}}, map[string]any{"tp": "LN", "data": map[string]any{}}},
}},
want: "file",
},
{
name: "drafty plain text",
msg: &types.Message{Content: map[string]any{
"txt": "hello",
}},
want: "text",
},
{
name: "legacy flat file_id content still works",
msg: &types.Message{Content: map[string]any{"file_id": "file-1", "mime": "image/png"}},
want: "image",
},
{
name: "legacy flat url content still works",
msg: &types.Message{Content: map[string]any{"url": "https://example.com"}},
want: "link",
},
{
name: "head type takes priority",
msg: &types.Message{
Head: types.KVMap{"type": "system_notice"},
Content: map[string]any{"ent": []any{map[string]any{"tp": "IM"}}},
},
want: "system_notice",
},
{
name: "string content",
msg: &types.Message{Content: "plain"},
want: "text",
},
}
for _, tc := range cases {
if got := imMessageType(tc.msg); got != tc.want {
t.Errorf("%s: imMessageType = %q, want %q", tc.name, got, tc.want)
}
}
}