import { Body, Controller, Post, Res, HttpStatus } from '@nestjs/common'; import { WhatsappService } from '../whatsapp/whatsapp.service'; import { formatJid } from '../common/utils'; class TextMessageBody { target!: string; message!: string; } class MediaMessageBody { target!: string; url!: string; caption?: string; filename?: string; mimetype?: string; } class PollMessageBody { target!: string; question!: string; options!: string[]; multiSelect?: boolean; } @Controller('send') export class MessageController { constructor(private readonly whatsappService: WhatsappService) {} private handleRouteError(err: unknown, res: any): void { const message = err instanceof Error ? err.message : 'Unknown error'; const statusCode = message.includes('belum terhubung') ? HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.INTERNAL_SERVER_ERROR; console.error(`Error Pengiriman: ${message}`); res.status(statusCode).json({ status: 'Error', message }); } @Post('/') async sendText(@Body() body: TextMessageBody, @Res() res: any) { const { target, message } = body; if (!target || !message) { return res.status(HttpStatus.BAD_REQUEST).json({ status: 'Error', message: 'Field "target" dan "message" wajib diisi.', }); } try { const sock = this.whatsappService.getSocket(); const jid = formatJid(target); if (jid.endsWith('@g.us')) { await this.whatsappService.groupCacheReady; } // ⚠️ PERHATIAN: Tidak Boleh menggunakan withTimeout di sini. // Jika disinkron ke 1000 anggota pertama kalinya dan butuh waktu 2 menit, // kita harus rela menunggu janji Baileys terpenuhi. // Terminate paksa promise ini hanya akan membuat mutex Baileys hang seumur hidup. await sock.sendMessage(jid, { text: message }); console.log(`📤 [TEXT] → ${jid} (Sukses)`); res .status(HttpStatus.OK) .json({ status: 'Success', type: 'text', to: jid }); } catch (err) { this.handleRouteError(err, res); } } @Post('/image') async sendImage(@Body() body: MediaMessageBody, @Res() res: any) { const { target, url, caption } = body; if (!target || !url) { return res .status(HttpStatus.BAD_REQUEST) .json({ status: 'Error', message: 'target & url wajib.' }); } try { const sock = this.whatsappService.getSocket(); const jid = formatJid(target); if (jid.endsWith('@g.us')) await this.whatsappService.groupCacheReady; await sock.sendMessage(jid, { image: { url }, caption: caption ?? '' }); console.log(`📤 [IMAGE] → ${jid}`); res .status(HttpStatus.OK) .json({ status: 'Success', type: 'image', to: jid }); } catch (err) { this.handleRouteError(err, res); } } @Post('/video') async sendVideo(@Body() body: MediaMessageBody, @Res() res: any) { const { target, url, caption } = body; if (!target || !url) { return res .status(HttpStatus.BAD_REQUEST) .json({ status: 'Error', message: 'target & url wajib.' }); } try { const sock = this.whatsappService.getSocket(); const jid = formatJid(target); if (jid.endsWith('@g.us')) await this.whatsappService.groupCacheReady; await sock.sendMessage(jid, { video: { url }, caption: caption ?? '' }); console.log(`📤 [VIDEO] → ${jid}`); res .status(HttpStatus.OK) .json({ status: 'Success', type: 'video', to: jid }); } catch (err) { this.handleRouteError(err, res); } } @Post('/document') async sendDocument(@Body() body: MediaMessageBody, @Res() res: any) { const { target, url, filename, mimetype } = body; if (!target || !url || !filename) { return res .status(HttpStatus.BAD_REQUEST) .json({ status: 'Error', message: 'target, url & filename wajib.' }); } try { const sock = this.whatsappService.getSocket(); const jid = formatJid(target); if (jid.endsWith('@g.us')) await this.whatsappService.groupCacheReady; await sock.sendMessage(jid, { document: { url }, fileName: filename, mimetype: mimetype ?? 'application/octet-stream', }); console.log(`📤 [DOC] → ${jid}`); res .status(HttpStatus.OK) .json({ status: 'Success', type: 'document', to: jid }); } catch (err) { this.handleRouteError(err, res); } } @Post('/poll') async sendPoll(@Body() body: PollMessageBody, @Res() res: any) { const { target, question, options, multiSelect } = body; if (!target || !question || !options || options.length < 2) { return res .status(HttpStatus.BAD_REQUEST) .json({ status: 'Error', message: 'Opsi poll minimal 2' }); } try { const sock = this.whatsappService.getSocket(); const jid = formatJid(target); if (jid.endsWith('@g.us')) await this.whatsappService.groupCacheReady; await sock.sendMessage(jid, { poll: { name: question, values: options, selectableCount: multiSelect ? options.length : 1, }, }); console.log(`📤 [POLL] → ${jid}`); res .status(HttpStatus.OK) .json({ status: 'Success', type: 'poll', to: jid }); } catch (err) { this.handleRouteError(err, res); } } }