initial commit

This commit is contained in:
2026-09-14 17:15:00 +08:00
commit c78b75297f
19 changed files with 2421 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
import WebSocket from "ws";
import type {
GetMessagesParams,
ListChatsParams,
SearchMessagesParams,
SendTextParams,
} from "../shared/protocol";
import type {
ChatDTO,
ContactDTO,
GroupMetadataDTO,
MessageDTO,
} from "../shared/dto";
export class WhatsAppClient {
private url: string = "";
private isConnected: boolean = false;
private socket: WebSocket | null = null;
private pending = new Map<
string,
{
resolve: (value: any) => void;
reject: (error: Error) => void;
timer: NodeJS.Timeout;
}
>();
constructor(url = "ws://127.0.0.1:8787") {
this.url = url;
}
async connect() {
return new Promise<void>((resolve, reject) => {
if (this.socket) return;
this.socket = new WebSocket(this.url);
this.socket.on("open", () => {
console.log("Connected to bridge");
this.isConnected = true;
resolve();
});
this.socket.on("message", (data) => {
const message = JSON.parse(data.toString());
if (message.type !== "response") {
return;
}
const pending = this.pending.get(message.id);
if (!pending) {
return;
}
this.pending.delete(message.id);
clearTimeout(pending.timer);
if (message.ok) {
pending.resolve(message.result);
} else {
pending.reject(new Error(message.error?.message ?? "Unknown error"));
}
});
this.socket.on("close", () => {
this.socket = null;
this.isConnected = false;
setTimeout(() => this.connect(), 3000);
});
});
}
private request<T>(method: string, params?: unknown): Promise<T> {
if (!this.isConnected || !this.socket) {
throw new Error("WhatsApp is not connected");
}
const id = crypto.randomUUID();
return new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`Request timeout: ${method}`));
}, 30_000);
this.pending.set(id, {
resolve,
reject,
timer,
});
this.socket!.send(
JSON.stringify({
type: "request",
id,
method,
params,
}),
);
});
}
status() {
return this.request<{
authenticated: boolean;
ready: boolean;
}>("status");
}
listChats(params?: ListChatsParams) {
return this.request<ChatDTO[]>("listChats", params);
}
listContacts(params?: { limit?: number }) {
return this.request<ContactDTO[]>("listContacts", params);
}
listGroups() {
return this.request<GroupMetadataDTO[]>("listGroups");
}
getMessages(params: GetMessagesParams) {
return this.request<MessageDTO[]>("getMessages", params);
}
searchMessages(params: SearchMessagesParams) {
return this.request<MessageDTO[]>("searchMessages", params);
}
sendText(params: SendTextParams) {
return this.request<any>("sendText", params);
}
}