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