Whatsapp service updates
This commit is contained in:
parent
e22a8e8d98
commit
3da103c010
16 changed files with 151 additions and 36 deletions
|
|
@ -2,7 +2,7 @@ import type { Metadata } from "next";
|
||||||
import { LicenseInfo } from "@mui/x-license";
|
import { LicenseInfo } from "@mui/x-license";
|
||||||
|
|
||||||
LicenseInfo.setLicenseKey(
|
LicenseInfo.setLicenseKey(
|
||||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
"c787ac6613c5f2aa0494c4285fe3e9f2Tz04OTY1NyxFPTE3NDYzNDE0ODkwMDAsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
||||||
);
|
);
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export const SendMessageRoute = withDefaults({
|
||||||
description: "Send a message",
|
description: "Send a message",
|
||||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
console.log({ payload: request.payload });
|
||||||
const { phoneNumber, message } = request.payload as MessageRequest;
|
const { phoneNumber, message } = request.payload as MessageRequest;
|
||||||
const whatsappService = getService(request);
|
const whatsappService = getService(request);
|
||||||
await whatsappService.send(id, phoneNumber, message as string);
|
await whatsappService.send(id, phoneNumber, message as string);
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,8 @@ export default class WhatsappService extends Service {
|
||||||
if (qr) {
|
if (qr) {
|
||||||
console.log("got qr code");
|
console.log("got qr code");
|
||||||
const botDirectory = this.getBotDirectory(botID);
|
const botDirectory = this.getBotDirectory(botID);
|
||||||
const qrPath = `${botDirectory}/qr.png`;
|
const qrPath = `${botDirectory}/qr.txt`;
|
||||||
fs.writeFileSync(qrPath, qr, "base64");
|
fs.writeFileSync(qrPath, qr, "utf8");
|
||||||
const verifiedFile = `${botDirectory}/verified`;
|
const verifiedFile = `${botDirectory}/verified`;
|
||||||
if (fs.existsSync(verifiedFile)) {
|
if (fs.existsSync(verifiedFile)) {
|
||||||
fs.rmSync(verifiedFile);
|
fs.rmSync(verifiedFile);
|
||||||
|
|
@ -220,13 +220,16 @@ export default class WhatsappService extends Service {
|
||||||
whatsappBotId: botID,
|
whatsappBotId: botID,
|
||||||
};
|
};
|
||||||
|
|
||||||
await fetch(`http://localhost:3000/api/whatsapp/${botID}/receive`, {
|
await fetch(
|
||||||
|
`${process.env.BRIDGE_FRONTEND_URL}/api/whatsapp/bots/${botID}/receive`,
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(receivedMessage),
|
body: JSON.stringify(receivedMessage),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,9 +245,9 @@ export default class WhatsappService extends Service {
|
||||||
|
|
||||||
getBot(botID: string): Record<string, any> {
|
getBot(botID: string): Record<string, any> {
|
||||||
const botDirectory = this.getBotDirectory(botID);
|
const botDirectory = this.getBotDirectory(botID);
|
||||||
const qrPath = `${botDirectory}/qr.png`;
|
const qrPath = `${botDirectory}/qr.txt`;
|
||||||
const verifiedFile = `${botDirectory}/verified`;
|
const verifiedFile = `${botDirectory}/verified`;
|
||||||
const qr = fs.existsSync(qrPath) ? fs.readFileSync(qrPath, "base64") : null;
|
const qr = fs.existsSync(qrPath) ? fs.readFileSync(qrPath, "utf8") : null;
|
||||||
const verified = fs.existsSync(verifiedFile);
|
const verified = fs.existsSync(verifiedFile);
|
||||||
|
|
||||||
return { qr, verified };
|
return { qr, verified };
|
||||||
|
|
@ -276,6 +279,7 @@ export default class WhatsappService extends Service {
|
||||||
_lastReceivedDate: Date,
|
_lastReceivedDate: Date,
|
||||||
): Promise<proto.IWebMessageInfo[]> {
|
): Promise<proto.IWebMessageInfo[]> {
|
||||||
const connection = this.connections[botID]?.socket;
|
const connection = this.connections[botID]?.socket;
|
||||||
|
console.log({ connection });
|
||||||
const messages = await connection.loadAllUnreadMessages();
|
const messages = await connection.loadAllUnreadMessages();
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
// import { db, getWorkerUtils } from "bridge-common";
|
import { db, getWorkerUtils } from "bridge-common";
|
||||||
|
|
||||||
interface ReceiveWhatsappMessageTaskOptions {
|
interface ReceiveWhatsappMessageTaskOptions {
|
||||||
|
token;
|
||||||
message: any;
|
message: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const receiveWhatsappMessageTask = async ({
|
const receiveWhatsappMessageTask = async ({
|
||||||
|
token,
|
||||||
message,
|
message,
|
||||||
}: ReceiveWhatsappMessageTaskOptions): Promise<void> => {};
|
}: ReceiveWhatsappMessageTaskOptions): Promise<void> => {
|
||||||
|
const bot = await db
|
||||||
|
.selectFrom("WhatsappBot")
|
||||||
|
.selectAll()
|
||||||
|
.where((eb) => eb.or([eb("token", "=", token), eb("id", "=", token)]))
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
|
console.log(bot);
|
||||||
|
};
|
||||||
|
|
||||||
export default receiveWhatsappMessageTask;
|
export default receiveWhatsappMessageTask;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,32 @@
|
||||||
// import { db, getWorkerUtils } from "bridge-common";
|
import { db } from "bridge-common";
|
||||||
|
|
||||||
interface SendWhatsappMessageTaskOptions {
|
interface SendWhatsappMessageTaskOptions {
|
||||||
|
token: string;
|
||||||
|
recipient: string;
|
||||||
message: any;
|
message: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendWhatsappMessageTask = async ({
|
const sendWhatsappMessageTask = async ({
|
||||||
message,
|
message,
|
||||||
}: SendWhatsappMessageTaskOptions): Promise<void> => {};
|
recipient,
|
||||||
|
token,
|
||||||
|
}: SendWhatsappMessageTaskOptions): Promise<void> => {
|
||||||
|
const bot = await db
|
||||||
|
.selectFrom("WhatsappBot")
|
||||||
|
.selectAll()
|
||||||
|
.where("token", "=", token)
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
|
||||||
|
const url = `${process.env.BRIDGE_WHATSAPP_URL}/api/bots/${bot.id}/send`;
|
||||||
|
const params = { message, phoneNumber: recipient };
|
||||||
|
console.log({ params });
|
||||||
|
const result = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log({ result });
|
||||||
|
};
|
||||||
|
|
||||||
export default sendWhatsappMessageTask;
|
export default sendWhatsappMessageTask;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import fr from "leafcutter-ui/locales/fr.json";
|
||||||
import { LicenseInfo } from "@mui/x-license";
|
import { LicenseInfo } from "@mui/x-license";
|
||||||
|
|
||||||
LicenseInfo.setLicenseKey(
|
LicenseInfo.setLicenseKey(
|
||||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
"c787ac6613c5f2aa0494c4285fe3e9f2Tz04OTY1NyxFPTE3NDYzNDE0ODkwMDAsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
||||||
);
|
);
|
||||||
|
|
||||||
const messages: any = { en, fr };
|
const messages: any = { en, fr };
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { LicenseInfo } from "@mui/x-license";
|
||||||
import { locales, LeafcutterProvider } from "leafcutter-ui";
|
import { locales, LeafcutterProvider } from "leafcutter-ui";
|
||||||
|
|
||||||
LicenseInfo.setLicenseKey(
|
LicenseInfo.setLicenseKey(
|
||||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
"c787ac6613c5f2aa0494c4285fe3e9f2Tz04OTY1NyxFPTE3NDYzNDE0ODkwMDAsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
||||||
);
|
);
|
||||||
|
|
||||||
export const MultiProvider: FC<PropsWithChildren> = ({ children }) => {
|
export const MultiProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
|
|
||||||
|
|
@ -112,3 +112,16 @@ export const deleteAction = async ({ entity, table, id }: DeleteActionArgs) => {
|
||||||
export const selectAllAction = async (table: keyof Database) => {
|
export const selectAllAction = async (table: keyof Database) => {
|
||||||
return db.selectFrom(table).selectAll().execute();
|
return db.selectFrom(table).selectAll().execute();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SelectOneArgs = {
|
||||||
|
table: keyof Database;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectOneAction = async ({ table, id }: SelectOneArgs) => {
|
||||||
|
return db
|
||||||
|
.selectFrom(table)
|
||||||
|
.selectAll()
|
||||||
|
.where("id", "=", id)
|
||||||
|
.executeTakeFirst();
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
|
||||||
[service]: { entity, table, displayName, displayFields: fields },
|
[service]: { entity, table, displayName, displayFields: fields },
|
||||||
} = serviceConfig;
|
} = serviceConfig;
|
||||||
const id = row.id as string;
|
const id = row.id as string;
|
||||||
|
const token = row.token as string;
|
||||||
const deleteAction = generateDeleteAction({ entity, table });
|
const deleteAction = generateDeleteAction({ entity, table });
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { almostBlack } = colors;
|
const { almostBlack } = colors;
|
||||||
|
|
@ -76,7 +77,9 @@ export const Detail: FC<DetailProps> = ({ service, row }) => {
|
||||||
name={field.name}
|
name={field.name}
|
||||||
label={field.label}
|
label={field.label}
|
||||||
getValue={field.getValue}
|
getValue={field.getValue}
|
||||||
id={row["id"] as string}
|
refreshInterval={field.refreshInterval}
|
||||||
|
token={token}
|
||||||
|
verified={row.verified as boolean}
|
||||||
helperText={field.helperText}
|
helperText={field.helperText}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
export const Home: FC = () => {
|
export const Home: FC = () => {
|
||||||
return <h1>Home</h1>;
|
return (
|
||||||
|
<Box sx={{ p: 3, fontSize: 30, fontWeight: "bold", textAlign: "center" }}>
|
||||||
|
Overview
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ import { colors } from "../styles/theme";
|
||||||
type QRCodeProps = {
|
type QRCodeProps = {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
id: string;
|
token: string;
|
||||||
|
verified: boolean;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
getValue?: (id: string) => Promise<string>;
|
getValue?: (id: string) => Promise<string>;
|
||||||
refreshInterval?: number;
|
refreshInterval?: number;
|
||||||
|
|
@ -15,7 +16,8 @@ type QRCodeProps = {
|
||||||
export const QRCode: FC<QRCodeProps> = ({
|
export const QRCode: FC<QRCodeProps> = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
id,
|
token,
|
||||||
|
verified,
|
||||||
helperText,
|
helperText,
|
||||||
getValue,
|
getValue,
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
|
|
@ -24,19 +26,19 @@ export const QRCode: FC<QRCodeProps> = ({
|
||||||
const { white } = colors;
|
const { white } = colors;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getValue && refreshInterval) {
|
if (!verified && getValue && refreshInterval) {
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
const result = await getValue(id);
|
const result = await getValue(token);
|
||||||
setValue(result);
|
setValue(result);
|
||||||
}, refreshInterval);
|
}, refreshInterval * 1000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}
|
}
|
||||||
}, [getValue, refreshInterval]);
|
}, [getValue, refreshInterval]);
|
||||||
|
|
||||||
return (
|
return !verified ? (
|
||||||
<Box sx={{ backgroundColor: white, m: 2 }}>
|
<Box sx={{ backgroundColor: white, m: 2 }}>
|
||||||
<QRCodeInternal value={value} />
|
<QRCodeInternal value={value} />
|
||||||
<Box>{helperText}</Box>
|
<Box>{helperText}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import { ServiceConfig } from "../lib/service";
|
import { ServiceConfig } from "../lib/service";
|
||||||
|
// import { generateSelectOneAction } from "../lib/actions";
|
||||||
|
|
||||||
const getQRCode = async (id: string) => {
|
const getQRCode = async (token: string) => {
|
||||||
console.log("Getting QR code");
|
const url = `/api/whatsapp/bots/${token}`;
|
||||||
return "xya"; // "2hVSc9OT18wbo60WLKlVrd5KqQqYZWdH+kVlRYlrnZcKbjbzwcL4ybkS1/jGaN5bLafX9ZaR829xyhQ=";
|
const result = await fetch(url, { cache: "no-store" });
|
||||||
|
const { qr } = await result.json();
|
||||||
|
|
||||||
|
return qr ?? "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const whatsappConfig: ServiceConfig = {
|
export const whatsappConfig: ServiceConfig = {
|
||||||
|
|
@ -71,7 +75,7 @@ export const whatsappConfig: ServiceConfig = {
|
||||||
size: 4,
|
size: 4,
|
||||||
getValue: getQRCode,
|
getValue: getQRCode,
|
||||||
helperText: "Go ahead, scan it",
|
helperText: "Go ahead, scan it",
|
||||||
refreshInterval: 5,
|
refreshInterval: 15,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
listColumns: [
|
listColumns: [
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
updateAction,
|
updateAction,
|
||||||
deleteAction,
|
deleteAction,
|
||||||
selectAllAction,
|
selectAllAction,
|
||||||
|
selectOneAction,
|
||||||
} from "../actions/service";
|
} from "../actions/service";
|
||||||
import { FieldDescription, Entity } from "./service";
|
import { FieldDescription, Entity } from "./service";
|
||||||
|
|
||||||
|
|
@ -70,3 +71,14 @@ export function generateSelectAllAction(table: keyof Database) {
|
||||||
return selectAllAction(table);
|
return selectAllAction(table);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GenerateSelectOneArgs = {
|
||||||
|
table: keyof Database;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function generateSelectOneAction({ table, id }: GenerateSelectOneArgs) {
|
||||||
|
return async () => {
|
||||||
|
return selectOneAction({ table, id });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export type FieldDescription = {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
kind?: "text" | "phone" | "select" | "multi" | "qrcode";
|
kind?: "text" | "phone" | "select" | "multi" | "qrcode";
|
||||||
getValue?: (id: string) => Promise<string>;
|
getValue?: (token: string) => Promise<string>;
|
||||||
refreshInterval?: number;
|
refreshInterval?: number;
|
||||||
getOptions?: (formState: any) => Promise<SelectOption[]>;
|
getOptions?: (formState: any) => Promise<SelectOption[]>;
|
||||||
autogenerated?: "token";
|
autogenerated?: "token";
|
||||||
|
|
@ -70,15 +70,21 @@ export class Service {
|
||||||
return NextResponse.json(row);
|
return NextResponse.json(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registerBot({
|
||||||
|
params: { service, token },
|
||||||
|
}: ServiceParams): Promise<NextResponse> {
|
||||||
|
return NextResponse.error() as any;
|
||||||
|
}
|
||||||
|
|
||||||
async sendMessage(
|
async sendMessage(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params: { service, token } }: ServiceParams,
|
{ params: { service, token } }: ServiceParams,
|
||||||
): Promise<NextResponse> {
|
): Promise<NextResponse> {
|
||||||
const message = await req.json();
|
const json = await req.json();
|
||||||
const worker = await getWorkerUtils();
|
const worker = await getWorkerUtils();
|
||||||
await worker.addJob(`${service}/send-${service}-message`, {
|
await worker.addJob(`${service}/send-${service}-message`, {
|
||||||
token,
|
token,
|
||||||
message,
|
...json,
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({ response: "ok" });
|
return NextResponse.json({ response: "ok" });
|
||||||
|
|
@ -88,7 +94,9 @@ export class Service {
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params: { service, token } }: ServiceParams,
|
{ params: { service, token } }: ServiceParams,
|
||||||
): Promise<NextResponse> {
|
): Promise<NextResponse> {
|
||||||
|
console.log("INTO receiveMessage");
|
||||||
const message = await req.json();
|
const message = await req.json();
|
||||||
|
console.log({ message });
|
||||||
const worker = await getWorkerUtils();
|
const worker = await getWorkerUtils();
|
||||||
await worker.addJob(`${service}/receive-${service}-message`, {
|
await worker.addJob(`${service}/receive-${service}-message`, {
|
||||||
token,
|
token,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,35 @@
|
||||||
import { Service } from "./service";
|
import { NextResponse } from "next/server";
|
||||||
|
import { db } from "bridge-common";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { Service, ServiceParams } from "./service";
|
||||||
|
|
||||||
export class Whatsapp extends Service {}
|
export class Whatsapp extends Service {
|
||||||
|
async getBot({ params: { token } }: ServiceParams) {
|
||||||
|
const row = await db
|
||||||
|
.selectFrom("WhatsappBot")
|
||||||
|
.selectAll()
|
||||||
|
.where("token", "=", token as string)
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
const id = row.id;
|
||||||
|
const url = `${process.env.BRIDGE_WHATSAPP_URL}/api/bots/${id}`;
|
||||||
|
const result = await fetch(url, { cache: "no-store" });
|
||||||
|
console.log({ result1: result });
|
||||||
|
const json = await result.json();
|
||||||
|
|
||||||
|
await db
|
||||||
|
.updateTable("WhatsappBot")
|
||||||
|
.set({ verified: json.verified })
|
||||||
|
.where("id", "=", id)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
revalidatePath(`/whatsapp/${id}`);
|
||||||
|
|
||||||
|
if (!json.verified) {
|
||||||
|
const url = `${process.env.BRIDGE_WHATSAPP_URL}/api/bots/${id}/register`;
|
||||||
|
const result = await fetch(url, { method: "POST", cache: "no-store" });
|
||||||
|
console.log({ result2: result });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { LicenseInfo } from "@mui/x-license";
|
import { LicenseInfo } from "@mui/x-license";
|
||||||
|
|
||||||
LicenseInfo.setLicenseKey(
|
LicenseInfo.setLicenseKey(
|
||||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
"c787ac6613c5f2aa0494c4285fe3e9f2Tz04OTY1NyxFPTE3NDYzNDE0ODkwMDAsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
||||||
);
|
);
|
||||||
|
|
||||||
export { List } from "./components/List";
|
export { List } from "./components/List";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue