Skip to content

Commit 9caff41

Browse files
committed
feat(builder): markdown-driven rich text via text(md, { rich: true }); remove aiRich()
DX overhaul: instead of structured AIRichPart[], users write plain markdown and toggle { rich: true }. A new block-level parser (markdown.ts) segments fenced code, GitHub tables, image lines, and ::: container directives (suggest/tip/image/video/product/reels/post) into AIRich parts; prose stays text with inline hyperlink/citation/LaTeX. The .aiRich() builder method is removed — rich rendering now flows through text() with title/footer/sources in the second arg (TextOptions). Engine (buildAIRichContent) unchanged. Example rewritten to markdown; +11 builder tests.
1 parent 72e5a75 commit 9caff41

7 files changed

Lines changed: 413 additions & 100 deletions

File tree

examples/airich-bot.ts

Lines changed: 67 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,15 @@ const showcase = async (target: string): Promise<void> => {
3131
await sleep(1800)
3232
}
3333

34-
const brief = [
34+
const briefingMd = [
3535
'*Tech Brief — Senin pagi* ☕',
3636
'',
3737
'Rangkuman harian dari [zaileys](https://github.com/zeative/zaileys).',
3838
'Sumber data dipantau otomatis. [](https://github.com/zeative/zaileys)',
3939
'',
4040
'Rumus hari ini: [E = mc^2|160|44]<https://latex.codecogs.com/png.image?E%20%3D%20mc%5E2>',
4141
'',
42-
'Tiga sorotan utama hari ini, plus cuplikan kode untuk bikin bot interaktif.',
43-
].join('\n')
44-
45-
const code = [
42+
'```typescript',
4643
"import { Client } from 'zaileys'",
4744
'',
4845
'const client = new Client()',
@@ -53,87 +50,79 @@ const showcase = async (target: string): Promise<void> => {
5350
" { text: 'Lanjut order?' },",
5451
' )',
5552
'})',
53+
'```',
5654
'',
57-
"client.on('button-click', (ctx) => {",
58-
" console.log('clicked:', ctx.buttonId)",
59-
'})',
55+
'| Repo | Stars | Δ 24h |',
56+
'|---|---|---|',
57+
'| zaileys | 12.4k | +318 |',
58+
'| baileys | 15.1k | +92 |',
59+
'| venom | 6.2k | +11 |',
6060
].join('\n')
6161

62-
const leaderboard = [
63-
['Repo', 'Stars', 'Δ 24h'],
64-
['zaileys', '12.4k', '+318'],
65-
['baileys', '15.1k', '+92'],
66-
['venom', '6.2k', '+11'],
67-
]
68-
6962
await send('briefing', () =>
70-
client.send(target).aiRich(
71-
[
72-
{ type: 'text', text: brief },
73-
{ type: 'code', language: 'typescript', content: code },
74-
{ type: 'table', rows: leaderboard },
75-
],
76-
{
77-
title: '📰 zaileys Daily',
78-
footer: '💡 Dibuat dengan zaileys — github.com/zeative/zaileys',
79-
sources: [['https://avatars.githubusercontent.com/u/9919?s=64', 'https://github.com/zeative/zaileys', 'zaileys on GitHub']],
80-
},
81-
),
63+
client.send(target).text(briefingMd, {
64+
rich: true,
65+
title: '📰 zaileys Daily',
66+
footer: '💡 Dibuat dengan zaileys — github.com/zeative/zaileys',
67+
sources: [['https://avatars.githubusercontent.com/u/9919?s=64', 'https://github.com/zeative/zaileys', 'zaileys on GitHub']],
68+
}),
8269
)
8370

84-
await send('galeri', () =>
85-
client.send(target).aiRich(
86-
[
87-
{ type: 'text', text: '*Galeri rilis v4* — geser untuk lihat tangkapan layar & klip.' },
88-
{ type: 'image', url: [POSTER, SHOT] },
89-
{ type: 'video', url: CLIP, duration: 10 },
90-
{ type: 'tip', text: 'Ketuk gambar untuk pratinjau penuh' },
91-
{ type: 'suggest', prompts: ['Lihat changelog', 'Cara upgrade', 'Bandingkan v3 vs v4'] },
92-
],
93-
{ title: '🖼️ zaileys v4', footer: '#zaileys' },
94-
),
95-
)
71+
const galeriMd = [
72+
'*Galeri rilis v4* — geser untuk lihat tangkapan layar & klip.',
73+
'',
74+
`![shot](${POSTER})`,
75+
`![shot](${SHOT})`,
76+
'',
77+
':::video',
78+
`${CLIP} | 10`,
79+
':::',
80+
'',
81+
':::tip',
82+
'Ketuk gambar untuk pratinjau penuh',
83+
':::',
84+
'',
85+
':::suggest',
86+
'Lihat changelog | Cara upgrade | Bandingkan v3 vs v4',
87+
':::',
88+
].join('\n')
9689

97-
await send('sosial', () =>
98-
client.send(target).aiRich(
99-
[
100-
{ type: 'text', text: 'Lagi ramai dibahas komunitas 👇' },
101-
{
102-
type: 'reels',
103-
reels: [
104-
{ username: 'zeative', title: 'Demo nativeFlow buttons', url: CLIP, thumbnail: SHOT, views: 12400, likes: 980, verified: true },
105-
{ username: 'zeative', title: 'AIRich rich response', url: CLIP, thumbnail: POSTER, views: 8800, likes: 740, verified: true },
106-
],
107-
},
108-
{
109-
type: 'post',
110-
posts: [
111-
{ username: 'zeative', title: 'zaileys v4 is out', caption: 'Buttons, carousel, AIRich — semua built-in.', thumbnail: SHOT, likes: 1500, comments: 132, verified: true, source: 'GITHUB' },
112-
],
113-
},
114-
{ type: 'suggest', prompts: ['Tonton lagi', 'Bagikan'] },
115-
],
116-
{ title: '🔥 Trending' },
117-
),
118-
)
90+
await send('galeri', () => client.send(target).text(galeriMd, { rich: true, title: '🖼️ zaileys v4', footer: '#zaileys' }))
11991

120-
await send('toko', () =>
121-
client.send(target).aiRich(
122-
[
123-
{ type: 'text', text: 'Merch komunitas zaileys 🛍️' },
124-
{
125-
type: 'product',
126-
products: [
127-
{ title: 'Sticker Pack', price: 'Rp35.000', salePrice: 'Rp25.000', brand: 'zaileys', image: SHOT, url: 'https://github.com/zeative/zaileys' },
128-
{ title: 'Hoodie Dev', price: 'Rp320.000', salePrice: 'Rp275.000', brand: 'zaileys', image: POSTER, url: 'https://github.com/zeative/zaileys' },
129-
{ title: 'Mug Coder', price: 'Rp90.000', salePrice: 'Rp69.000', brand: 'zaileys', image: SHOT, url: 'https://github.com/zeative/zaileys' },
130-
],
131-
},
132-
{ type: 'suggest', prompts: ['Lihat semua produk', 'Checkout'] },
133-
],
134-
{ title: '🛍️ zaileys Store' },
135-
),
136-
)
92+
const sosialMd = [
93+
'Lagi ramai dibahas komunitas 👇',
94+
'',
95+
':::reels',
96+
`- user: zeative | title: Demo nativeFlow buttons | url: ${CLIP} | thumb: ${SHOT} | views: 12400 | likes: 980 | verified: true`,
97+
`- user: zeative | title: AIRich rich response | url: ${CLIP} | thumb: ${POSTER} | views: 8800 | likes: 740 | verified: true`,
98+
':::',
99+
'',
100+
':::post',
101+
`- user: zeative | title: zaileys v4 is out | caption: Buttons, carousel, AIRich — semua built-in. | thumb: ${SHOT} | likes: 1500 | comments: 132 | verified: true | source: GITHUB`,
102+
':::',
103+
'',
104+
':::suggest',
105+
'Tonton lagi | Bagikan',
106+
':::',
107+
].join('\n')
108+
109+
await send('sosial', () => client.send(target).text(sosialMd, { rich: true, title: '🔥 Trending' }))
110+
111+
const tokoMd = [
112+
'Merch komunitas zaileys 🛍️',
113+
'',
114+
':::product',
115+
`- title: Sticker Pack | price: Rp35.000 | sale: Rp25.000 | brand: zaileys | image: ${SHOT} | url: https://github.com/zeative/zaileys`,
116+
`- title: Hoodie Dev | price: Rp320.000 | sale: Rp275.000 | brand: zaileys | image: ${POSTER} | url: https://github.com/zeative/zaileys`,
117+
`- title: Mug Coder | price: Rp90.000 | sale: Rp69.000 | brand: zaileys | image: ${SHOT} | url: https://github.com/zeative/zaileys`,
118+
':::',
119+
'',
120+
':::suggest',
121+
'Lihat semua produk | Checkout',
122+
':::',
123+
].join('\n')
124+
125+
await send('toko', () => client.send(target).text(tokoMd, { rich: true, title: '🛍️ zaileys Store' }))
137126

138127
console.log(` └─ ${ok}/${n} terkirim`)
139128
}

examples/express-integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ app.get('/health', (_req: Request, res: Response) => {
4343
res.json({ connected })
4444
})
4545

46-
const port = Number(process.env['PORT'] ?? 3000)
46+
const port = Number(process.env['PORT'] ?? 4252)
4747
app.listen(port, () => {
4848
console.log(`HTTP send gateway listening on :${port}`)
4949
})

src/builder/builder.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { sendAlbum } from './album.js'
1111
import { buildAudioContent } from './content/audio.js'
1212
import { buildButtonsContent, RELAY_CONTENT_KEY, RELAY_MEDIA_KEY, type ButtonsContentOptions, type HeaderMedia } from './content/buttons.js'
1313
import { buildCarouselContent, RELAY_CARDS_MEDIA_KEY, type CardMedia, type CarouselCard } from './content/carousel.js'
14-
import { buildAIRichContent, type AIRichOptions, type AIRichPart } from './content/airich.js'
14+
import { buildAIRichContent, type AIRichOptions } from './content/airich.js'
15+
import { parseRichMarkdown } from './content/markdown.js'
1516
import { loadMedia } from './media-loader.js'
1617
import { buildContactContent } from './content/contact.js'
1718
import { buildDocumentContent } from './content/document.js'
@@ -62,6 +63,9 @@ export interface BuilderSocketLike {
6263
waUploadToServer?: unknown
6364
}
6465

66+
/** Options for {@link MessageBuilder.text}; `rich` opts the body into markdown→AIRich rendering. */
67+
export type TextOptions = { rich?: boolean } & AIRichOptions
68+
6569
/**
6670
* Binary-node hint that makes WhatsApp render `interactiveMessage` content
6771
* (native_flow buttons/lists). Without it the server delivers the message but the
@@ -123,8 +127,18 @@ export class MessageBuilder<State extends BuilderState> {
123127
return this
124128
}
125129

126-
text(this: MessageBuilder<'init'>, content: string): MessageBuilder<'content-set'> {
127-
this.internal.content = buildTextContent(content)
130+
/**
131+
* Set the message body. Plain text by default; pass `{ rich: true }` to parse
132+
* `content` as markdown and send it as an EXPERIMENTAL AIRich rich-response
133+
* (fenced code, tables, images, `:::` directives, inline hyperlinks/citations/
134+
* LaTeX). `title`/`footer`/`sources` decorate the rich card.
135+
*/
136+
text(this: MessageBuilder<'init'>, content: string, opts?: TextOptions): MessageBuilder<'content-set'> {
137+
if (opts?.rich === true) {
138+
this.internal.content = buildAIRichContent(parseRichMarkdown(content), opts)
139+
} else {
140+
this.internal.content = buildTextContent(content)
141+
}
128142
return this as unknown as MessageBuilder<'content-set'>
129143
}
130144

@@ -171,20 +185,6 @@ export class MessageBuilder<State extends BuilderState> {
171185
return this as unknown as MessageBuilder<'content-set'>
172186
}
173187

174-
/**
175-
* EXPERIMENTAL — send a Meta AI rich-response message (text with hyperlinks/
176-
* citations, code blocks, tables). Uses a reverse-engineered, non-standard
177-
* WhatsApp format that may break with WhatsApp updates.
178-
*/
179-
aiRich(
180-
this: MessageBuilder<'init'>,
181-
parts: AIRichPart[],
182-
opts?: AIRichOptions,
183-
): MessageBuilder<'content-set'> {
184-
this.internal.content = buildAIRichContent(parts, opts)
185-
return this as unknown as MessageBuilder<'content-set'>
186-
}
187-
188188
list(this: MessageBuilder<'init'>, opts: ListOptions): MessageBuilder<'content-set'> {
189189
this.internal.content = buildListContent(opts)
190190
return this as unknown as MessageBuilder<'content-set'>

src/builder/content/airich.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ const SOURCE_URL = 'https://github.com/zeative/zaileys'
285285
*/
286286
export const buildAIRichContent = (parts: AIRichPart[], opts?: AIRichOptions): AnyMessageContent => {
287287
if (!Array.isArray(parts) || parts.length === 0) {
288-
throw new ZaileysBuilderError('INVALID_OPTIONS', 'aiRich() requires at least one part')
288+
throw new ZaileysBuilderError('INVALID_OPTIONS', 'text({ rich: true }) requires non-empty markdown content')
289289
}
290290
const sections: Array<Record<string, unknown>> = []
291291
const submessages: Array<Record<string, unknown>> = []

0 commit comments

Comments
 (0)