Skip to content

Commit c8765bc

Browse files
committed
refactor(examples): unify house-style (headers, helpers, env, logging); exclude convex from typecheck
1 parent 5ff8eff commit c8765bc

11 files changed

Lines changed: 142 additions & 82 deletions

examples/airich-bot.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
/**
2+
* Owner-triggered AIRich showcase: reply ".za oii" to render briefing, gallery,
3+
* social, and store cards from plain markdown.
4+
*
5+
* Run: OWNER=6285xxxx bun run examples/airich-bot.ts
6+
*/
17
import { Client } from '../src/index.js'
28

3-
const OWNER = (process.env['OWNER'] ?? process.env['AIRICH_TO'] ?? '').replace(/\D/g, '')
9+
const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))
10+
const digitsOf = (jid: string): string => (jid.split(/[:@]/)[0] ?? '').replace(/\D/g, '')
11+
12+
const OWNER = digitsOf(process.env['OWNER'] ?? '')
413
if (!OWNER) {
5-
console.error('Set OWNER (nomor kamu), e.g. OWNER=6285xxxx bun run examples/airich-bot.ts')
14+
console.error('Set OWNER (your number), e.g. OWNER=6285xxxx bun run examples/airich-bot.ts')
615
process.exit(1)
716
}
817

@@ -11,9 +20,6 @@ const POSTER = 'https://placehold.co/600x800/png'
1120
const SHOT = 'https://placehold.co/512x512/png'
1221
const CLIP = 'https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/360/Big_Buck_Bunny_360_10s_1MB.mp4'
1322

14-
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))
15-
const digitsOf = (jid: string): string => (jid.split(/[:@]/)[0] ?? '').replace(/\D/g, '')
16-
1723
const client = new Client({ ignoreMe: false })
1824

1925
const showcase = async (target: string): Promise<void> => {

examples/broadcast.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
/**
2+
* Broadcast one message to many recipients with rate limiting and progress.
3+
*
4+
* Run: bun run examples/broadcast.ts
5+
*/
16
import { Client } from '../src/index.js'
27

38
const client = new Client()
49

10+
client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
11+
512
const recipients = [
613
'6281111111111@s.whatsapp.net',
714
'6282222222222@s.whatsapp.net',

examples/buttons-bot.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,42 @@
1+
/**
2+
* Send every interactive button variant (reply, CTA, template, list, carousel,
3+
* reminder, location, bottomSheet, limitedTimeOffer) and log click round-trips.
4+
*
5+
* Run: TO=628xxxx@s.whatsapp.net bun run examples/buttons-bot.ts
6+
*/
17
import { Client } from '../src/index.js'
28

3-
const TO = process.env['BUTTONS_TO'] ?? ''
9+
const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))
10+
11+
const fetchBuf = async (url: string): Promise<Buffer | null> => {
12+
try {
13+
const res = await fetch(url)
14+
return res.ok ? Buffer.from(await res.arrayBuffer()) : null
15+
} catch {
16+
return null
17+
}
18+
}
19+
20+
const TO = process.env['TO'] ?? ''
421
if (!TO) {
5-
console.error('Set BUTTONS_TO, e.g. BUTTONS_TO=628xxxx@s.whatsapp.net bun run examples/buttons-bot.ts')
22+
console.error('Set TO, e.g. TO=628xxxx@s.whatsapp.net bun run examples/buttons-bot.ts')
623
process.exit(1)
724
}
825

926
const client = new Client()
10-
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))
1127

1228
client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
1329

14-
const fetchBuf = async (url: string): Promise<Buffer | undefined> => {
15-
try {
16-
const res = await fetch(url)
17-
return res.ok ? Buffer.from(await res.arrayBuffer()) : undefined
18-
} catch {
19-
return undefined
20-
}
21-
}
22-
2330
client.on('connect', async ({ me }) => {
24-
console.log('Connected as', me.id, '\n=== sending button variants ->', TO, '===\n')
31+
console.log(`Connected as ${me.id} sending button variants to ${TO}\n`)
2532
const headerImage = await fetchBuf('https://placehold.co/512x512/png')
2633

2734
const send = async (label: string, fn: () => unknown): Promise<void> => {
2835
try {
2936
const key = (await (fn() as Promise<{ id?: string }>)) ?? {}
30-
console.log('OK ', label, '|', key.id ?? 'sent')
37+
console.log(`✓ ${label}${key.id ?? 'sent'}`)
3138
} catch (e) {
32-
console.log('FAIL ', label, '->', e instanceof Error ? e.message : String(e))
39+
console.log(`✗ ${label}${e instanceof Error ? e.message : String(e)}`)
3340
}
3441
await sleep(1800)
3542
}
@@ -178,13 +185,13 @@ client.on('connect', async ({ me }) => {
178185
)
179186
}
180187

181-
console.log('\n[done] check your phone. Tap any button to test the click round-trip.\n')
188+
console.log('\nDone — tap any button on your phone to test the click round-trip.\n')
182189
})
183190

184191
client.on('button-click', (ctx) => {
185-
console.log('>>> button-click FIRED | id:', ctx.buttonId, '| text:', ctx.buttonText, '| from:', ctx.sender.jid)
192+
console.log(`button-click id: ${ctx.buttonId} | text: ${ctx.buttonText} | from: ${ctx.sender.jid}`)
186193
})
187194

188195
client.on('list-select', (ctx) => {
189-
console.log('>>> list-select FIRED | rowId:', ctx.rowId, '| title:', ctx.title, '| from:', ctx.sender.jid)
196+
console.log(`list-select rowId: ${ctx.rowId} | title: ${ctx.title} | from: ${ctx.sender.jid}`)
190197
})

examples/command-bot.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
/**
2+
* Slash-command router with middleware (/ping, /help, /weather).
3+
*
4+
* Run: bun run examples/command-bot.ts
5+
*/
16
import { Client, type Middleware } from '../src/index.js'
27

38
const client = new Client({ commandPrefix: ['/', '!'] })
49

10+
client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
11+
512
const loggingMiddleware: Middleware = async (ctx, next) => {
613
console.log(`[command] ${ctx.command} from ${ctx.senderId} args=${ctx.args.join(',')}`)
714
await next()

examples/convex-store.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
/**
2+
* Persist session + chat history in Convex. Deploy examples/convex/{schema,zaileys}.ts
3+
* to your Convex project first (see examples/convex/README.md).
4+
*
5+
* Run: CONVEX_URL=https://your.convex.cloud bun run examples/convex-store.ts
6+
*/
17
import { Client, ConvexAuthStore, ConvexMessageStore } from '../src/index.js'
28

3-
// Persist session + chat history in Convex. Deploy docs/convex/{schema,zaileys}.ts
4-
// to your Convex project first, then: CONVEX_URL=https://xxx.convex.cloud bun run examples/convex-store.ts
59
const CONVEX_URL = process.env['CONVEX_URL'] ?? ''
610
if (!CONVEX_URL) {
711
console.error('Set CONVEX_URL, e.g. CONVEX_URL=https://your.convex.cloud bun run examples/convex-store.ts')

examples/e2e-gauntlet.ts

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
import { Client } from '../src/index.js'
1+
/**
2+
* End-to-end gauntlet: send every message type to one recipient and print a
3+
* pass/fail report. Useful for smoke-testing a build against a real account.
4+
*
5+
* Run: TO=628xxxx@s.whatsapp.net GAP_MS=1500 bun run examples/e2e-gauntlet.ts
6+
*/
27
import type { WAMessageKey } from 'baileys'
8+
import { Client } from '../src/index.js'
39

4-
const TARGET = process.env['GAUNTLET_TO'] ?? ''
5-
const GAP_MS = Number(process.env['GAUNTLET_GAP'] ?? 1500)
10+
const TO = process.env['TO'] ?? ''
11+
const GAP_MS = Number(process.env['GAP_MS'] ?? 1500)
612

7-
if (!TARGET) {
8-
console.error('Set GAUNTLET_TO to a recipient jid, e.g. GAUNTLET_TO=628xxxx@s.whatsapp.net bun run examples/e2e-gauntlet.ts')
13+
if (!TO) {
14+
console.error('Set TO, e.g. TO=628xxxx@s.whatsapp.net bun run examples/e2e-gauntlet.ts')
915
process.exit(1)
1016
}
1117

@@ -41,8 +47,10 @@ const results: Result[] = []
4147

4248
const client = new Client({ ignoreMe: true })
4349

50+
client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
51+
4452
client.on('connect', async ({ me }) => {
45-
console.log('Connected as', me.id, '\n=== E2E gauntlet -> ', TARGET, '===\n')
53+
console.log(`Connected as ${me.id} — running E2E gauntlet against ${TO}\n`)
4654

4755
const video = await fetchBuf('https://www.w3schools.com/html/mov_bbb.mp4')
4856
const audio = await fetchBuf('https://www.w3schools.com/html/horse.mp3')
@@ -54,59 +62,59 @@ client.on('connect', async ({ me }) => {
5462
const run = async (feature: string, fn: () => unknown, skip = false): Promise<void> => {
5563
if (skip) {
5664
results.push({ feature, status: 'SKIP', detail: 'asset unavailable' })
57-
console.log(`SKIP ${feature} (asset unavailable)`)
65+
console.log(` ${feature} (asset unavailable)`)
5866
return
5967
}
6068
try {
6169
const key = (await (fn() as unknown as Promise<WAMessageKey | void>)) as WAMessageKey | undefined
6270
const id = key && typeof key === 'object' && 'id' in key ? String(key.id) : 'ok'
6371
results.push({ feature, status: 'OK', detail: id })
64-
console.log(`OK ${feature} (${id})`)
72+
console.log(`${feature} ${id}`)
6573
} catch (e) {
66-
const msg = e instanceof Error ? e.message : String(e)
67-
results.push({ feature, status: 'FAIL', detail: msg })
68-
console.log(`FAIL ${feature} -> ${msg}`)
74+
const detail = e instanceof Error ? e.message : String(e)
75+
results.push({ feature, status: 'FAIL', detail })
76+
console.log(`${feature} ${detail}`)
6977
}
7078
await sleep(GAP_MS)
7179
}
7280

7381
await run('text', async () => {
74-
anchor = await client.send(TARGET).text('zaileys e2e: text ✅')
82+
anchor = await client.send(TO).text('zaileys e2e: text ✅')
7583
return anchor
7684
})
77-
await run('text + mentions', () => client.send(TARGET).text('zaileys e2e: mentions ✅').mentions([TARGET]))
85+
await run('text + mentions', () => client.send(TO).text('zaileys e2e: mentions ✅').mentions([TO]))
7886
await run('reply (quote)', async () => {
7987
if (!anchor) throw new Error('no anchor')
8088
const full = await client.store.getMessage(anchor)
81-
return client.send(TARGET).text('zaileys e2e: reply ✅').reply(full ?? anchor)
89+
return client.send(TO).text('zaileys e2e: reply ✅').reply(full ?? anchor)
8290
})
8391
await run('react 👍', () => (anchor ? client.react(anchor, '👍') : Promise.reject(new Error('no anchor'))))
84-
await run('image (real png)', () => client.send(TARGET).image(realImage, { caption: 'zaileys e2e: image ✅' }))
85-
await run('sticker (real webp)', () => client.send(TARGET).sticker(realSticker))
86-
await run('document (txt inline)', () => client.send(TARGET).document(DOC_BYTES, { fileName: 'zaileys-e2e.txt', mimetype: 'text/plain', caption: 'doc ✅' }))
87-
await run('video (fetched mp4)', () => client.send(TARGET).video(video as Buffer, { caption: 'zaileys e2e: video ✅' }), video === null)
88-
await run('gif (video gifPlayback)', () => client.send(TARGET).video(video as Buffer, { caption: 'gif ✅', gifPlayback: true }), video === null)
89-
await run('audio (fetched mp3)', () => client.send(TARGET).audio(audio as Buffer), audio === null)
90-
await run('voice note (ptt)', () => client.send(TARGET).audio(audio as Buffer, { ptt: true }), audio === null)
91-
await run('album (2 images)', () => client.send(TARGET).album([
92+
await run('image (real png)', () => client.send(TO).image(realImage, { caption: 'zaileys e2e: image ✅' }))
93+
await run('sticker (real webp)', () => client.send(TO).sticker(realSticker))
94+
await run('document (txt inline)', () => client.send(TO).document(DOC_BYTES, { fileName: 'zaileys-e2e.txt', mimetype: 'text/plain', caption: 'doc ✅' }))
95+
await run('video (fetched mp4)', () => client.send(TO).video(video as Buffer, { caption: 'zaileys e2e: video ✅' }), video === null)
96+
await run('gif (video gifPlayback)', () => client.send(TO).video(video as Buffer, { caption: 'gif ✅', gifPlayback: true }), video === null)
97+
await run('audio (fetched mp3)', () => client.send(TO).audio(audio as Buffer), audio === null)
98+
await run('voice note (ptt)', () => client.send(TO).audio(audio as Buffer, { ptt: true }), audio === null)
99+
await run('album (2 images)', () => client.send(TO).album([
92100
{ type: 'image', src: realImage, caption: 'album 1' },
93101
{ type: 'image', src: realImage, caption: 'album 2' },
94102
]))
95-
await run('location', () => client.send(TARGET).location(-6.2, 106.816666, { name: 'Jakarta', address: 'Indonesia' }))
96-
await run('contact (vcard)', () => client.send(TARGET).contact(VCARD))
97-
await run('poll', () => client.send(TARGET).poll('zaileys e2e poll?', ['A', 'B', 'C'], { multipleChoice: false }))
98-
await run('buttons', () => client.send(TARGET).buttons([{ id: 'b1', text: 'Yes' }, { id: 'b2', text: 'No' }], { text: 'zaileys e2e: buttons', footer: 'pick one' }))
99-
await run('list', () => client.send(TARGET).list({
103+
await run('location', () => client.send(TO).location(-6.2, 106.816666, { name: 'Jakarta', address: 'Indonesia' }))
104+
await run('contact (vcard)', () => client.send(TO).contact(VCARD))
105+
await run('poll', () => client.send(TO).poll('zaileys e2e poll?', ['A', 'B', 'C'], { multipleChoice: false }))
106+
await run('buttons', () => client.send(TO).buttons([{ id: 'b1', text: 'Yes' }, { id: 'b2', text: 'No' }], { text: 'zaileys e2e: buttons', footer: 'pick one' }))
107+
await run('list', () => client.send(TO).list({
100108
title: 'zaileys e2e list', description: 'choose', buttonText: 'Open', footerText: 'footer',
101109
sections: [{ title: 'Section', rows: [{ id: 'r1', title: 'Row 1', description: 'first' }, { id: 'r2', title: 'Row 2' }] }],
102110
}))
103-
await run('forward', () => (anchor ? client.forward(anchor, TARGET) : Promise.reject(new Error('no anchor'))))
111+
await run('forward', () => (anchor ? client.forward(anchor, TO) : Promise.reject(new Error('no anchor'))))
104112
await run('edit', async () => {
105113
if (!anchor) throw new Error('no anchor')
106114
await client.edit(anchor).text('zaileys e2e: text ✅ (edited)')
107115
})
108116
await run('delete (for everyone)', async () => {
109-
const k = await client.send(TARGET).text('zaileys e2e: this will be deleted')
117+
const k = await client.send(TO).text('zaileys e2e: this will be deleted')
110118
await sleep(800)
111119
await client.delete(k)
112120
})

examples/express-integration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
/**
2+
* Expose an HTTP gateway (POST /send, GET /health) backed by a WhatsApp client.
3+
*
4+
* Run: PORT=4252 bun run examples/express-integration.ts
5+
*/
16
import express, { type Request, type Response } from 'express'
27
import { Client } from '../src/index.js'
38

49
const client = new Client()
510
let connected = false
611

12+
client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
13+
714
client.on('connect', ({ me }) => {
815
connected = true
916
console.log('WhatsApp connected as', me.id)

examples/multi-account.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
1+
/**
2+
* Run two independent WhatsApp sessions in one process.
3+
*
4+
* Run: bun run examples/multi-account.ts
5+
*/
16
import { Client } from '../src/index.js'
27

38
const primary = new Client({ sessionId: 'account-a' })
49
const secondary = new Client({ sessionId: 'account-b' })
510

6-
primary.on('connect', ({ me }) => console.log('[account-a] connected as', me.id))
7-
secondary.on('connect', ({ me }) => console.log('[account-b] connected as', me.id))
11+
primary.on('qr', ({ qrString }) => console.log('[account-a] Scan QR:', qrString))
12+
secondary.on('qr', ({ qrString }) => console.log('[account-b] Scan QR:', qrString))
813

9-
primary.on('text', async (message) => {
10-
if (message.isFromMe) return
11-
await primary.send(message.senderId).text('Reply from account A')
14+
primary.on('connect', ({ me }) => console.log('[account-a] Connected as', me.id))
15+
secondary.on('connect', ({ me }) => console.log('[account-b] Connected as', me.id))
16+
17+
primary.on('text', async (msg) => {
18+
if (msg.isFromMe) return
19+
await primary.send(msg.senderId).text('Reply from account A')
1220
})
1321

14-
secondary.on('text', async (message) => {
15-
if (message.isFromMe) return
16-
await secondary.send(message.senderId).text('Reply from account B')
22+
secondary.on('text', async (msg) => {
23+
if (msg.isFromMe) return
24+
await secondary.send(msg.senderId).text('Reply from account B')
1725
})
1826

1927
process.on('SIGINT', async () => {

examples/quickstart-connect.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Connect, scan the QR, and echo incoming text (with quoted-reply lookup).
3+
*
4+
* Run: bun run examples/quickstart-connect.ts
5+
*/
16
import { Client } from '../src/index.js'
27

38
const client = new Client()
@@ -6,7 +11,7 @@ client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
611
client.on('connect', ({ me }) => console.log('Connected as', me.id))
712

813
client.on('text', async (msg) => {
9-
console.log('Received message:', msg.senderId, '|', msg.text)
14+
console.log('Received:', msg.senderId, '|', msg.text)
1015

1116
const quoted = await msg.replied()
1217
if (quoted) console.log('In reply to:', quoted.senderId, '|', quoted.text)

examples/simple-bot.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,39 @@
1+
/**
2+
* Owner-only echo bot: reacts, echoes text, and replies "rich" on demand.
3+
*
4+
* Run: OWNER=6285xxxx bun run examples/simple-bot.ts
5+
*/
16
import { Client } from '../src/index.js'
27

3-
const OWNER = (process.env['OWNER'] ?? '').replace(/\D/g, '')
8+
const digitsOf = (jid: string): string => (jid.split(/[:@]/)[0] ?? '').replace(/\D/g, '')
9+
10+
const OWNER = digitsOf(process.env['OWNER'] ?? '')
411
if (!OWNER) {
5-
console.error('Set OWNER (nomor kamu), e.g. OWNER=6285xxxx bun run examples/simple-bot.ts')
12+
console.error('Set OWNER (your number), e.g. OWNER=6285xxxx bun run examples/simple-bot.ts')
613
process.exit(1)
714
}
8-
const digitsOf = (jid: string): string => (jid.split(/[:@]/)[0] ?? '').replace(/\D/g, '')
915

1016
const client = new Client()
1117

12-
client.on('qr', ({ qrString }) => {
13-
console.log('Scan this QR to authenticate:', qrString)
14-
})
18+
client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
19+
client.on('connect', ({ me }) => console.log('Connected as', me.id))
1520

16-
client.on('connect', ({ me }) => {
17-
console.log('Connected as', me.id)
21+
client.on('disconnect', ({ reason, willReconnect }) => {
22+
console.log('Disconnected:', reason, willReconnect ? '(reconnecting)' : '')
1823
})
1924

20-
client.on('text', async (message) => {
21-
if (digitsOf(message.senderId) !== OWNER) return
25+
client.on('text', async (msg) => {
26+
if (digitsOf(msg.senderId) !== OWNER) return
2227

23-
await message.react('👀')
28+
await msg.react('👀')
2429

25-
if (message.text.trim().toLowerCase() === 'rich') {
26-
await message.reply(
30+
if (msg.text.trim().toLowerCase() === 'rich') {
31+
await msg.reply(
2732
['*Contoh rich reply* ✨', '', '```ts', 'const x = 1', '```', '', ':::suggest', 'Lagi | Tutup', ':::'].join('\n'),
2833
{ rich: true, title: '🤖 zaileys' },
2934
)
3035
return
3136
}
3237

33-
await message.reply(`Echo: ${message.text}`)
34-
})
35-
36-
client.on('disconnect', ({ reason, willReconnect }) => {
37-
console.log('Disconnected:', reason, willReconnect ? '(reconnecting)' : '')
38+
await msg.reply(`Echo: ${msg.text}`)
3839
})

0 commit comments

Comments
 (0)