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:
@@ -8885,10 +8885,70 @@ func imMessageType(msg *types.Message) string {
|
|||||||
if stringFromAny(content["url"]) != "" {
|
if stringFromAny(content["url"]) != "" {
|
||||||
return "link"
|
return "link"
|
||||||
}
|
}
|
||||||
|
if mtype := draftyIMMessageType(content); mtype != "" {
|
||||||
|
return mtype
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "text"
|
return "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// draftyIMMessageType derives the semantic message type from Drafty content
|
||||||
|
// ({"txt","fmt","ent"}), where "ent" holds entity definitions {"tp","data"}.
|
||||||
|
// Attachment entities win over inline links; EX is refined by its mime type.
|
||||||
|
// Returns "" when no recognized entity is present.
|
||||||
|
func draftyIMMessageType(content map[string]any) string {
|
||||||
|
ents, ok := content["ent"].([]any)
|
||||||
|
if !ok || len(ents) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
link := ""
|
||||||
|
file := ""
|
||||||
|
for _, e := range ents {
|
||||||
|
ent, ok := e.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, _ := ent["data"].(map[string]any)
|
||||||
|
switch stringFromAny(ent["tp"]) {
|
||||||
|
case "IM":
|
||||||
|
return "image"
|
||||||
|
case "VD":
|
||||||
|
return "video"
|
||||||
|
case "AU":
|
||||||
|
return "voice"
|
||||||
|
case "EX":
|
||||||
|
if file == "" {
|
||||||
|
file = draftyAttachmentType(data)
|
||||||
|
}
|
||||||
|
case "LN":
|
||||||
|
if link == "" {
|
||||||
|
link = "link"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if file != "" {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
|
||||||
|
func draftyAttachmentType(data map[string]any) string {
|
||||||
|
mime := ""
|
||||||
|
if data != nil {
|
||||||
|
mime = stringFromAny(data["mime"])
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(mime, "image/"):
|
||||||
|
return "image"
|
||||||
|
case strings.HasPrefix(mime, "video/"):
|
||||||
|
return "video"
|
||||||
|
case strings.HasPrefix(mime, "audio/"):
|
||||||
|
return "voice"
|
||||||
|
default:
|
||||||
|
return "file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func favoriteSnapshot(msg *types.Message) map[string]any {
|
func favoriteSnapshot(msg *types.Message) map[string]any {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return map[string]any{}
|
return map[string]any{}
|
||||||
|
|||||||
@@ -8002,3 +8002,110 @@ func ptrTime(t time.Time) *time.Time {
|
|||||||
func ptrString(s string) *string {
|
func ptrString(s string) *string {
|
||||||
return &s
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user