initial commit
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
# dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# whatsapp-bridge
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.3.12. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "whatsapp-bridge",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g=="],
|
||||||
|
|
||||||
|
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="],
|
||||||
|
|
||||||
|
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"undici-types": ["[email protected]", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+413
@@ -0,0 +1,413 @@
|
|||||||
|
|
||||||
|
// ==UserScript==
|
||||||
|
// @name WhatsApp Web Bridge
|
||||||
|
// @namespace https://rul.sh/
|
||||||
|
// @version 0.0.1
|
||||||
|
// @description WhatsApp Web WebSocket bridge
|
||||||
|
// @match https://web.whatsapp.com/*
|
||||||
|
// @updateURL https://example.com/my-script.user.js
|
||||||
|
// @downloadURL https://example.com/my-script.user.js
|
||||||
|
// @require https://github.com/wppconnect-team/wa-js/releases/download/nightly/wppconnect-wa.js
|
||||||
|
// @grant none
|
||||||
|
// @run-at document-idle
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
(() => {
|
||||||
|
// src/userscript/serializer.ts
|
||||||
|
function serialize(value) {
|
||||||
|
if (value == null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(serialize);
|
||||||
|
}
|
||||||
|
if (typeof value !== "object") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const result = {};
|
||||||
|
for (const key of Object.keys(value)) {
|
||||||
|
const v = value[key];
|
||||||
|
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null) {
|
||||||
|
result[key] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function safeProperty(object, key) {
|
||||||
|
try {
|
||||||
|
if (object && typeof object === "object") {
|
||||||
|
return object[key];
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
function safeGet(object, key) {
|
||||||
|
if (object == null || typeof object !== "object") {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return object[key];
|
||||||
|
} catch {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function serializeId(id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof id === "string") {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
if (typeof id !== "object") {
|
||||||
|
return String(id);
|
||||||
|
}
|
||||||
|
const result = {};
|
||||||
|
for (const key of [
|
||||||
|
"_serialized",
|
||||||
|
"user",
|
||||||
|
"server",
|
||||||
|
"domain",
|
||||||
|
"device",
|
||||||
|
"agent",
|
||||||
|
"fromMe",
|
||||||
|
"remote",
|
||||||
|
"participant"
|
||||||
|
]) {
|
||||||
|
const value = safeGet(id, key);
|
||||||
|
if (value !== void 0) {
|
||||||
|
result[key] = serialize(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result._serialized && typeof safeGet(id, "toString") === "function") {
|
||||||
|
try {
|
||||||
|
const serialized = id.toString();
|
||||||
|
if (serialized && serialized !== "[object Object]") {
|
||||||
|
result._serialized = serialized;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.keys(result).length ? result : String(id);
|
||||||
|
}
|
||||||
|
function serializeChat(chat) {
|
||||||
|
return {
|
||||||
|
id: serializeId(chat.id),
|
||||||
|
accountLid: chat.accountLid,
|
||||||
|
name: chat.name,
|
||||||
|
formattedTitle: chat.formattedTitle,
|
||||||
|
isGroup: chat.isGroup,
|
||||||
|
isUser: chat.isUser,
|
||||||
|
isReadOnly: chat.isReadOnly,
|
||||||
|
archived: chat.archived,
|
||||||
|
unreadCount: chat.unreadCount,
|
||||||
|
timestamp: chat.t,
|
||||||
|
lastMessage: chat.lastMessage ? serialize(chat.lastMessage) : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function serializeContact(contact) {
|
||||||
|
return {
|
||||||
|
id: serializeId(contact.id),
|
||||||
|
accountLid: contact.accountLid ?? null,
|
||||||
|
name: contact.name ?? null,
|
||||||
|
pushname: contact.pushname ?? null,
|
||||||
|
shortName: contact.shortName ?? null,
|
||||||
|
formattedName: contact.formattedName ?? null,
|
||||||
|
number: contact.number ?? null,
|
||||||
|
isMe: contact.isMe ?? false,
|
||||||
|
isUser: contact.isUser ?? false,
|
||||||
|
isBusiness: contact.isBusiness ?? false,
|
||||||
|
isEnterprise: contact.isEnterprise ?? false,
|
||||||
|
isGroup: contact.isGroup ?? false,
|
||||||
|
isMyContact: contact.isMyContact ?? false,
|
||||||
|
isBlocked: contact.isBlocked ?? false,
|
||||||
|
profilePicThumbObj: contact.profilePicThumbObj ? serialize(contact.profilePicThumbObj) : void 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function serializeGroupParticipant(participant) {
|
||||||
|
return {
|
||||||
|
id: serializeId(participant.id),
|
||||||
|
isAdmin: participant.isAdmin ?? false,
|
||||||
|
isSuperAdmin: participant.isSuperAdmin ?? false,
|
||||||
|
isSuperParticipant: participant.isSuperParticipant ?? false,
|
||||||
|
isBusiness: participant.isBusiness ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function serializeGroupMetadata(metadata) {
|
||||||
|
return {
|
||||||
|
id: serializeId(metadata.id),
|
||||||
|
subject: metadata.subject ?? null,
|
||||||
|
subjectOwner: serializeId(metadata.subjectOwner),
|
||||||
|
subjectTime: metadata.subjectTime ?? null,
|
||||||
|
creation: metadata.creation ?? null,
|
||||||
|
owner: serializeId(metadata.owner),
|
||||||
|
participants: Array.isArray(metadata.participants) ? metadata.participants.map(serializeGroupParticipant).filter(Boolean) : [],
|
||||||
|
size: metadata.size ?? metadata.participants?.length ?? 0,
|
||||||
|
restrict: metadata.restrict ?? false,
|
||||||
|
announce: metadata.announce ?? false,
|
||||||
|
isCommunity: metadata.isCommunity ?? false,
|
||||||
|
isCommunityAnnounce: metadata.isCommunityAnnounce ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function serializeMessageId(id) {
|
||||||
|
if (!id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: id.id ?? null,
|
||||||
|
_serialized: id._serialized ?? null,
|
||||||
|
fromMe: id.fromMe ?? false,
|
||||||
|
remote: serializeId(id.remote),
|
||||||
|
participant: serializeId(id.participant)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function serializeLocation(location) {
|
||||||
|
if (!location) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
latitude: location.latitude ?? null,
|
||||||
|
longitude: location.longitude ?? null,
|
||||||
|
description: location.description ?? null,
|
||||||
|
address: location.address ?? null,
|
||||||
|
url: location.url ?? null,
|
||||||
|
name: location.name ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function serializeMessageLink(link) {
|
||||||
|
return {
|
||||||
|
link: link?.link ?? null,
|
||||||
|
isSuspicious: link?.isSuspicious ?? false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function serializeMessage(message) {
|
||||||
|
const quotedMsgId = safeProperty(message, "quotedMsgId");
|
||||||
|
const hasQuoted = !!quotedMsgId;
|
||||||
|
let quotedMsg = null;
|
||||||
|
if (hasQuoted) {
|
||||||
|
const rawQuoted = safeProperty(message, "quotedMsg");
|
||||||
|
if (rawQuoted) {
|
||||||
|
quotedMsg = serializeMessage(rawQuoted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: serializeMessageId(message.id),
|
||||||
|
chatId: message.chatId ?? message.id?.remote?._serialized ?? null,
|
||||||
|
from: serializeId(message.from),
|
||||||
|
to: serializeId(message.to),
|
||||||
|
author: serializeId(message.author),
|
||||||
|
fromMe: message.fromMe ?? false,
|
||||||
|
body: message.body ?? null,
|
||||||
|
caption: message.caption ?? null,
|
||||||
|
type: message.type ?? null,
|
||||||
|
subtype: message.subtype ?? null,
|
||||||
|
timestamp: message.timestamp ?? null,
|
||||||
|
ack: message.ack ?? null,
|
||||||
|
hasMedia: message.hasMedia ?? false,
|
||||||
|
mediaKey: message.mediaKey ?? null,
|
||||||
|
isForwarded: message.isForwarded ?? false,
|
||||||
|
forwardingScore: message.forwardingScore ?? 0,
|
||||||
|
isStarred: message.isStarred ?? false,
|
||||||
|
broadcast: message.broadcast ?? false,
|
||||||
|
mentionedIds: Array.isArray(message.mentionedIds) ? message.mentionedIds.map(serializeId) : [],
|
||||||
|
quotedMsgId: serializeMessageId(quotedMsgId),
|
||||||
|
quotedMsg,
|
||||||
|
links: Array.isArray(message.links) ? message.links.map(serializeMessageLink) : [],
|
||||||
|
location: serializeLocation(message.location),
|
||||||
|
vCard: message.vCard ?? null,
|
||||||
|
vCards: Array.isArray(message.vCards) ? message.vCards : [],
|
||||||
|
filename: message.filename ?? null,
|
||||||
|
mimetype: message.mimetype ?? null,
|
||||||
|
size: message.size ?? null,
|
||||||
|
duration: message.duration ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/userscript/index.ts
|
||||||
|
var WS_URL = "ws://127.0.0.1:8787";
|
||||||
|
var socket;
|
||||||
|
function send(message) {
|
||||||
|
if (socket?.readyState !== WebSocket.OPEN) {
|
||||||
|
throw new Error("WebSocket is not connected");
|
||||||
|
}
|
||||||
|
socket.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
function reply(id, result) {
|
||||||
|
send({
|
||||||
|
type: "response",
|
||||||
|
id,
|
||||||
|
ok: true,
|
||||||
|
result: serialize(result)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function error(id, err) {
|
||||||
|
send({
|
||||||
|
type: "response",
|
||||||
|
id,
|
||||||
|
ok: false,
|
||||||
|
error: {
|
||||||
|
code: "ERROR",
|
||||||
|
message: err instanceof Error ? err.message : String(err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function handleRequest(request) {
|
||||||
|
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.method}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function searchMessages(query, chatId, 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 = [];
|
||||||
|
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);
|
||||||
|
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 = void 0;
|
||||||
|
setTimeout(connect, 3e3);
|
||||||
|
};
|
||||||
|
socket.onerror = (event) => {
|
||||||
|
console.error("[WA Bridge] websocket error", event);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function start() {
|
||||||
|
WPP.loader.onReady(() => {
|
||||||
|
console.log("[WA Bridge] WA-JS ready");
|
||||||
|
connect();
|
||||||
|
WPP.chat.on("chat.new_message", (message) => {
|
||||||
|
if (socket?.readyState !== WebSocket.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send({
|
||||||
|
type: "event",
|
||||||
|
event: "message",
|
||||||
|
data: serialize(message)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
start();
|
||||||
|
})();
|
||||||
|
//# sourceMappingURL=whatsapp-bridge.user.js.map
|
||||||
Vendored
+7
File diff suppressed because one or more lines are too long
+32
@@ -0,0 +1,32 @@
|
|||||||
|
import * as esbuild from "esbuild";
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
entryPoints: ["src/userscript/index.ts"],
|
||||||
|
outfile: "dist/whatsapp-bridge.user.js",
|
||||||
|
|
||||||
|
bundle: true,
|
||||||
|
format: "iife",
|
||||||
|
platform: "browser",
|
||||||
|
target: "es2020",
|
||||||
|
|
||||||
|
banner: {
|
||||||
|
js: `
|
||||||
|
// ==UserScript==
|
||||||
|
// @name WhatsApp Web Bridge
|
||||||
|
// @namespace https://rul.sh/
|
||||||
|
// @version 0.0.1
|
||||||
|
// @description WhatsApp Web WebSocket bridge
|
||||||
|
// @match https://web.whatsapp.com/*
|
||||||
|
// @updateURL https://example.com/my-script.user.js
|
||||||
|
// @downloadURL https://example.com/my-script.user.js
|
||||||
|
// @require https://github.com/wppconnect-team/wa-js/releases/download/nightly/wppconnect-wa.js
|
||||||
|
// @grant none
|
||||||
|
// @run-at document-idle
|
||||||
|
// ==/UserScript==
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
sourcemap: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Built dist/whatsapp-bridge.user.js");
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "whatsapp-bridge",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "node esbuild.mjs",
|
||||||
|
"server": "bun run src/server/main.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "^26.5.1",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"@wppconnect/wa-js": "^4.6.0",
|
||||||
|
"esbuild": "^0.28.2",
|
||||||
|
"typescript": "^7.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^8.21.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+565
@@ -0,0 +1,565 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
ws:
|
||||||
|
specifier: ^8.21.3
|
||||||
|
version: 8.21.3
|
||||||
|
devDependencies:
|
||||||
|
'@types/bun':
|
||||||
|
specifier: latest
|
||||||
|
version: 1.4.2
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^26.5.1
|
||||||
|
version: 26.5.1
|
||||||
|
'@types/ws':
|
||||||
|
specifier: ^8.18.1
|
||||||
|
version: 8.18.1
|
||||||
|
'@wppconnect/wa-js':
|
||||||
|
specifier: ^4.6.0
|
||||||
|
version: 4.6.0
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.28.2
|
||||||
|
version: 0.28.2
|
||||||
|
typescript:
|
||||||
|
specifier: ^7.0.2
|
||||||
|
version: 7.0.2
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/[email protected]':
|
||||||
|
resolution: {integrity: sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ==}
|
||||||
|
|
||||||
|
'@types/[email protected]':
|
||||||
|
resolution: {integrity: sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==}
|
||||||
|
|
||||||
|
'@types/[email protected]':
|
||||||
|
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@wppconnect/[email protected]':
|
||||||
|
resolution: {integrity: sha512-epf+ucZO9c25dCaGraslVNHB1mRjid7rfijZhKbalXed/gbDzi6u69wdHCgPbcZW9McVpKmVW+3AzffI39p2SQ==}
|
||||||
|
engines: {whatsapp-web: '>=2.3000.1038792969-alpha'}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==}
|
||||||
|
engines: {node: '>=16.20.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: '>=5.0.2'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
bun-types: 1.4.2
|
||||||
|
|
||||||
|
'@types/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 8.9.0
|
||||||
|
|
||||||
|
'@types/[email protected]':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 26.5.1
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@typescript/[email protected]':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@wppconnect/[email protected]': {}
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 26.5.1
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.28.2
|
||||||
|
'@esbuild/android-arm': 0.28.2
|
||||||
|
'@esbuild/android-arm64': 0.28.2
|
||||||
|
'@esbuild/android-x64': 0.28.2
|
||||||
|
'@esbuild/darwin-arm64': 0.28.2
|
||||||
|
'@esbuild/darwin-x64': 0.28.2
|
||||||
|
'@esbuild/freebsd-arm64': 0.28.2
|
||||||
|
'@esbuild/freebsd-x64': 0.28.2
|
||||||
|
'@esbuild/linux-arm': 0.28.2
|
||||||
|
'@esbuild/linux-arm64': 0.28.2
|
||||||
|
'@esbuild/linux-ia32': 0.28.2
|
||||||
|
'@esbuild/linux-loong64': 0.28.2
|
||||||
|
'@esbuild/linux-mips64el': 0.28.2
|
||||||
|
'@esbuild/linux-ppc64': 0.28.2
|
||||||
|
'@esbuild/linux-riscv64': 0.28.2
|
||||||
|
'@esbuild/linux-s390x': 0.28.2
|
||||||
|
'@esbuild/linux-x64': 0.28.2
|
||||||
|
'@esbuild/netbsd-arm64': 0.28.2
|
||||||
|
'@esbuild/netbsd-x64': 0.28.2
|
||||||
|
'@esbuild/openbsd-arm64': 0.28.2
|
||||||
|
'@esbuild/openbsd-x64': 0.28.2
|
||||||
|
'@esbuild/openharmony-arm64': 0.28.2
|
||||||
|
'@esbuild/sunos-x64': 0.28.2
|
||||||
|
'@esbuild/win32-arm64': 0.28.2
|
||||||
|
'@esbuild/win32-ia32': 0.28.2
|
||||||
|
'@esbuild/win32-x64': 0.28.2
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
optionalDependencies:
|
||||||
|
'@typescript/typescript-aix-ppc64': 7.0.2
|
||||||
|
'@typescript/typescript-darwin-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-darwin-x64': 7.0.2
|
||||||
|
'@typescript/typescript-freebsd-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-freebsd-x64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-arm': 7.0.2
|
||||||
|
'@typescript/typescript-linux-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-loong64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-mips64el': 7.0.2
|
||||||
|
'@typescript/typescript-linux-ppc64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-riscv64': 7.0.2
|
||||||
|
'@typescript/typescript-linux-s390x': 7.0.2
|
||||||
|
'@typescript/typescript-linux-x64': 7.0.2
|
||||||
|
'@typescript/typescript-netbsd-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-netbsd-x64': 7.0.2
|
||||||
|
'@typescript/typescript-openbsd-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-openbsd-x64': 7.0.2
|
||||||
|
'@typescript/typescript-sunos-x64': 7.0.2
|
||||||
|
'@typescript/typescript-win32-arm64': 7.0.2
|
||||||
|
'@typescript/typescript-win32-x64': 7.0.2
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
|
|
||||||
|
[email protected]: {}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { WhatsAppClient } from "./whatsapp";
|
||||||
|
|
||||||
|
const wa = new WhatsAppClient("ws://127.0.0.1:8787");
|
||||||
|
await wa.connect();
|
||||||
|
|
||||||
|
const chats = await wa.listChats({
|
||||||
|
limit: 10,
|
||||||
|
groupsOnly: true,
|
||||||
|
// usersOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
chats.map((i) => ({
|
||||||
|
id: typeof i.id === "object" ? i.id?._serialized : i.id,
|
||||||
|
lid: i.accountLid,
|
||||||
|
name: i.formattedTitle,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = await wa.getMessages({
|
||||||
|
chatId: "[email protected]",
|
||||||
|
count: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
messages.map((i) => ({
|
||||||
|
id: i.id.id,
|
||||||
|
from: i.from,
|
||||||
|
text: i.body,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
// const result = await wa.searchMessages({
|
||||||
|
// query: "pasien",
|
||||||
|
// chatId: "[email protected]",
|
||||||
|
// limit: 20,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// await wa.sendText({
|
||||||
|
// chatId: "[email protected]",
|
||||||
|
// text: "Hello from Node!",
|
||||||
|
// });
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import { WebSocketServer, WebSocket } from "ws";
|
||||||
|
|
||||||
|
import type { BridgeRequest, RpcResponse } from "../shared/protocol";
|
||||||
|
|
||||||
|
const PORT = 8787;
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({
|
||||||
|
host: "0.0.0.0",
|
||||||
|
port: PORT,
|
||||||
|
});
|
||||||
|
|
||||||
|
let whatsapp: WebSocket | undefined;
|
||||||
|
|
||||||
|
const pending = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
resolve: (value: unknown) => void;
|
||||||
|
reject: (reason: unknown) => void;
|
||||||
|
timer: NodeJS.Timeout;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
function requestId() {
|
||||||
|
return crypto.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
function call<T>(
|
||||||
|
method: BridgeRequest["method"],
|
||||||
|
params?: unknown,
|
||||||
|
): Promise<T> {
|
||||||
|
if (!whatsapp || whatsapp.readyState !== WebSocket.OPEN) {
|
||||||
|
throw new Error("WhatsApp Web is not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = requestId();
|
||||||
|
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
pending.delete(id);
|
||||||
|
|
||||||
|
reject(new Error(`WhatsApp request timed out: ${method}`));
|
||||||
|
}, 30_000);
|
||||||
|
|
||||||
|
pending.set(id, {
|
||||||
|
resolve: resolve as never,
|
||||||
|
reject,
|
||||||
|
timer,
|
||||||
|
});
|
||||||
|
|
||||||
|
whatsapp!.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "request",
|
||||||
|
id,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWhatsAppMessage(data: string) {
|
||||||
|
const message = JSON.parse(data) as
|
||||||
|
| RpcResponse
|
||||||
|
| {
|
||||||
|
type: "event";
|
||||||
|
event: string;
|
||||||
|
data: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (message.type === "response") {
|
||||||
|
const request = pending.get(message.id);
|
||||||
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending.delete(message.id);
|
||||||
|
clearTimeout(request.timer);
|
||||||
|
|
||||||
|
if (message.ok) {
|
||||||
|
const res = JSON.parse(message.result as string);
|
||||||
|
request.resolve(res);
|
||||||
|
} else {
|
||||||
|
request.reject(
|
||||||
|
new Error(message.error?.message ?? "Unknown WhatsApp error"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "event") {
|
||||||
|
console.log("[WhatsApp event]", message.event, message.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wss.on("connection", (ws) => {
|
||||||
|
console.log("[WS] client connected");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First connection from Tampermonkey is the
|
||||||
|
* WhatsApp bridge.
|
||||||
|
*/
|
||||||
|
if (!whatsapp) {
|
||||||
|
whatsapp = ws;
|
||||||
|
|
||||||
|
console.log("[WS] WhatsApp bridge attached");
|
||||||
|
|
||||||
|
ws.on("message", (data) => {
|
||||||
|
handleWhatsAppMessage(data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on("close", () => {
|
||||||
|
if (whatsapp === ws) {
|
||||||
|
whatsapp = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[WS] WhatsApp disconnected");
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is an external application/client.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* my backend
|
||||||
|
* CLI
|
||||||
|
* React application
|
||||||
|
* another service
|
||||||
|
*/
|
||||||
|
|
||||||
|
ws.on("message", async (data) => {
|
||||||
|
let request: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
request = JSON.parse(data.toString()) as BridgeRequest;
|
||||||
|
|
||||||
|
if (request.type !== "request") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: unknown;
|
||||||
|
|
||||||
|
switch (request.method) {
|
||||||
|
case "status":
|
||||||
|
result = await call("status", request.params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "listChats":
|
||||||
|
result = await call("listChats", request.params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "listContacts":
|
||||||
|
result = await call("listContacts", request.params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "listGroups":
|
||||||
|
result = await call("listGroups", request.params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "getMessages":
|
||||||
|
result = await call("getMessages", request.params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "searchMessages":
|
||||||
|
result = await call("searchMessages", request.params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "sendText":
|
||||||
|
result = await call("sendText", request.params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported method: ${(request as any).method}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "response",
|
||||||
|
id: request.id,
|
||||||
|
ok: true,
|
||||||
|
result,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "response",
|
||||||
|
id: request?.id || null,
|
||||||
|
ok: false,
|
||||||
|
error: {
|
||||||
|
code: "ERROR",
|
||||||
|
message: err instanceof Error ? err.message : String(err),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on("close", () => {
|
||||||
|
console.log("[WS] external client disconnected");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`WhatsApp bridge listening on ws://127.0.0.1:${PORT}`);
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
export type JsonValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null
|
||||||
|
| JsonValue[]
|
||||||
|
| {
|
||||||
|
[key: string]: JsonValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SerializedId {
|
||||||
|
_serialized?: string;
|
||||||
|
user?: string;
|
||||||
|
server?: string;
|
||||||
|
domain?: string;
|
||||||
|
device?: string;
|
||||||
|
agent?: string;
|
||||||
|
[key: string]: JsonValue | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContactDTO {
|
||||||
|
id: SerializedId | string | null;
|
||||||
|
|
||||||
|
accountLid?: string | null;
|
||||||
|
|
||||||
|
name?: string | null;
|
||||||
|
pushname?: string | null;
|
||||||
|
shortName?: string | null;
|
||||||
|
formattedName?: string | null;
|
||||||
|
|
||||||
|
number?: string | null;
|
||||||
|
|
||||||
|
isMe?: boolean;
|
||||||
|
isUser?: boolean;
|
||||||
|
isBusiness?: boolean;
|
||||||
|
isEnterprise?: boolean;
|
||||||
|
isGroup?: boolean;
|
||||||
|
|
||||||
|
isMyContact?: boolean;
|
||||||
|
isBlocked?: boolean;
|
||||||
|
|
||||||
|
profilePicThumbObj?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatDTO {
|
||||||
|
id: SerializedId | string | null;
|
||||||
|
|
||||||
|
accountLid?: string | null;
|
||||||
|
|
||||||
|
name?: string | null;
|
||||||
|
formattedTitle?: string | null;
|
||||||
|
|
||||||
|
isGroup?: boolean;
|
||||||
|
isUser?: boolean;
|
||||||
|
isNewsletter?: boolean;
|
||||||
|
isCommunity?: boolean;
|
||||||
|
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
isAnnounceGrpRestrict?: boolean;
|
||||||
|
|
||||||
|
archived?: boolean;
|
||||||
|
pinned?: boolean;
|
||||||
|
muted?: boolean;
|
||||||
|
|
||||||
|
unreadCount?: number;
|
||||||
|
unreadMentionsCount?: number;
|
||||||
|
|
||||||
|
timestamp?: number | null;
|
||||||
|
lastMessage?: MessageDTO | null;
|
||||||
|
contact?: ContactDTO | null;
|
||||||
|
groupMetadata?: GroupMetadataDTO | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupMetadataDTO {
|
||||||
|
id: SerializedId | string | null;
|
||||||
|
|
||||||
|
subject?: string | null;
|
||||||
|
subjectOwner?: SerializedId | string | null;
|
||||||
|
|
||||||
|
subjectTime?: number | null;
|
||||||
|
creation?: number | null;
|
||||||
|
|
||||||
|
owner?: SerializedId | string | null;
|
||||||
|
|
||||||
|
participants?: GroupParticipantDTO[];
|
||||||
|
|
||||||
|
size?: number;
|
||||||
|
|
||||||
|
restrict?: boolean;
|
||||||
|
announce?: boolean;
|
||||||
|
|
||||||
|
isCommunity?: boolean;
|
||||||
|
isCommunityAnnounce?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupParticipantDTO {
|
||||||
|
id: SerializedId | string | null;
|
||||||
|
|
||||||
|
isAdmin?: boolean;
|
||||||
|
isSuperAdmin?: boolean;
|
||||||
|
isSuperParticipant?: boolean;
|
||||||
|
|
||||||
|
isBusiness?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageDTO {
|
||||||
|
id: MessageIdDTO;
|
||||||
|
|
||||||
|
chatId?: string | null;
|
||||||
|
|
||||||
|
from?: SerializedId | string | null;
|
||||||
|
to?: SerializedId | string | null;
|
||||||
|
|
||||||
|
author?: SerializedId | string | null;
|
||||||
|
|
||||||
|
fromMe?: boolean;
|
||||||
|
|
||||||
|
body?: string | null;
|
||||||
|
caption?: string | null;
|
||||||
|
|
||||||
|
type?: string | null;
|
||||||
|
subtype?: string | null;
|
||||||
|
|
||||||
|
timestamp?: number | null;
|
||||||
|
|
||||||
|
ack?: number | null;
|
||||||
|
|
||||||
|
hasMedia?: boolean;
|
||||||
|
mediaKey?: string | null;
|
||||||
|
|
||||||
|
isForwarded?: boolean;
|
||||||
|
forwardingScore?: number;
|
||||||
|
|
||||||
|
isStarred?: boolean;
|
||||||
|
|
||||||
|
broadcast?: boolean;
|
||||||
|
|
||||||
|
mentionedIds?: (SerializedId | string)[];
|
||||||
|
|
||||||
|
quotedMsgId?: MessageIdDTO | null;
|
||||||
|
|
||||||
|
quotedMsg?: MessageDTO | null;
|
||||||
|
|
||||||
|
links?: MessageLinkDTO[];
|
||||||
|
|
||||||
|
location?: LocationDTO | null;
|
||||||
|
|
||||||
|
vCard?: string | null;
|
||||||
|
vCards?: string[];
|
||||||
|
|
||||||
|
filename?: string | null;
|
||||||
|
mimetype?: string | null;
|
||||||
|
size?: number | null;
|
||||||
|
|
||||||
|
duration?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageIdDTO {
|
||||||
|
id?: string | null;
|
||||||
|
_serialized?: string | null;
|
||||||
|
|
||||||
|
fromMe?: boolean;
|
||||||
|
remote?: SerializedId | string | null;
|
||||||
|
participant?: SerializedId | string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageLinkDTO {
|
||||||
|
link?: string | null;
|
||||||
|
isSuspicious?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationDTO {
|
||||||
|
latitude?: number | null;
|
||||||
|
longitude?: number | null;
|
||||||
|
|
||||||
|
description?: string | null;
|
||||||
|
address?: string | null;
|
||||||
|
|
||||||
|
url?: string | null;
|
||||||
|
|
||||||
|
name?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SentMessageDTO {
|
||||||
|
id: MessageIdDTO | null;
|
||||||
|
|
||||||
|
ack?: number | null;
|
||||||
|
|
||||||
|
timestamp?: number | null;
|
||||||
|
|
||||||
|
chatId?: string | null;
|
||||||
|
|
||||||
|
from?: SerializedId | string | null;
|
||||||
|
to?: SerializedId | string | null;
|
||||||
|
|
||||||
|
body?: string | null;
|
||||||
|
|
||||||
|
type?: string | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
export type RequestId = string;
|
||||||
|
|
||||||
|
export interface RpcRequest<M extends string = string, P = unknown> {
|
||||||
|
type: "request";
|
||||||
|
id: RequestId;
|
||||||
|
method: M;
|
||||||
|
params: P;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RpcResponse<T = unknown> {
|
||||||
|
type: "response";
|
||||||
|
id: RequestId;
|
||||||
|
ok: boolean;
|
||||||
|
result?: T;
|
||||||
|
error?: {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RpcEvent<T = unknown> {
|
||||||
|
type: "event";
|
||||||
|
event: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServerMessage = RpcResponse | RpcEvent;
|
||||||
|
|
||||||
|
export interface ListChatsParams {
|
||||||
|
limit?: number;
|
||||||
|
groupsOnly?: boolean;
|
||||||
|
usersOnly?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetMessagesParams {
|
||||||
|
chatId: string;
|
||||||
|
count?: number;
|
||||||
|
direction?: "before" | "after";
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchMessagesParams {
|
||||||
|
query: string;
|
||||||
|
chatId?: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendTextParams {
|
||||||
|
chatId: string;
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
options?: {
|
||||||
|
delay?: number;
|
||||||
|
quotedMsg?: string;
|
||||||
|
mentionedList?: string[];
|
||||||
|
waitForAck?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetContactsParams {
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BridgeRequest =
|
||||||
|
| RpcRequest<"status">
|
||||||
|
| RpcRequest<"listChats", ListChatsParams>
|
||||||
|
| RpcRequest<"listContacts", GetContactsParams>
|
||||||
|
| RpcRequest<"listGroups">
|
||||||
|
| RpcRequest<"getMessages", GetMessagesParams>
|
||||||
|
| RpcRequest<"searchMessages", SearchMessagesParams>
|
||||||
|
| RpcRequest<"sendText", SendTextParams>;
|
||||||
@@ -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();
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import type {
|
||||||
|
ChatDTO,
|
||||||
|
ContactDTO,
|
||||||
|
GroupMetadataDTO,
|
||||||
|
GroupParticipantDTO,
|
||||||
|
LocationDTO,
|
||||||
|
MessageDTO,
|
||||||
|
MessageIdDTO,
|
||||||
|
SerializedId,
|
||||||
|
} from "../shared/dto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WA models contain a lot of internal properties and sometimes
|
||||||
|
* circular references. Never JSON.stringify them directly.
|
||||||
|
*/
|
||||||
|
export function serialize(value: any): any {
|
||||||
|
if (value == null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(serialize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== "object") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(value)) {
|
||||||
|
const v = value[key];
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof v === "string" ||
|
||||||
|
typeof v === "number" ||
|
||||||
|
typeof v === "boolean" ||
|
||||||
|
v === null
|
||||||
|
) {
|
||||||
|
result[key] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeProperty<T>(object: unknown, key: string): T | undefined {
|
||||||
|
try {
|
||||||
|
if (object && typeof object === "object") {
|
||||||
|
return (object as any)[key];
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function safeGet<T = unknown>(
|
||||||
|
object: unknown,
|
||||||
|
key: string,
|
||||||
|
): T | undefined {
|
||||||
|
if (object == null || typeof object !== "object") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return (object as any)[key];
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pick<T extends object>(
|
||||||
|
source: any,
|
||||||
|
keys: readonly string[],
|
||||||
|
): Partial<T> {
|
||||||
|
const result: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = safeGet(source, key);
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
result[key] = serialize(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result as Partial<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeId(id: any): SerializedId | string | null {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof id === "string") {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof id !== "object") {
|
||||||
|
return String(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: SerializedId = {};
|
||||||
|
|
||||||
|
for (const key of [
|
||||||
|
"_serialized",
|
||||||
|
"user",
|
||||||
|
"server",
|
||||||
|
"domain",
|
||||||
|
"device",
|
||||||
|
"agent",
|
||||||
|
"fromMe",
|
||||||
|
"remote",
|
||||||
|
"participant",
|
||||||
|
]) {
|
||||||
|
const value = safeGet(id, key);
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
result[key] = serialize(value) as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some WA objects expose useful fields that aren't
|
||||||
|
// enumerable.
|
||||||
|
if (!result._serialized && typeof safeGet(id, "toString") === "function") {
|
||||||
|
try {
|
||||||
|
const serialized = id.toString();
|
||||||
|
|
||||||
|
if (serialized && serialized !== "[object Object]") {
|
||||||
|
result._serialized = serialized;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(result).length ? result : String(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeChat(chat: any): ChatDTO {
|
||||||
|
return {
|
||||||
|
id: serializeId(chat.id),
|
||||||
|
accountLid: chat.accountLid,
|
||||||
|
name: chat.name,
|
||||||
|
formattedTitle: chat.formattedTitle,
|
||||||
|
|
||||||
|
isGroup: chat.isGroup,
|
||||||
|
isUser: chat.isUser,
|
||||||
|
isReadOnly: chat.isReadOnly,
|
||||||
|
archived: chat.archived,
|
||||||
|
unreadCount: chat.unreadCount,
|
||||||
|
|
||||||
|
timestamp: chat.t,
|
||||||
|
lastMessage: chat.lastMessage ? serialize(chat.lastMessage) : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeContact(contact: any): ContactDTO {
|
||||||
|
return {
|
||||||
|
id: serializeId(contact.id),
|
||||||
|
|
||||||
|
accountLid: contact.accountLid ?? null,
|
||||||
|
name: contact.name ?? null,
|
||||||
|
pushname: contact.pushname ?? null,
|
||||||
|
shortName: contact.shortName ?? null,
|
||||||
|
formattedName: contact.formattedName ?? null,
|
||||||
|
number: contact.number ?? null,
|
||||||
|
|
||||||
|
isMe: contact.isMe ?? false,
|
||||||
|
isUser: contact.isUser ?? false,
|
||||||
|
isBusiness: contact.isBusiness ?? false,
|
||||||
|
isEnterprise: contact.isEnterprise ?? false,
|
||||||
|
isGroup: contact.isGroup ?? false,
|
||||||
|
isMyContact: contact.isMyContact ?? false,
|
||||||
|
isBlocked: contact.isBlocked ?? false,
|
||||||
|
|
||||||
|
profilePicThumbObj: contact.profilePicThumbObj
|
||||||
|
? serialize(contact.profilePicThumbObj)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeGroupParticipant(
|
||||||
|
participant: any,
|
||||||
|
): GroupParticipantDTO {
|
||||||
|
return {
|
||||||
|
id: serializeId(participant.id),
|
||||||
|
|
||||||
|
isAdmin: participant.isAdmin ?? false,
|
||||||
|
isSuperAdmin: participant.isSuperAdmin ?? false,
|
||||||
|
isSuperParticipant: participant.isSuperParticipant ?? false,
|
||||||
|
isBusiness: participant.isBusiness ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeGroupMetadata(metadata: any): GroupMetadataDTO {
|
||||||
|
return {
|
||||||
|
id: serializeId(metadata.id),
|
||||||
|
|
||||||
|
subject: metadata.subject ?? null,
|
||||||
|
subjectOwner: serializeId(metadata.subjectOwner),
|
||||||
|
subjectTime: metadata.subjectTime ?? null,
|
||||||
|
creation: metadata.creation ?? null,
|
||||||
|
owner: serializeId(metadata.owner),
|
||||||
|
|
||||||
|
participants: Array.isArray(metadata.participants)
|
||||||
|
? (metadata.participants
|
||||||
|
.map(serializeGroupParticipant)
|
||||||
|
.filter(Boolean) as GroupParticipantDTO[])
|
||||||
|
: [],
|
||||||
|
|
||||||
|
size: metadata.size ?? metadata.participants?.length ?? 0,
|
||||||
|
restrict: metadata.restrict ?? false,
|
||||||
|
announce: metadata.announce ?? false,
|
||||||
|
isCommunity: metadata.isCommunity ?? false,
|
||||||
|
isCommunityAnnounce: metadata.isCommunityAnnounce ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function serializeMessageId(id: any): MessageIdDTO | null {
|
||||||
|
if (!id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id.id ?? null,
|
||||||
|
_serialized: id._serialized ?? null,
|
||||||
|
fromMe: id.fromMe ?? false,
|
||||||
|
remote: serializeId(id.remote),
|
||||||
|
participant: serializeId(id.participant),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function serializeLocation(location: any): LocationDTO | null {
|
||||||
|
if (!location) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
latitude: location.latitude ?? null,
|
||||||
|
longitude: location.longitude ?? null,
|
||||||
|
description: location.description ?? null,
|
||||||
|
address: location.address ?? null,
|
||||||
|
url: location.url ?? null,
|
||||||
|
name: location.name ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function serializeMessageLink(link: any): any {
|
||||||
|
return {
|
||||||
|
link: link?.link ?? null,
|
||||||
|
isSuspicious: link?.isSuspicious ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function serializeMessage(message: any): MessageDTO {
|
||||||
|
const quotedMsgId = safeProperty<any>(message, "quotedMsgId");
|
||||||
|
const hasQuoted = !!quotedMsgId;
|
||||||
|
let quotedMsg = null;
|
||||||
|
|
||||||
|
if (hasQuoted) {
|
||||||
|
const rawQuoted = safeProperty<any>(message, "quotedMsg");
|
||||||
|
if (rawQuoted) {
|
||||||
|
quotedMsg = serializeMessage(rawQuoted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: serializeMessageId(message.id)!,
|
||||||
|
chatId: message.chatId ?? message.id?.remote?._serialized ?? null,
|
||||||
|
from: serializeId(message.from),
|
||||||
|
to: serializeId(message.to),
|
||||||
|
author: serializeId(message.author),
|
||||||
|
fromMe: message.fromMe ?? false,
|
||||||
|
body: message.body ?? null,
|
||||||
|
caption: message.caption ?? null,
|
||||||
|
type: message.type ?? null,
|
||||||
|
subtype: message.subtype ?? null,
|
||||||
|
timestamp: message.timestamp ?? null,
|
||||||
|
ack: message.ack ?? null,
|
||||||
|
hasMedia: message.hasMedia ?? false,
|
||||||
|
mediaKey: message.mediaKey ?? null,
|
||||||
|
isForwarded: message.isForwarded ?? false,
|
||||||
|
forwardingScore: message.forwardingScore ?? 0,
|
||||||
|
isStarred: message.isStarred ?? false,
|
||||||
|
broadcast: message.broadcast ?? false,
|
||||||
|
mentionedIds: Array.isArray(message.mentionedIds)
|
||||||
|
? message.mentionedIds.map(serializeId)
|
||||||
|
: [],
|
||||||
|
quotedMsgId: serializeMessageId(quotedMsgId),
|
||||||
|
quotedMsg,
|
||||||
|
links: Array.isArray(message.links)
|
||||||
|
? message.links.map(serializeMessageLink)
|
||||||
|
: [],
|
||||||
|
location: serializeLocation(message.location),
|
||||||
|
vCard: message.vCard ?? null,
|
||||||
|
vCards: Array.isArray(message.vCards) ? message.vCards : [],
|
||||||
|
filename: message.filename ?? null,
|
||||||
|
mimetype: message.mimetype ?? null,
|
||||||
|
size: message.size ?? null,
|
||||||
|
duration: message.duration ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
Vendored
+62
@@ -0,0 +1,62 @@
|
|||||||
|
declare const WPP: {
|
||||||
|
isReady: boolean;
|
||||||
|
|
||||||
|
loader: {
|
||||||
|
onReady(callback: () => void): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
conn: {
|
||||||
|
isAuthenticated(): boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
chat: {
|
||||||
|
list(options?: {
|
||||||
|
count?: number;
|
||||||
|
onlyUsers?: boolean;
|
||||||
|
onlyGroups?: boolean;
|
||||||
|
onlyCommunities?: boolean;
|
||||||
|
onlyNewsletter?: boolean;
|
||||||
|
onlyArchived?: boolean;
|
||||||
|
}): Promise<any[]>;
|
||||||
|
|
||||||
|
get(chatId: string): any | undefined;
|
||||||
|
|
||||||
|
getMessages(
|
||||||
|
chatId: string,
|
||||||
|
options?: {
|
||||||
|
count?: number;
|
||||||
|
direction?: "before" | "after";
|
||||||
|
id?: string;
|
||||||
|
includeCallMessages?: boolean;
|
||||||
|
media?: "image" | "document" | "url" | "all";
|
||||||
|
onlyUnread?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<any[]>;
|
||||||
|
|
||||||
|
sendTextMessage(
|
||||||
|
chatId: string,
|
||||||
|
text: string,
|
||||||
|
options?: {
|
||||||
|
delay?: number;
|
||||||
|
detectMentioned?: boolean;
|
||||||
|
markIsRead?: boolean;
|
||||||
|
mentionedList?: string[];
|
||||||
|
messageId?: string;
|
||||||
|
quotedMsg?: string;
|
||||||
|
quotedMsgPayload?: string;
|
||||||
|
waitForAck?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<any>;
|
||||||
|
|
||||||
|
on(event: string, callback: (...args: any[]) => void): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
contact: {
|
||||||
|
getAllContacts(): Promise<any[]>;
|
||||||
|
get(contactId: string): any | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
group: {
|
||||||
|
getAllGroups(): Promise<any[]>;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"types": ["bun"],
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user