initial commit
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
import type { BridgeRequest } from "../shared/protocol";
|
||||
import {
|
||||
serialize,
|
||||
serializeChat,
|
||||
serializeContact,
|
||||
serializeGroupMetadata,
|
||||
serializeMessage,
|
||||
} from "./serializer";
|
||||
|
||||
const WS_URL = "ws://127.0.0.1:8787";
|
||||
|
||||
let socket: WebSocket | undefined;
|
||||
|
||||
function send(message: unknown) {
|
||||
if (socket?.readyState !== WebSocket.OPEN) {
|
||||
throw new Error("WebSocket is not connected");
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
function reply(id: string, result: unknown) {
|
||||
send({
|
||||
type: "response",
|
||||
id,
|
||||
ok: true,
|
||||
result: serialize(result),
|
||||
});
|
||||
}
|
||||
|
||||
function error(id: string, err: unknown) {
|
||||
send({
|
||||
type: "response",
|
||||
id,
|
||||
ok: false,
|
||||
error: {
|
||||
code: "ERROR",
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRequest(request: BridgeRequest) {
|
||||
switch (request.method) {
|
||||
case "status":
|
||||
return {
|
||||
authenticated: WPP.conn.isAuthenticated(),
|
||||
ready: WPP.isReady,
|
||||
};
|
||||
|
||||
case "listChats": {
|
||||
const chats = await WPP.chat.list({
|
||||
...(request.params?.limit != null
|
||||
? { count: request.params.limit }
|
||||
: {}),
|
||||
...(request.params?.groupsOnly ? { onlyGroups: true } : {}),
|
||||
...(request.params?.usersOnly ? { onlyUsers: true } : {}),
|
||||
});
|
||||
|
||||
return chats.filter(Boolean).map(serializeChat);
|
||||
}
|
||||
|
||||
case "listContacts": {
|
||||
const contacts = await WPP.contact.getAllContacts();
|
||||
|
||||
const result = request.params?.limit
|
||||
? contacts.slice(0, request.params.limit)
|
||||
: contacts;
|
||||
|
||||
return result.filter(Boolean).map(serializeContact);
|
||||
}
|
||||
|
||||
case "listGroups": {
|
||||
const groups = await WPP.group.getAllGroups();
|
||||
|
||||
return groups.filter(Boolean).map(serializeGroupMetadata);
|
||||
}
|
||||
|
||||
case "getMessages": {
|
||||
const messages = await WPP.chat.getMessages(request.params.chatId, {
|
||||
count: request.params.count ?? 50,
|
||||
direction: request.params.direction,
|
||||
id: request.params.id,
|
||||
});
|
||||
|
||||
return messages.filter(Boolean).map(serializeMessage);
|
||||
}
|
||||
|
||||
case "searchMessages":
|
||||
return searchMessages(
|
||||
request.params.query,
|
||||
request.params.chatId,
|
||||
request.params.limit ?? 100,
|
||||
);
|
||||
|
||||
case "sendText": {
|
||||
const result = await WPP.chat.sendTextMessage(
|
||||
request.params.chatId,
|
||||
request.params.text,
|
||||
{
|
||||
delay: request.params.options?.delay,
|
||||
quotedMsg: request.params.options?.quotedMsg,
|
||||
mentionedList: request.params.options?.mentionedList,
|
||||
waitForAck: request.params.options?.waitForAck,
|
||||
},
|
||||
);
|
||||
|
||||
return serialize(result);
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown method: ${(request as any).method}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WA-JS doesn't need a separate "global search" API for our
|
||||
* first implementation. Search the messages loaded from chats.
|
||||
*
|
||||
* Later we can optimize this using WhatsApp's internal message
|
||||
* store/index if necessary.
|
||||
*/
|
||||
async function searchMessages(query: string, chatId?: string, limit = 100) {
|
||||
const normalized = query.toLowerCase();
|
||||
|
||||
let chats;
|
||||
|
||||
if (chatId) {
|
||||
const chat = WPP.chat.get(chatId);
|
||||
chats = chat ? [chat] : [];
|
||||
} else {
|
||||
chats = await WPP.chat.list();
|
||||
}
|
||||
|
||||
const results: any[] = [];
|
||||
|
||||
for (const chat of chats) {
|
||||
if (results.length >= limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
const id = chat?.id?._serialized ?? chat?.id ?? chat?.wid?._serialized;
|
||||
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const messages = await WPP.chat.getMessages(id, {
|
||||
count: 100,
|
||||
});
|
||||
|
||||
for (const message of messages) {
|
||||
const body = String(message.body ?? message.caption ?? "");
|
||||
|
||||
if (body.toLowerCase().includes(normalized)) {
|
||||
results.push({
|
||||
chatId: id,
|
||||
message: serialize(message),
|
||||
});
|
||||
|
||||
if (results.length >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Failed searching chat", id, err);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (
|
||||
socket &&
|
||||
(socket.readyState === WebSocket.OPEN ||
|
||||
socket.readyState === WebSocket.CONNECTING)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket = new WebSocket(WS_URL);
|
||||
|
||||
socket.onopen = () => {
|
||||
console.log("[WA Bridge] connected");
|
||||
|
||||
send({
|
||||
type: "event",
|
||||
event: "ready",
|
||||
data: {
|
||||
authenticated: WPP.conn.isAuthenticated(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
socket.onmessage = async (event) => {
|
||||
try {
|
||||
const request = JSON.parse(event.data) as BridgeRequest;
|
||||
|
||||
if (request.type !== "request") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await handleRequest(request);
|
||||
|
||||
reply(request.id, JSON.stringify(result));
|
||||
} catch (err) {
|
||||
error(request.id, err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[WA Bridge] invalid message", err);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = (event) => {
|
||||
console.log("[WA Bridge] disconnected", {
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
wasClean: event.wasClean,
|
||||
});
|
||||
|
||||
socket = undefined;
|
||||
setTimeout(connect, 3000);
|
||||
};
|
||||
|
||||
socket.onerror = (event) => {
|
||||
console.error("[WA Bridge] websocket error", event);
|
||||
};
|
||||
}
|
||||
|
||||
function start() {
|
||||
WPP.loader.onReady(() => {
|
||||
console.log("[WA Bridge] WA-JS ready");
|
||||
|
||||
connect();
|
||||
|
||||
/*
|
||||
* Forward new-message events to Node.
|
||||
*/
|
||||
WPP.chat.on("chat.new_message", (message: unknown) => {
|
||||
if (socket?.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
send({
|
||||
type: "event",
|
||||
event: "message",
|
||||
data: serialize(message),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
start();
|
||||
Reference in New Issue
Block a user