Bridge whatsapp simplification

This commit is contained in:
Darren Clarke 2024-05-15 14:39:33 +02:00
parent 6305a8b0bc
commit f6dc60eb08
8 changed files with 142 additions and 333 deletions

View file

@ -1,6 +1,5 @@
import { Server } from "@hapi/hapi";
import { Service } from "@hapipal/schmervice";
import { db, WhatsappBot } from "bridge-common";
import makeWASocket, {
DisconnectReason,
proto,
@ -29,8 +28,12 @@ export default class WhatsappService extends Service {
super(server, options);
}
getAuthDirectory(bot: WhatsappBot): string {
return `/baileys/${bot.id}`;
getBotDirectory(id: string): string {
return `/baileys/${id}`;
}
getAuthDirectory(id: string): string {
return `${this.getBotDirectory(id)}/auth`;
}
async initialize(): Promise<void> {
@ -42,7 +45,6 @@ export default class WhatsappService extends Service {
}
private async sleep(ms: number): Promise<void> {
console.log(`pausing ${ms}`);
return new Promise((resolve) => setTimeout(resolve, ms));
}
@ -58,13 +60,13 @@ export default class WhatsappService extends Service {
}
private async createConnection(
bot: WhatsappBot,
botID: string,
server: Server,
options: any,
authCompleteCallback?: any,
) {
const directory = this.getAuthDirectory(bot);
const { state, saveCreds } = await useMultiFileAuthState(directory);
const authDirectory = this.getAuthDirectory(botID);
const { state, saveCreds } = await useMultiFileAuthState(authDirectory);
const msgRetryCounterMap: any = {};
const socket = makeWASocket({
...options,
@ -86,23 +88,18 @@ export default class WhatsappService extends Service {
} = update;
if (qr) {
console.log("got qr code");
await db
.updateTable("WhatsappBot")
.set({
qrCode: qr,
verified: false,
})
.where("id", "=", bot.id)
.executeTakeFirst();
const botDirectory = this.getBotDirectory(botID);
const qrPath = `${botDirectory}/qr.png`;
fs.writeFileSync(qrPath, qr, "base64");
const verifiedFile = `${botDirectory}/verified`;
if (fs.existsSync(verifiedFile)) {
fs.rmSync(verifiedFile);
}
} else if (isNewLogin) {
console.log("got new login");
await db
.updateTable("WhatsappBot")
.set({
verified: true,
})
.where("id", "=", bot.id)
.executeTakeFirst();
const botDirectory = this.getBotDirectory(botID);
const verifiedFile = `${botDirectory}/verified`;
fs.writeFileSync(verifiedFile, "");
} else if (connectionState === "open") {
console.log("opened connection");
} else if (connectionState === "close") {
@ -112,18 +109,13 @@ export default class WhatsappService extends Service {
if (disconnectStatusCode === DisconnectReason.restartRequired) {
console.log("reconnecting after got new login");
const updatedBot = await db
.selectFrom("WhatsappBot")
.selectAll()
.where("id", "=", bot.id)
.executeTakeFirstOrThrow();
await this.createConnection(updatedBot, server, options);
await this.createConnection(botID, server, options);
authCompleteCallback?.();
} else if (disconnectStatusCode !== DisconnectReason.loggedOut) {
console.log("reconnecting");
await this.sleep(pause);
pause *= 2;
this.createConnection(bot, server, options);
this.createConnection(botID, server, options);
}
}
}
@ -138,25 +130,29 @@ export default class WhatsappService extends Service {
const upsert = events["messages.upsert"];
const { messages } = upsert;
if (messages) {
await this.queueUnreadMessages(bot, messages);
await this.queueUnreadMessages(botID, messages);
}
}
});
this.connections[bot.id] = { socket, msgRetryCounterMap };
this.connections[botID] = { socket, msgRetryCounterMap };
}
private async updateConnections() {
this.resetConnections();
const bots = await db.selectFrom("WhatsappBot").selectAll().execute();
for await (const bot of bots) {
if (bot.verified) {
const botIDs = fs.readdirSync("/baileys");
console.log({ botIDs });
for await (const botID of botIDs) {
const directory = this.getBotDirectory(botID);
const verifiedFile = `${directory}/verified`;
if (fs.existsSync(verifiedFile)) {
const { version, isLatest } = await fetchLatestBaileysVersion();
console.log(`using WA v${version.join(".")}, isLatest: ${isLatest}`);
await this.createConnection(bot, this.server, {
await this.createConnection(botID, this.server, {
browser: WhatsappService.browserDescription,
printQRInTerminal: false,
printQRInTerminal: true,
version,
});
}
@ -164,7 +160,7 @@ export default class WhatsappService extends Service {
}
private async queueMessage(
bot: WhatsappBot,
botID: string,
webMessageInfo: proto.IWebMessageInfo,
) {
const {
@ -221,127 +217,67 @@ export default class WhatsappService extends Service {
attachment,
filename,
mimetype,
whatsappBotId: bot.id,
botPhoneNumber: bot.phoneNumber,
whatsappBotId: botID,
};
// switch to send to bridge-frontend
// workerUtils.addJob("whatsapp-message", receivedMessage, {
// jobKey: id,
// });
await fetch(`http://localhost:3000/api/whatsapp/${botID}/receive`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(receivedMessage),
});
}
}
}
private async queueUnreadMessages(
bot: WhatsappBot,
botID: string,
messages: proto.IWebMessageInfo[],
) {
for await (const message of messages) {
await this.queueMessage(bot, message);
await this.queueMessage(botID, message);
}
}
async create(
phoneNumber: string,
description: string,
email: string,
): Promise<WhatsappBot> {
const user = await db
.selectFrom("User")
.selectAll()
.where("email", "=", email)
.executeTakeFirstOrThrow();
const row = await db
.insertInto("WhatsappBot")
.values({
phoneNumber,
description,
userId: user.id,
})
.returningAll()
.executeTakeFirst();
getBot(botID: string): Record<string, any> {
const botDirectory = this.getBotDirectory(botID);
const qrPath = `${botDirectory}/qr.png`;
const verifiedFile = `${botDirectory}/verified`;
const qr = fs.existsSync(qrPath) ? fs.readFileSync(qrPath, "base64") : null;
const verified = fs.existsSync(verifiedFile);
return row;
return { qr, verified };
}
async unverify(bot: WhatsappBot): Promise<WhatsappBot> {
const directory = this.getAuthDirectory(bot);
fs.rmSync(directory, { recursive: true, force: true });
return db
.updateTable("WhatsappBot")
.set({ verified: false })
.where("id", "=", bot.id)
.returningAll()
.executeTakeFirst();
async unverify(botID: string): Promise<void> {
const botDirectory = this.getBotDirectory(botID);
fs.rmSync(botDirectory, { recursive: true, force: true });
}
async remove(bot: WhatsappBot): Promise<number> {
const directory = this.getAuthDirectory(bot);
fs.rmSync(directory, { recursive: true, force: true });
const result = await db
.deleteFrom("WhatsappBot")
.where("id", "=", bot.id)
.execute();
return result.length;
}
async findAll(): Promise<WhatsappBot[]> {
return db.selectFrom("WhatsappBot").selectAll().execute();
}
async findById(id: string): Promise<WhatsappBot> {
return db
.selectFrom("WhatsappBot")
.selectAll()
.where("id", "=", id)
.executeTakeFirstOrThrow();
}
async findByToken(token: string): Promise<WhatsappBot> {
return db
.selectFrom("WhatsappBot")
.selectAll()
.where("token", "=", token)
.executeTakeFirstOrThrow();
}
async register(
bot: WhatsappBot,
callback: AuthCompleteCallback,
): Promise<void> {
async register(botID: string, callback: AuthCompleteCallback): Promise<void> {
const { version } = await fetchLatestBaileysVersion();
await this.createConnection(bot, this.server, { version }, callback);
await this.createConnection(botID, this.server, { version }, callback);
callback();
}
async send(
bot: WhatsappBot,
botID: string,
phoneNumber: string,
message: string,
): Promise<void> {
const connection = this.connections[bot.id]?.socket;
const connection = this.connections[botID]?.socket;
const recipient = `${phoneNumber.replace(/\D+/g, "")}@s.whatsapp.net`;
await connection.sendMessage(recipient, { text: message });
}
async receiveSince(bot: WhatsappBot, lastReceivedDate: Date): Promise<void> {
const connection = this.connections[bot.id]?.socket;
const messages = await connection.messagesReceivedAfter(
lastReceivedDate,
false,
);
for (const message of messages) {
this.queueMessage(bot, message);
}
}
async receive(
bot: WhatsappBot,
botID: string,
_lastReceivedDate: Date,
): Promise<proto.IWebMessageInfo[]> {
const connection = this.connections[bot.id]?.socket;
const connection = this.connections[botID]?.socket;
const messages = await connection.loadAllUnreadMessages();
return messages;
}
}