forked from EduCraft/curriculum-project-hub
feat: parse inbound post (rich text) messages
- parsePostMessage: full Feishu post payload parser with locale fallback, supports text/a/at/img/media/file/code_block/br/hr/emotion elements - Trigger accepts post messages, extracts text + downloads attachments - Post messages with attachments bypass batching, text-only posts batch - Add unit tests (10) for all element types and edge cases
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parsePostMessage } from "../../src/feishu/client.js";
|
||||
|
||||
describe("parsePostMessage", () => {
|
||||
it("parses a simple text post", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: { content: [[{ tag: "text", text: "Hello" }]] },
|
||||
}));
|
||||
|
||||
expect(result).toEqual({ text: "Hello", imageKeys: [], fileKeys: [] });
|
||||
});
|
||||
|
||||
it("prepends a post title as a markdown header", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: { title: "Lesson Notes", content: [[{ tag: "text", text: "Hello" }]] },
|
||||
}));
|
||||
|
||||
expect(result.text).toBe("# Lesson Notes\n\nHello");
|
||||
});
|
||||
|
||||
it("converts links to markdown", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: {
|
||||
content: [[
|
||||
{ tag: "text", text: "Read " },
|
||||
{ tag: "a", text: "docs", href: "https://example.com" },
|
||||
]],
|
||||
},
|
||||
}));
|
||||
|
||||
expect(result.text).toBe("Read [docs](https://example.com)");
|
||||
});
|
||||
|
||||
it("converts mentions to readable @names", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: { content: [[{ tag: "at", user_id: "@_user_1", user_name: "John" }]] },
|
||||
}));
|
||||
|
||||
expect(result.text).toBe("@John");
|
||||
});
|
||||
|
||||
it("converts code blocks to fenced markdown", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: { content: [[{ tag: "code_block", language: "ts", text: "const x = 1;" }]] },
|
||||
}));
|
||||
|
||||
expect(result.text).toBe("```ts\nconst x = 1;\n```");
|
||||
});
|
||||
|
||||
it("collects image keys and inserts an image placeholder", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: { content: [[{ tag: "img", image_key: "img_key_here" }]] },
|
||||
}));
|
||||
|
||||
expect(result).toEqual({ text: "[Image]", imageKeys: ["img_key_here"], fileKeys: [] });
|
||||
});
|
||||
|
||||
it("collects file keys and inserts an attachment placeholder", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: { content: [[{ tag: "file", file_key: "file_key_here", file_name: "worksheet.pdf" }]] },
|
||||
}));
|
||||
|
||||
expect(result).toEqual({
|
||||
text: "[Attachment: worksheet.pdf]",
|
||||
imageKeys: [],
|
||||
fileKeys: ["file_key_here"],
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to en_us when zh_cn is missing", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
en_us: { content: [[{ tag: "text", text: "Hello from English" }]] },
|
||||
}));
|
||||
|
||||
expect(result.text).toBe("Hello from English");
|
||||
});
|
||||
|
||||
it("unwraps content nested under a post key", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
post: {
|
||||
zh_cn: { content: [[{ tag: "text", text: "Wrapped" }]] },
|
||||
},
|
||||
}));
|
||||
|
||||
expect(result.text).toBe("Wrapped");
|
||||
});
|
||||
|
||||
it("joins multiple rows with newlines", () => {
|
||||
const result = parsePostMessage(postContent({
|
||||
zh_cn: {
|
||||
content: [
|
||||
[{ tag: "text", text: "First row" }],
|
||||
[{ tag: "text", text: "Second row" }],
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
expect(result.text).toBe("First row\nSecond row");
|
||||
});
|
||||
});
|
||||
|
||||
function postContent(value: unknown): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
Reference in New Issue
Block a user