Files
whatsapp-bridge/src/userscript/index.ts
T

235 lines
5.1 KiB
TypeScript

import type { BridgeRequest } from "../shared/protocol";
import {
serialize,
serializeChat,
serializeContact,
serializeGroupMetadata,
serializeMessage,
} from "./serializer";
let socket: WebSocket | undefined;
let consts: Record<string, unknown> = {
DEVICE_ID: String(Date.now()),
DEVICE_NAME: "",
};
function init() {
try {
const _consts = localStorage.getItem("wa-bridge-consts");
if (!_consts) {
localStorage.setItem("wa-bridge-consts", JSON.stringify(consts));
} else {
consts = JSON.parse(_consts);
}
} catch (_) {}
}
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:
typeof err === "object" && "message" in err!
? err.message
: String(err),
},
});
}
async function handleRequest(request: BridgeRequest) {
switch (request.method) {
case "status":
return {
authenticated: WPP.conn.isAuthenticated(),
ready: WPP.isReady,
deviceId: consts.DEVICE_ID,
deviceName: consts.DEVICE_NAME,
};
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.list();
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((i) => i.groupMetadata != null)
.map((i) => serializeGroupMetadata(i.groupMetadata));
}
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 "sendText": {
const result = await WPP.chat.sendTextMessage(
request.params.chatId,
request.params.text,
request.params.options,
);
return serialize(result);
}
case "sendFile": {
const result = await WPP.chat.sendFileMessage(
request.params.chatId,
request.params.data,
request.params.options,
);
return serialize(result);
}
case "getMedia":
const media = await WPP.chat
.downloadMedia(request.params)
.then(WPP.util.blobToBase64);
return media;
case "exec":
const fn = new Function(request.params);
return fn();
default:
throw new Error(`Unknown method: ${(request as any).method}`);
}
}
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", __WS_URL__);
send({
type: "event",
event: "ready",
data: {
version: __VERSION__,
authenticated: WPP.conn.isAuthenticated(),
deviceId: consts.DEVICE_ID,
deviceName: consts.DEVICE_NAME,
},
});
};
socket.onmessage = async (event) => {
try {
const request = JSON.parse(event.data) as BridgeRequest;
if (request.type === "ping") {
send({ type: "pong" });
}
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.on("chat.new_message", (message: unknown) => {
if (socket?.readyState !== WebSocket.OPEN) {
return;
}
send({
type: "event",
event: "message",
data: serializeMessage(message),
});
});
});
}
init();
start();