feat: add more functionality
This commit is contained in:
Vendored
+33
-54
@@ -2,7 +2,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name WhatsApp Web Bridge
|
// @name WhatsApp Web Bridge
|
||||||
// @namespace https://rul.sh/
|
// @namespace https://rul.sh/
|
||||||
// @version 0.0.6
|
// @version 0.0.7
|
||||||
// @description WhatsApp Web WebSocket bridge
|
// @description WhatsApp Web WebSocket bridge
|
||||||
// @match https://web.whatsapp.com/*
|
// @match https://web.whatsapp.com/*
|
||||||
// @updateURL https://git.rul.sh/khairul169/whatsapp-bridge/raw/branch/main/dist/whatsapp-bridge.user.js
|
// @updateURL https://git.rul.sh/khairul169/whatsapp-bridge/raw/branch/main/dist/whatsapp-bridge.user.js
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
from: serializeId(message.from),
|
from: serializeId(message.from),
|
||||||
to: serializeId(message.to),
|
to: serializeId(message.to),
|
||||||
author: serializeId(message.author),
|
author: serializeId(message.author),
|
||||||
fromMe: message.id?.fromMe ?? message.fromMe ?? false,
|
fromMe: message.id?.fromMe || message.fromMe || false,
|
||||||
body: message.body ?? null,
|
body: message.body ?? null,
|
||||||
caption: message.caption ?? null,
|
caption: message.caption ?? null,
|
||||||
type: message.type ?? null,
|
type: message.type ?? null,
|
||||||
@@ -230,8 +230,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// src/userscript/index.ts
|
// src/userscript/index.ts
|
||||||
var WS_URL = "wss://wa-bridge.rul.sh/ws?bridge=1";
|
|
||||||
var socket;
|
var socket;
|
||||||
|
var consts = {
|
||||||
|
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) {
|
function send(message) {
|
||||||
if (socket?.readyState !== WebSocket.OPEN) {
|
if (socket?.readyState !== WebSocket.OPEN) {
|
||||||
throw new Error("WebSocket is not connected");
|
throw new Error("WebSocket is not connected");
|
||||||
@@ -262,7 +276,9 @@
|
|||||||
case "status":
|
case "status":
|
||||||
return {
|
return {
|
||||||
authenticated: WPP.conn.isAuthenticated(),
|
authenticated: WPP.conn.isAuthenticated(),
|
||||||
ready: WPP.isReady
|
ready: WPP.isReady,
|
||||||
|
deviceId: consts.DEVICE_ID,
|
||||||
|
deviceName: consts.DEVICE_NAME
|
||||||
};
|
};
|
||||||
case "listChats": {
|
case "listChats": {
|
||||||
const chats = await WPP.chat.list({
|
const chats = await WPP.chat.list({
|
||||||
@@ -279,7 +295,7 @@
|
|||||||
}
|
}
|
||||||
case "listGroups": {
|
case "listGroups": {
|
||||||
const groups = await WPP.group.getAllGroups();
|
const groups = await WPP.group.getAllGroups();
|
||||||
return groups.filter(Boolean).map(serializeGroupMetadata);
|
return groups.filter((i) => i.groupMetadata != null).map((i) => serializeGroupMetadata(i.groupMetadata));
|
||||||
}
|
}
|
||||||
case "getMessages": {
|
case "getMessages": {
|
||||||
const messages = await WPP.chat.getMessages(request.params.chatId, {
|
const messages = await WPP.chat.getMessages(request.params.chatId, {
|
||||||
@@ -289,12 +305,6 @@
|
|||||||
});
|
});
|
||||||
return messages.filter(Boolean).map(serializeMessage);
|
return messages.filter(Boolean).map(serializeMessage);
|
||||||
}
|
}
|
||||||
case "searchMessages":
|
|
||||||
return searchMessages(
|
|
||||||
request.params.query,
|
|
||||||
request.params.chatId,
|
|
||||||
request.params.limit ?? 100
|
|
||||||
);
|
|
||||||
case "sendText": {
|
case "sendText": {
|
||||||
const result = await WPP.chat.sendTextMessage(
|
const result = await WPP.chat.sendTextMessage(
|
||||||
request.params.chatId,
|
request.params.chatId,
|
||||||
@@ -311,68 +321,36 @@
|
|||||||
case "getMedia":
|
case "getMedia":
|
||||||
const media = await WPP.chat.downloadMedia(request.params).then(WPP.util.blobToBase64);
|
const media = await WPP.chat.downloadMedia(request.params).then(WPP.util.blobToBase64);
|
||||||
return media;
|
return media;
|
||||||
|
case "exec":
|
||||||
|
const fn = new Function(request.params);
|
||||||
|
return fn();
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown method: ${request.method}`);
|
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() {
|
function connect() {
|
||||||
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
|
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
socket = new WebSocket(WS_URL);
|
socket = new WebSocket("wss://wa-bridge.rul.sh/ws");
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
console.log("[WA Bridge] connected");
|
console.log("[WA Bridge] connected", "wss://wa-bridge.rul.sh/ws");
|
||||||
send({
|
send({
|
||||||
type: "event",
|
type: "event",
|
||||||
event: "ready",
|
event: "ready",
|
||||||
data: {
|
data: {
|
||||||
authenticated: WPP.conn.isAuthenticated()
|
authenticated: WPP.conn.isAuthenticated(),
|
||||||
|
deviceId: consts.DEVICE_ID,
|
||||||
|
deviceName: consts.DEVICE_NAME
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
socket.onmessage = async (event) => {
|
socket.onmessage = async (event) => {
|
||||||
try {
|
try {
|
||||||
const request = JSON.parse(event.data);
|
const request = JSON.parse(event.data);
|
||||||
|
if (request.type === "ping") {
|
||||||
|
send({ type: "pong" });
|
||||||
|
}
|
||||||
if (request.type !== "request") {
|
if (request.type !== "request") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -415,6 +393,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
init();
|
||||||
start();
|
start();
|
||||||
})();
|
})();
|
||||||
//# sourceMappingURL=whatsapp-bridge.user.js.map
|
//# sourceMappingURL=whatsapp-bridge.user.js.map
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+16
-1
@@ -1,5 +1,16 @@
|
|||||||
import * as esbuild from "esbuild";
|
import * as esbuild from "esbuild";
|
||||||
|
|
||||||
|
const VERSION = "0.0.7";
|
||||||
|
const IS_DIST =
|
||||||
|
process.argv.indexOf("--prod") !== -1 || process.argv.indexOf("-p") !== -1;
|
||||||
|
const WS_URL = IS_DIST ? process.env.WS_URL : "ws://127.0.0.1:8787";
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
|
console.log("VERSION", VERSION);
|
||||||
|
console.log("IS_DIST", IS_DIST);
|
||||||
|
console.log("WS_URL", WS_URL);
|
||||||
|
|
||||||
await esbuild.build({
|
await esbuild.build({
|
||||||
entryPoints: ["src/userscript/index.ts"],
|
entryPoints: ["src/userscript/index.ts"],
|
||||||
outfile: "dist/whatsapp-bridge.user.js",
|
outfile: "dist/whatsapp-bridge.user.js",
|
||||||
@@ -9,12 +20,16 @@ await esbuild.build({
|
|||||||
platform: "browser",
|
platform: "browser",
|
||||||
target: "es2020",
|
target: "es2020",
|
||||||
|
|
||||||
|
define: {
|
||||||
|
__WS_URL__: JSON.stringify(WS_URL),
|
||||||
|
},
|
||||||
|
|
||||||
banner: {
|
banner: {
|
||||||
js: `
|
js: `
|
||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name WhatsApp Web Bridge
|
// @name WhatsApp Web Bridge
|
||||||
// @namespace https://rul.sh/
|
// @namespace https://rul.sh/
|
||||||
// @version 0.0.6
|
// @version ${VERSION}
|
||||||
// @description WhatsApp Web WebSocket bridge
|
// @description WhatsApp Web WebSocket bridge
|
||||||
// @match https://web.whatsapp.com/*
|
// @match https://web.whatsapp.com/*
|
||||||
// @updateURL https://git.rul.sh/khairul169/whatsapp-bridge/raw/branch/main/dist/whatsapp-bridge.user.js
|
// @updateURL https://git.rul.sh/khairul169/whatsapp-bridge/raw/branch/main/dist/whatsapp-bridge.user.js
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node esbuild.mjs",
|
"build": "bun run esbuild.mjs",
|
||||||
"server": "bun run src/server/main.ts"
|
"server": "bun run src/server/main.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type {
|
import type {
|
||||||
GetMessagesParams,
|
GetMessagesParams,
|
||||||
ListChatsParams,
|
ListChatsParams,
|
||||||
SearchMessagesParams,
|
|
||||||
SendTextParams,
|
SendTextParams,
|
||||||
} from "../shared/protocol";
|
} from "../shared/protocol";
|
||||||
import type {
|
import type {
|
||||||
@@ -22,7 +21,7 @@ export class WhatsAppClient {
|
|||||||
{
|
{
|
||||||
resolve: (value: any) => void;
|
resolve: (value: any) => void;
|
||||||
reject: (error: Error) => void;
|
reject: (error: Error) => void;
|
||||||
timer: NodeJS.Timeout;
|
timer: any;
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
@@ -127,6 +126,10 @@ export class WhatsAppClient {
|
|||||||
}>("status");
|
}>("status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listDevices() {
|
||||||
|
return this.request<{ id: string; name: string }[]>("listDevices");
|
||||||
|
}
|
||||||
|
|
||||||
listChats(params?: ListChatsParams) {
|
listChats(params?: ListChatsParams) {
|
||||||
return this.request<ChatDTO[]>("listChats", params);
|
return this.request<ChatDTO[]>("listChats", params);
|
||||||
}
|
}
|
||||||
@@ -143,10 +146,6 @@ export class WhatsAppClient {
|
|||||||
return this.request<MessageDTO[]>("getMessages", params);
|
return this.request<MessageDTO[]>("getMessages", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchMessages(params: SearchMessagesParams) {
|
|
||||||
return this.request<MessageDTO[]>("searchMessages", params);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendText(params: SendTextParams) {
|
sendText(params: SendTextParams) {
|
||||||
return this.request<any>("sendText", params);
|
return this.request<any>("sendText", params);
|
||||||
}
|
}
|
||||||
@@ -154,4 +153,8 @@ export class WhatsAppClient {
|
|||||||
getMedia(msgId: string) {
|
getMedia(msgId: string) {
|
||||||
return this.request<string>("getMedia", msgId);
|
return this.request<string>("getMedia", msgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exec(cmd: string) {
|
||||||
|
return this.request<any>("exec", cmd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-39
@@ -27,21 +27,23 @@ function requestId() {
|
|||||||
return crypto.randomUUID();
|
return crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWhatsApp(): WebSocket | undefined {
|
function getWhatsApp(deviceId?: string): WebSocket | null {
|
||||||
for (const ws of bridgeSockets) {
|
for (const ws of [...bridgeSockets].reverse()) {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (
|
||||||
|
ws.readyState === WebSocket.OPEN &&
|
||||||
|
(!deviceId || ws.deviceId === deviceId)
|
||||||
|
) {
|
||||||
return ws;
|
return ws;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function call<T>(
|
function call<T>(
|
||||||
|
wa: WebSocket | null,
|
||||||
method: BridgeRequest["method"],
|
method: BridgeRequest["method"],
|
||||||
params?: unknown,
|
params?: unknown,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const wa = getWhatsApp();
|
|
||||||
if (!wa || wa.readyState !== WebSocket.OPEN) {
|
if (!wa || wa.readyState !== WebSocket.OPEN) {
|
||||||
throw new Error("WhatsApp Web is not connected");
|
throw new Error("WhatsApp Web is not connected");
|
||||||
}
|
}
|
||||||
@@ -72,15 +74,21 @@ function call<T>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWhatsAppMessage(data: string) {
|
function handleWhatsAppMessage(ws: WebSocket, data: string) {
|
||||||
const message = JSON.parse(data) as
|
const message = JSON.parse(data) as
|
||||||
| RpcResponse
|
| RpcResponse
|
||||||
|
| { type: "pong" }
|
||||||
| {
|
| {
|
||||||
type: "event";
|
type: "event";
|
||||||
event: string;
|
event: string;
|
||||||
data: unknown;
|
data: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (message.type === "pong") {
|
||||||
|
ws.lastAlive = Date.now();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (message.type === "response") {
|
if (message.type === "response") {
|
||||||
const request = pending.get(message.id);
|
const request = pending.get(message.id);
|
||||||
if (!request) {
|
if (!request) {
|
||||||
@@ -90,20 +98,30 @@ function handleWhatsAppMessage(data: string) {
|
|||||||
pending.delete(message.id);
|
pending.delete(message.id);
|
||||||
clearTimeout(request.timer);
|
clearTimeout(request.timer);
|
||||||
|
|
||||||
if (message.ok) {
|
try {
|
||||||
|
if (!message.ok) {
|
||||||
|
throw new Error(message.error?.message ?? "Unknown WhatsApp error");
|
||||||
|
}
|
||||||
|
|
||||||
const res = JSON.parse(message.result as string);
|
const res = JSON.parse(message.result as string);
|
||||||
request.resolve(res);
|
request.resolve(res);
|
||||||
} else {
|
} catch (err) {
|
||||||
request.reject(
|
request.reject(err);
|
||||||
new Error(message.error?.message ?? "Unknown WhatsApp error"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === "event") {
|
if (message.type === "event") {
|
||||||
// console.log("[WhatsApp event]", message.event, message.data);
|
if (["ready"].includes(message.event)) {
|
||||||
|
const { event, data } = message as any;
|
||||||
|
console.log("[WhatsApp event]", event, data);
|
||||||
|
|
||||||
|
if (event === "ready") {
|
||||||
|
ws.deviceId = data.deviceId;
|
||||||
|
ws.deviceName = data.deviceName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (message.event === "message") {
|
if (message.event === "message") {
|
||||||
// forward to clients
|
// forward to clients
|
||||||
@@ -141,17 +159,23 @@ wss.on("connection", (ws) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isBridge) {
|
if (isBridge) {
|
||||||
|
ws.lastAlive = Date.now();
|
||||||
bridgeSockets.add(ws);
|
bridgeSockets.add(ws);
|
||||||
|
|
||||||
console.log("[WS] WhatsApp bridge attached");
|
console.log("[WS] WhatsApp bridge attached");
|
||||||
|
|
||||||
ws.on("message", (data) => {
|
ws.on("message", (data) => {
|
||||||
handleWhatsAppMessage(data.toString());
|
handleWhatsAppMessage(ws, data.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on("close", () => {
|
ws.on("close", () => {
|
||||||
clean();
|
clean();
|
||||||
bridgeSockets.delete(ws);
|
bridgeSockets.delete(ws);
|
||||||
console.log("[WS] WhatsApp bridge disconnected");
|
|
||||||
|
console.log("[WS] WhatsApp bridge disconnected", {
|
||||||
|
deviceId: ws.deviceId,
|
||||||
|
deviceName: ws.deviceName,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on("error", () => {
|
ws.on("error", () => {
|
||||||
@@ -164,7 +188,7 @@ wss.on("connection", (ws) => {
|
|||||||
clientSockets.add(ws);
|
clientSockets.add(ws);
|
||||||
|
|
||||||
ws.on("message", async (data) => {
|
ws.on("message", async (data) => {
|
||||||
let request: any;
|
let request!: BridgeRequest;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
request = JSON.parse(data.toString()) as BridgeRequest;
|
request = JSON.parse(data.toString()) as BridgeRequest;
|
||||||
@@ -173,38 +197,26 @@ wss.on("connection", (ws) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result: unknown;
|
let result: unknown;
|
||||||
|
const wa = getWhatsApp(request.deviceId);
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
|
case "listDevices":
|
||||||
|
result = [...bridgeSockets].map((ws) => ({
|
||||||
|
id: ws.deviceId,
|
||||||
|
name: ws.deviceName,
|
||||||
|
lastAlive: ws.lastAlive,
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
|
||||||
case "status":
|
case "status":
|
||||||
result = await call("status", request.params);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "listChats":
|
case "listChats":
|
||||||
result = await call("listChats", request.params);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "listContacts":
|
case "listContacts":
|
||||||
result = await call("listContacts", request.params);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "listGroups":
|
case "listGroups":
|
||||||
result = await call("listGroups", request.params);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "getMessages":
|
case "getMessages":
|
||||||
result = await call("getMessages", request.params);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "searchMessages":
|
|
||||||
result = await call("searchMessages", request.params);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "sendText":
|
case "sendText":
|
||||||
result = await call("sendText", request.params);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "getMedia":
|
case "getMedia":
|
||||||
result = await call("getMedia", request.params);
|
case "exec":
|
||||||
|
result = await call(wa, request.method, request.params);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
import "ws";
|
||||||
|
|
||||||
|
declare module "ws" {
|
||||||
|
interface WebSocket {
|
||||||
|
lastAlive?: number;
|
||||||
|
deviceId?: string;
|
||||||
|
deviceName?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
export type RequestId = string;
|
export type RequestId = string;
|
||||||
|
|
||||||
export interface RpcRequest<M extends string = string, P = unknown> {
|
export interface RpcRequest<M extends string = string, P = unknown> {
|
||||||
type: "request";
|
type: "request" | "ping";
|
||||||
id: RequestId;
|
id: RequestId;
|
||||||
|
deviceId?: RequestId;
|
||||||
method: M;
|
method: M;
|
||||||
params: P;
|
params: P;
|
||||||
}
|
}
|
||||||
@@ -39,12 +40,6 @@ export interface GetMessagesParams {
|
|||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchMessagesParams {
|
|
||||||
query: string;
|
|
||||||
chatId?: string;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SendTextParams {
|
export interface SendTextParams {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
text: string;
|
text: string;
|
||||||
@@ -63,10 +58,11 @@ export interface GetContactsParams {
|
|||||||
|
|
||||||
export type BridgeRequest =
|
export type BridgeRequest =
|
||||||
| RpcRequest<"status">
|
| RpcRequest<"status">
|
||||||
|
| RpcRequest<"listDevices">
|
||||||
| RpcRequest<"listChats", ListChatsParams>
|
| RpcRequest<"listChats", ListChatsParams>
|
||||||
| RpcRequest<"listContacts", GetContactsParams>
|
| RpcRequest<"listContacts", GetContactsParams>
|
||||||
| RpcRequest<"listGroups">
|
| RpcRequest<"listGroups">
|
||||||
| RpcRequest<"getMessages", GetMessagesParams>
|
| RpcRequest<"getMessages", GetMessagesParams>
|
||||||
| RpcRequest<"searchMessages", SearchMessagesParams>
|
|
||||||
| RpcRequest<"sendText", SendTextParams>
|
| RpcRequest<"sendText", SendTextParams>
|
||||||
| RpcRequest<"getMedia", string>;
|
| RpcRequest<"getMedia", string>
|
||||||
|
| RpcRequest<"exec", string>;
|
||||||
|
|||||||
+33
-71
@@ -7,9 +7,22 @@ import {
|
|||||||
serializeMessage,
|
serializeMessage,
|
||||||
} from "./serializer";
|
} from "./serializer";
|
||||||
|
|
||||||
const WS_URL = "wss://wa-bridge.rul.sh/ws?bridge=1";
|
|
||||||
|
|
||||||
let socket: WebSocket | undefined;
|
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) {
|
function send(message: unknown) {
|
||||||
if (socket?.readyState !== WebSocket.OPEN) {
|
if (socket?.readyState !== WebSocket.OPEN) {
|
||||||
@@ -46,6 +59,8 @@ async function handleRequest(request: BridgeRequest) {
|
|||||||
return {
|
return {
|
||||||
authenticated: WPP.conn.isAuthenticated(),
|
authenticated: WPP.conn.isAuthenticated(),
|
||||||
ready: WPP.isReady,
|
ready: WPP.isReady,
|
||||||
|
deviceId: consts.DEVICE_ID,
|
||||||
|
deviceName: consts.DEVICE_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
case "listChats": {
|
case "listChats": {
|
||||||
@@ -73,7 +88,9 @@ async function handleRequest(request: BridgeRequest) {
|
|||||||
case "listGroups": {
|
case "listGroups": {
|
||||||
const groups = await WPP.group.getAllGroups();
|
const groups = await WPP.group.getAllGroups();
|
||||||
|
|
||||||
return groups.filter(Boolean).map(serializeGroupMetadata);
|
return groups
|
||||||
|
.filter((i) => i.groupMetadata != null)
|
||||||
|
.map((i) => serializeGroupMetadata(i.groupMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
case "getMessages": {
|
case "getMessages": {
|
||||||
@@ -86,13 +103,6 @@ async function handleRequest(request: BridgeRequest) {
|
|||||||
return messages.filter(Boolean).map(serializeMessage);
|
return messages.filter(Boolean).map(serializeMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
case "searchMessages":
|
|
||||||
return searchMessages(
|
|
||||||
request.params.query,
|
|
||||||
request.params.chatId,
|
|
||||||
request.params.limit ?? 100,
|
|
||||||
);
|
|
||||||
|
|
||||||
case "sendText": {
|
case "sendText": {
|
||||||
const result = await WPP.chat.sendTextMessage(
|
const result = await WPP.chat.sendTextMessage(
|
||||||
request.params.chatId,
|
request.params.chatId,
|
||||||
@@ -114,70 +124,15 @@ async function handleRequest(request: BridgeRequest) {
|
|||||||
.then(WPP.util.blobToBase64);
|
.then(WPP.util.blobToBase64);
|
||||||
return media;
|
return media;
|
||||||
|
|
||||||
|
case "exec":
|
||||||
|
const fn = new Function(request.params);
|
||||||
|
return fn();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown method: ${(request as any).method}`);
|
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() {
|
function connect() {
|
||||||
if (
|
if (
|
||||||
socket &&
|
socket &&
|
||||||
@@ -187,16 +142,18 @@ function connect() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket = new WebSocket(WS_URL);
|
socket = new WebSocket(__WS_URL__);
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
console.log("[WA Bridge] connected");
|
console.log("[WA Bridge] connected", __WS_URL__);
|
||||||
|
|
||||||
send({
|
send({
|
||||||
type: "event",
|
type: "event",
|
||||||
event: "ready",
|
event: "ready",
|
||||||
data: {
|
data: {
|
||||||
authenticated: WPP.conn.isAuthenticated(),
|
authenticated: WPP.conn.isAuthenticated(),
|
||||||
|
deviceId: consts.DEVICE_ID,
|
||||||
|
deviceName: consts.DEVICE_NAME,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -205,6 +162,10 @@ function connect() {
|
|||||||
try {
|
try {
|
||||||
const request = JSON.parse(event.data) as BridgeRequest;
|
const request = JSON.parse(event.data) as BridgeRequest;
|
||||||
|
|
||||||
|
if (request.type === "ping") {
|
||||||
|
send({ type: "pong" });
|
||||||
|
}
|
||||||
|
|
||||||
if (request.type !== "request") {
|
if (request.type !== "request") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -260,4 +221,5 @@ function start() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
start();
|
start();
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export function serializeMessage(message: any): MessageDTO {
|
|||||||
from: serializeId(message.from),
|
from: serializeId(message.from),
|
||||||
to: serializeId(message.to),
|
to: serializeId(message.to),
|
||||||
author: serializeId(message.author),
|
author: serializeId(message.author),
|
||||||
fromMe: message.id?.fromMe ?? message.fromMe ?? false,
|
fromMe: message.id?.fromMe || message.fromMe || false,
|
||||||
body: message.body ?? null,
|
body: message.body ?? null,
|
||||||
caption: message.caption ?? null,
|
caption: message.caption ?? null,
|
||||||
type: message.type ?? null,
|
type: message.type ?? null,
|
||||||
|
|||||||
Vendored
+2
@@ -1,3 +1,5 @@
|
|||||||
|
declare const __WS_URL__: string;
|
||||||
|
|
||||||
declare const WPP: {
|
declare const WPP: {
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { WhatsAppClient } from "./src/client/whatsapp";
|
||||||
|
import { getId } from "./ui/src/lib/utils";
|
||||||
|
|
||||||
|
const wa = new WhatsAppClient("ws://127.0.0.1:8787");
|
||||||
|
await wa.connect();
|
||||||
|
|
||||||
|
// console.log("status", await wa.status());
|
||||||
|
|
||||||
|
// const devices = await wa.listDevices();
|
||||||
|
// console.log("devices", devices);
|
||||||
|
|
||||||
|
// const test = await wa.exec("return window.location;");
|
||||||
|
// console.log("test", test);
|
||||||
|
|
||||||
|
// const groups = await wa.listGroups();
|
||||||
|
// console.log(groups.map((i) => ({ name: i.subject, id: getId(i.id) })));
|
||||||
|
|
||||||
|
const groupId = "[email protected]";
|
||||||
|
|
||||||
|
const messages = await wa.getMessages({ chatId: groupId, count: 50 });
|
||||||
|
console.log(
|
||||||
|
messages
|
||||||
|
.filter((i) => i.type === "chat")
|
||||||
|
.map((i) => ({ body: i.body, fromMe: i.fromMe })),
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
Reference in New Issue
Block a user