// ==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://git.rul.sh/khairul169/whatsapp-bridge/raw/branch/main/dist/whatsapp-bridge.user.js // @downloadURL https://git.rul.sh/khairul169/whatsapp-bridge/raw/branch/main/dist/whatsapp-bridge.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 = "wss://wa-bridge.rul.sh/ws"; 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