tambahannya

This commit is contained in:
bipproduction
2025-10-23 12:03:47 +08:00
parent a97c0362bd
commit 367759c4cc

View File

@@ -3,48 +3,71 @@ import { prisma } from "../lib/prisma";
import type { WAHookMessage } from "types/wa_messages"; import type { WAHookMessage } from "types/wa_messages";
import _ from "lodash"; import _ from "lodash";
import { logger } from "../lib/logger"; import { logger } from "../lib/logger";
import {
WhatsAppClient,
import { WhatsAppClient, WhatsAppMessageType } from 'whatsapp-client-sdk'; WhatsAppMessageType,
type ProcessedIncomingMessage,
} from "whatsapp-client-sdk";
const client = new WhatsAppClient({ const client = new WhatsAppClient({
accessToken: process.env.WA_TOKEN!, accessToken: process.env.WA_TOKEN!,
phoneNumberId: process.env.WA_PHONE_NUMBER_ID!, phoneNumberId: process.env.WA_PHONE_NUMBER_ID!,
webhookVerifyToken: process.env.WA_WEBHOOK_TOKEN! webhookVerifyToken: process.env.WA_WEBHOOK_TOKEN!,
}); });
async function fetchWithTimeout(
async function fetchWithTimeout(input: RequestInfo, init: RequestInit, timeoutMs = 120_000) { input: RequestInfo,
const controller = new AbortController() init: RequestInit,
const id = setTimeout(() => controller.abort(), timeoutMs) timeoutMs = 120_000
) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeoutMs);
try { try {
return await fetch(input, { ...init, signal: controller.signal }) return await fetch(input, { ...init, signal: controller.signal });
} finally { } finally {
clearTimeout(id) clearTimeout(id);
} }
} }
async function flowAi({ question, name, number }: { question: string, name: string, number: string }) { const FLOW_ID = "1";
async function flowAi({
message,
question,
name,
number,
}: {
message: ProcessedIncomingMessage;
question: string;
name: string;
number: string;
}) {
const flow = await prisma.chatFlows.findUnique({ const flow = await prisma.chatFlows.findUnique({
where: { where: { id: FLOW_ID },
id: "1", });
},
})
if (!flow) { if (!flow) {
logger.info("[POST] no flow found") logger.info("[POST] no flow found");
return;
} }
if (flow?.defaultFlow && flow.active) { if (flow.defaultFlow && flow.active) {
const { flowUrl, flowToken } = flow logger.info("[POST] flow found");
const response = await fetchWithTimeout(`${flowUrl}/prediction/${flow.defaultFlow}`, {
await client.markMessageAsRead(message.id);
await client.sendTypingIndicator(message.from);
const { flowUrl, flowToken } = flow;
try {
const response = await fetchWithTimeout(
`${flowUrl}/prediction/${flow.defaultFlow}`,
{
headers: { headers: {
Authorization: `Bearer ${flowToken}`, Authorization: `Bearer ${flowToken}`,
'Content-Type': 'application/json', "Content-Type": "application/json",
}, },
method: 'POST', method: "POST",
body: JSON.stringify({ body: JSON.stringify({
question, question,
overrideConfig: { overrideConfig: {
@@ -52,12 +75,14 @@ async function flowAi({ question, name, number }: { question: string, name: stri
vars: { userName: _.kebabCase(name), userPhone: number }, vars: { userName: _.kebabCase(name), userPhone: number },
}, },
}), }),
}) }
);
const responseText = await response.text();
const responseText = await response.text()
try { try {
const result = JSON.parse(responseText) const result = JSON.parse(responseText);
const create = await prisma.waHook.create({ await prisma.waHook.create({
data: { data: {
data: JSON.stringify({ data: JSON.stringify({
question, question,
@@ -69,16 +94,17 @@ async function flowAi({ question, name, number }: { question: string, name: stri
}, },
}); });
if (flow?.waPhoneNumberId && flow?.waToken && flow.active) { if (flow.waPhoneNumberId && flow.waToken && flow.active) {
client.sendText(number, result.text) await client.sendText(number, result.text);
} }
} catch (error) { } catch (error) {
console.log(error) logger.error(`[POST] Error parsing AI response ${error}`);
console.log(responseText) logger.error(responseText);
}
} catch (error) {
logger.error(`[POST] Error calling flow API ${error}`);
} }
} }
} }
const WaHookRoute = new Elysia({ const WaHookRoute = new Elysia({
@@ -86,20 +112,18 @@ const WaHookRoute = new Elysia({
tags: ["WhatsApp Hook"], tags: ["WhatsApp Hook"],
}) })
// ✅ Handle verifikasi Webhook (GET) // ✅ Handle verifikasi Webhook (GET)
.get("/hook", async (ctx) => { .get(
"/hook",
async (ctx) => {
const { query, set } = ctx; const { query, set } = ctx;
const mode = query["hub.mode"]; const mode = query["hub.mode"];
const challenge = query["hub.challenge"]; const challenge = query["hub.challenge"];
const verifyToken = query["hub.verify_token"]; const verifyToken = query["hub.verify_token"];
const getToken = await prisma.apiKey.findUnique({ const getToken = await prisma.apiKey.findUnique({
where: { where: { key: verifyToken },
key: verifyToken,
}
}); });
console.log(getToken);
if (!getToken) { if (!getToken) {
set.status = 403; set.status = 403;
return "Verification failed [ERR01]"; return "Verification failed [ERR01]";
@@ -112,74 +136,85 @@ const WaHookRoute = new Elysia({
set.status = 403; set.status = 403;
return "Verification failed [ERR02]"; return "Verification failed [ERR02]";
}, { },
{
query: t.Object({ query: t.Object({
["hub.mode"]: t.Optional(t.String()), ["hub.mode"]: t.Optional(t.String()),
["hub.verify_token"]: t.Optional(t.String()), ["hub.verify_token"]: t.Optional(t.String()),
["hub.challenge"]: t.Optional(t.String()) ["hub.challenge"]: t.Optional(t.String()),
}), }),
detail: { detail: {
summary: "Webhook Verification", summary: "Webhook Verification",
description: "Verifikasi dari WhatsApp API", description: "Verifikasi dari WhatsApp API",
},
} }
}) )
// ✅ Handle incoming message (POST) // ✅ Handle incoming message (POST)
.post("/hook", async ({ body }) => { .post(
"/hook",
const webhook = client.parseWebhook(body) async ({ body }) => {
const webhook = client.parseWebhook(body);
if (webhook[0]?.type === WhatsAppMessageType.TEXT) { if (webhook[0]?.type === WhatsAppMessageType.TEXT) {
const message = webhook[0]?.text const messageQuestion = webhook[0]?.text;
const from = webhook[0]?.from const from = webhook[0]?.from;
const name = webhook[0].contact?.name const name = webhook[0].contact?.name;
if (message && from) { if (messageQuestion && from) {
logger.info(`[POST] Message: ${JSON.stringify({ logger.info(
message, `[POST] Message: ${JSON.stringify({ message: messageQuestion, from, name })}`
from, );
name // gunakan void agar tidak ada warning “unawaited promise”
})}`) void flowAi({
flowAi({ message: webhook[0],
question: message, question: messageQuestion,
name: name || "default_name", name: name || "default_name",
number: from, number: from,
}) });
} }
} }
return { return {
success: true, success: true,
message: "WhatsApp Hook received" message: "WhatsApp Hook received",
}; };
}, { },
{
body: t.Any(), body: t.Any(),
detail: { detail: {
summary: "Receive WhatsApp Messages", summary: "Receive WhatsApp Messages",
description: "Menerima pesan dari WhatsApp Webhook" description: "Menerima pesan dari WhatsApp Webhook",
}
})
.get("/list", async ({ query }) => {
const list = await prisma.waHook.findMany({
take: query.limit,
skip: ((query.page || 1) - 1) * (query.limit || 10),
orderBy: {
createdAt: "desc",
}, },
}
)
// ✅ List WhatsApp Hook
.get(
"/list",
async ({ query }) => {
const limit = query.limit ?? 10;
const page = query.page ?? 1;
const list = await prisma.waHook.findMany({
take: limit,
skip: (page - 1) * limit,
orderBy: { createdAt: "desc" },
}); });
const count = await prisma.waHook.count() const count = await prisma.waHook.count();
const result = list.map((item) => ({ const result = list.map((item) => ({
id: item.id, id: item.id,
data: item.data as WAHookMessage, data: item.data as WAHookMessage,
createdAt: item.createdAt, createdAt: item.createdAt,
})) }));
return { return {
list: result, list: result,
count: Math.ceil(count / (query.limit || 10)), count: Math.ceil(count / limit),
}; };
}, { },
{
query: t.Object({ query: t.Object({
page: t.Optional(t.Number({ minimum: 1, default: 1 })), page: t.Optional(t.Number({ minimum: 1, default: 1 })),
limit: t.Optional(t.Number({ minimum: 1, maximum: 100, default: 10 })), limit: t.Optional(t.Number({ minimum: 1, maximum: 100, default: 10 })),
@@ -187,19 +222,26 @@ const WaHookRoute = new Elysia({
detail: { detail: {
summary: "List WhatsApp Hook", summary: "List WhatsApp Hook",
description: "List semua WhatsApp Hook", description: "List semua WhatsApp Hook",
},
} }
}) )
.post("/reset", async () => {
await prisma.waHook.deleteMany() // ✅ Reset WhatsApp Hook
.post(
"/reset",
async () => {
await prisma.waHook.deleteMany();
return { return {
success: true, success: true,
message: "WhatsApp Hook reset" message: "WhatsApp Hook reset",
}; };
}, { },
{
detail: { detail: {
summary: "Reset WhatsApp Hook", summary: "Reset WhatsApp Hook",
description: "Reset semua WhatsApp Hook", description: "Reset semua WhatsApp Hook",
},
} }
}); );
export default WaHookRoute; export default WaHookRoute;