Initial commit

This commit is contained in:
2026-05-12 16:16:49 +07:00
commit ac2cfca335
32 changed files with 6613 additions and 0 deletions

86
src/lib/whatsapp.ts Normal file
View File

@ -0,0 +1,86 @@
import { appendLog, readJsonFile, writeJsonFile } from './storage'
type FlowState = Record<string, { step: string; name?: string }>
function normalizeNumber(number?: string | null) {
return (number || '').replace(/\D+/g, '')
}
export async function sendTextMessage(to: string, message: string) {
const apiVersion = process.env.WHATSAPP_API_VERSION || 'v23.0'
const phoneNumberId = process.env.WHATSAPP_PHONE_NUMBER_ID || ''
const accessToken = process.env.WHATSAPP_ACCESS_TOKEN || ''
const url = `https://graph.facebook.com/${apiVersion}/${phoneNumberId}/messages`
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messaging_product: 'whatsapp',
to,
type: 'text',
text: { preview_url: false, body: message },
}),
})
const payload = await response.json().catch(async () => ({ raw: await response.text() }))
appendLog({ type: 'outgoing_message', to, message, response: payload, success: response.ok, created_at: new Date().toISOString() })
if (!response.ok) {
throw new Error('Gagal kirim pesan ke WhatsApp')
}
return payload
}
export async function handleWebhook(payload: any) {
appendLog({ type: 'webhook_received', payload, created_at: new Date().toISOString() })
for (const entry of payload.entry || []) {
for (const change of entry.changes || []) {
const value = change.value || {}
for (const message of value.messages || []) {
await handleIncomingMessage(message)
}
for (const status of value.statuses || []) {
appendLog({ type: 'message_status', status, created_at: new Date().toISOString() })
}
}
}
}
async function handleIncomingMessage(message: any) {
const from = message.from || ''
appendLog({ type: 'incoming_message', from, message, created_at: new Date().toISOString() })
if (normalizeNumber(from) !== normalizeNumber(process.env.WA_TEST_NUMBER)) {
appendLog({ type: 'ignored_message', reason: 'sender_not_test_number', from, created_at: new Date().toISOString() })
return
}
const body = (message.text?.body || '').trim()
const state = readJsonFile<FlowState>('flow-state.json', {})
const step = state[from]?.step || 'idle'
if (body.toLowerCase() === 'daftar' && step === 'idle') {
state[from] = { step: 'waiting_name' }
writeJsonFile('flow-state.json', state)
await sendTextMessage(from, 'Silakan kirim nama Anda.')
return
}
if (step === 'waiting_name' && body) {
state[from] = { step: 'completed', name: body }
writeJsonFile('flow-state.json', state)
await sendTextMessage(from, `Terima kasih ${body}, data testing diterima.`)
return
}
await sendTextMessage(from, 'Kirim "daftar" untuk mulai flow testing.')
}