diff --git a/dist/whatsapp-bridge.user.js b/dist/whatsapp-bridge.user.js index eb5d0b3..6926fd0 100644 --- a/dist/whatsapp-bridge.user.js +++ b/dist/whatsapp-bridge.user.js @@ -2,7 +2,7 @@ // ==UserScript== // @name WhatsApp Web Bridge // @namespace https://rul.sh/ -// @version 0.0.6 +// @version 0.0.7 // @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 @@ -201,7 +201,7 @@ from: serializeId(message.from), to: serializeId(message.to), author: serializeId(message.author), - fromMe: message.id?.fromMe ?? message.fromMe ?? false, + fromMe: message.id?.fromMe || message.fromMe || false, body: message.body ?? null, caption: message.caption ?? null, type: message.type ?? null, @@ -230,8 +230,22 @@ } // src/userscript/index.ts - var WS_URL = "wss://wa-bridge.rul.sh/ws?bridge=1"; 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) { if (socket?.readyState !== WebSocket.OPEN) { throw new Error("WebSocket is not connected"); @@ -262,7 +276,9 @@ case "status": return { authenticated: WPP.conn.isAuthenticated(), - ready: WPP.isReady + ready: WPP.isReady, + deviceId: consts.DEVICE_ID, + deviceName: consts.DEVICE_NAME }; case "listChats": { const chats = await WPP.chat.list({ @@ -279,7 +295,7 @@ } case "listGroups": { 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": { const messages = await WPP.chat.getMessages(request.params.chatId, { @@ -289,12 +305,6 @@ }); 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, @@ -311,68 +321,36 @@ 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.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 = new WebSocket("wss://wa-bridge.rul.sh/ws"); socket.onopen = () => { - console.log("[WA Bridge] connected"); + console.log("[WA Bridge] connected", "wss://wa-bridge.rul.sh/ws"); send({ type: "event", event: "ready", data: { - authenticated: WPP.conn.isAuthenticated() + authenticated: WPP.conn.isAuthenticated(), + deviceId: consts.DEVICE_ID, + deviceName: consts.DEVICE_NAME } }); }; socket.onmessage = async (event) => { try { const request = JSON.parse(event.data); + if (request.type === "ping") { + send({ type: "pong" }); + } if (request.type !== "request") { return; } @@ -415,6 +393,7 @@ }); }); } + init(); start(); })(); //# sourceMappingURL=whatsapp-bridge.user.js.map diff --git a/dist/whatsapp-bridge.user.js.map b/dist/whatsapp-bridge.user.js.map index ff24f60..938bdd6 100644 --- a/dist/whatsapp-bridge.user.js.map +++ b/dist/whatsapp-bridge.user.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/userscript/serializer.ts", "../src/userscript/index.ts"], - "sourcesContent": ["import type {\r\n ChatDTO,\r\n ContactDTO,\r\n GroupMetadataDTO,\r\n GroupParticipantDTO,\r\n LocationDTO,\r\n MessageDTO,\r\n MessageIdDTO,\r\n SerializedId,\r\n} from \"../shared/dto\";\r\n\r\n/**\r\n * WA models contain a lot of internal properties and sometimes\r\n * circular references. Never JSON.stringify them directly.\r\n */\r\nexport function serialize(value: any): any {\r\n if (value == null) {\r\n return value;\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return value.map(serialize);\r\n }\r\n\r\n if (typeof value !== \"object\") {\r\n return value;\r\n }\r\n\r\n const result: Record = {};\r\n\r\n for (const key of Object.keys(value)) {\r\n const v = value[key];\r\n\r\n if (\r\n typeof v === \"string\" ||\r\n typeof v === \"number\" ||\r\n typeof v === \"boolean\" ||\r\n v === null\r\n ) {\r\n result[key] = v;\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction safeProperty(object: unknown, key: string): T | undefined {\r\n try {\r\n if (object && typeof object === \"object\") {\r\n return (object as any)[key];\r\n }\r\n } catch {\r\n return undefined;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nexport function safeGet(\r\n object: unknown,\r\n key: string,\r\n): T | undefined {\r\n if (object == null || typeof object !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n try {\r\n return (object as any)[key];\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nexport function pick(\r\n source: any,\r\n keys: readonly string[],\r\n): Partial {\r\n const result: Record = {};\r\n\r\n for (const key of keys) {\r\n const value = safeGet(source, key);\r\n\r\n if (value !== undefined) {\r\n result[key] = serialize(value);\r\n }\r\n }\r\n\r\n return result as Partial;\r\n}\r\n\r\nexport function serializeId(id: any): SerializedId | string | null {\r\n if (id == null) {\r\n return null;\r\n }\r\n\r\n if (typeof id === \"string\") {\r\n return id;\r\n }\r\n\r\n if (typeof id !== \"object\") {\r\n return String(id);\r\n }\r\n\r\n const result: SerializedId = {};\r\n\r\n for (const key of [\r\n \"_serialized\",\r\n \"user\",\r\n \"server\",\r\n \"domain\",\r\n \"device\",\r\n \"agent\",\r\n \"fromMe\",\r\n \"remote\",\r\n \"participant\",\r\n ]) {\r\n const value = safeGet(id, key);\r\n\r\n if (value !== undefined) {\r\n result[key] = serialize(value) as any;\r\n }\r\n }\r\n\r\n // Some WA objects expose useful fields that aren't\r\n // enumerable.\r\n if (!result._serialized && typeof safeGet(id, \"toString\") === \"function\") {\r\n try {\r\n const serialized = id.toString();\r\n\r\n if (serialized && serialized !== \"[object Object]\") {\r\n result._serialized = serialized;\r\n }\r\n } catch {}\r\n }\r\n\r\n return Object.keys(result).length ? result : String(id);\r\n}\r\n\r\nexport function serializeChat(chat: any): ChatDTO {\r\n const rawPreviewMessage = safeProperty(chat, \"previewMessage\");\r\n\r\n return {\r\n id: serializeId(chat.id),\r\n accountLid: chat.accountLid,\r\n name: chat.name,\r\n formattedTitle: chat.formattedTitle,\r\n\r\n isGroup: chat.isGroup,\r\n isUser: chat.isUser,\r\n isReadOnly: chat.isReadOnly,\r\n archived: chat.archived,\r\n unreadCount: chat.unreadCount,\r\n previewMessage: rawPreviewMessage\r\n ? serializeMessage(rawPreviewMessage)\r\n : null,\r\n\r\n timestamp: chat.t,\r\n lastMessage: chat.lastMessage ? serialize(chat.lastMessage) : null,\r\n };\r\n}\r\n\r\nexport function serializeContact(contact: any): ContactDTO {\r\n return {\r\n id: serializeId(contact.id),\r\n\r\n accountLid: contact.accountLid ?? null,\r\n name: contact.name ?? null,\r\n pushname: contact.pushname ?? null,\r\n shortName: contact.shortName ?? null,\r\n formattedName: contact.formattedName ?? null,\r\n notifyName: contact.notifyName ?? null,\r\n number: contact.number ?? null,\r\n\r\n isMe: contact.isMe ?? false,\r\n isUser: contact.isUser ?? false,\r\n isBusiness: contact.isBusiness ?? false,\r\n isEnterprise: contact.isEnterprise ?? false,\r\n isGroup: contact.isGroup ?? false,\r\n isMyContact: contact.isMyContact ?? false,\r\n isBlocked: contact.isBlocked ?? false,\r\n\r\n profilePicThumbObj: contact.profilePicThumbObj\r\n ? serialize(contact.profilePicThumbObj)\r\n : undefined,\r\n };\r\n}\r\n\r\nexport function serializeGroupParticipant(\r\n participant: any,\r\n): GroupParticipantDTO {\r\n return {\r\n id: serializeId(participant.id),\r\n\r\n isAdmin: participant.isAdmin ?? false,\r\n isSuperAdmin: participant.isSuperAdmin ?? false,\r\n isSuperParticipant: participant.isSuperParticipant ?? false,\r\n isBusiness: participant.isBusiness ?? false,\r\n };\r\n}\r\n\r\nexport function serializeGroupMetadata(metadata: any): GroupMetadataDTO {\r\n return {\r\n id: serializeId(metadata.id),\r\n\r\n subject: metadata.subject ?? null,\r\n subjectOwner: serializeId(metadata.subjectOwner),\r\n subjectTime: metadata.subjectTime ?? null,\r\n creation: metadata.creation ?? null,\r\n owner: serializeId(metadata.owner),\r\n\r\n participants: Array.isArray(metadata.participants)\r\n ? (metadata.participants\r\n .map(serializeGroupParticipant)\r\n .filter(Boolean) as GroupParticipantDTO[])\r\n : [],\r\n\r\n size: metadata.size ?? metadata.participants?.length ?? 0,\r\n restrict: metadata.restrict ?? false,\r\n announce: metadata.announce ?? false,\r\n isCommunity: metadata.isCommunity ?? false,\r\n isCommunityAnnounce: metadata.isCommunityAnnounce ?? false,\r\n };\r\n}\r\nexport function serializeMessageId(id: any): MessageIdDTO | null {\r\n if (!id) {\r\n return null;\r\n }\r\n\r\n return {\r\n id: id.id ?? null,\r\n _serialized: id._serialized ?? null,\r\n fromMe: id.fromMe ?? false,\r\n remote: serializeId(id.remote),\r\n participant: serializeId(id.participant),\r\n };\r\n}\r\nexport function serializeLocation(location: any): LocationDTO | null {\r\n if (!location) {\r\n return null;\r\n }\r\n\r\n return {\r\n latitude: location.latitude ?? null,\r\n longitude: location.longitude ?? null,\r\n description: location.description ?? null,\r\n address: location.address ?? null,\r\n url: location.url ?? null,\r\n name: location.name ?? null,\r\n };\r\n}\r\nexport function serializeMessageLink(link: any): any {\r\n return {\r\n link: link?.link ?? null,\r\n isSuspicious: link?.isSuspicious ?? false,\r\n };\r\n}\r\nexport function serializeMessage(message: any): MessageDTO {\r\n const quotedMsgId = safeProperty(message, \"quotedMsgId\");\r\n const hasQuoted = !!quotedMsgId;\r\n let quotedMsg = null;\r\n\r\n if (hasQuoted) {\r\n const rawQuoted = safeProperty(message, \"quotedMsg\");\r\n if (rawQuoted) {\r\n quotedMsg = serializeMessage(rawQuoted);\r\n }\r\n }\r\n\r\n return {\r\n id: serializeMessageId(message.id)!,\r\n chatId: message.chatId ?? message.id?.remote?._serialized ?? null,\r\n from: serializeId(message.from),\r\n to: serializeId(message.to),\r\n author: serializeId(message.author),\r\n fromMe: message.id?.fromMe ?? message.fromMe ?? false,\r\n body: message.body ?? null,\r\n caption: message.caption ?? null,\r\n type: message.type ?? null,\r\n subtype: message.subtype ?? null,\r\n timestamp: message.t ?? message.timestamp ?? null,\r\n ack: message.ack ?? null,\r\n hasMedia: message.hasMedia ?? false,\r\n mediaKey: message.mediaKey ?? null,\r\n isForwarded: message.isForwarded ?? false,\r\n forwardingScore: message.forwardingScore ?? 0,\r\n isStarred: message.isStarred ?? false,\r\n broadcast: message.broadcast ?? false,\r\n notifyName: message.notifyName ?? null,\r\n mentionedIds: Array.isArray(message.mentionedIds)\r\n ? message.mentionedIds.map(serializeId)\r\n : [],\r\n quotedMsgId: serializeMessageId(quotedMsgId),\r\n quotedMsg,\r\n links: Array.isArray(message.links)\r\n ? message.links.map(serializeMessageLink)\r\n : [],\r\n location: serializeLocation(message.location),\r\n vCard: message.vCard ?? null,\r\n vCards: Array.isArray(message.vCards) ? message.vCards : [],\r\n filename: message.filename ?? null,\r\n mimetype: message.mimetype ?? null,\r\n size: message.size ?? null,\r\n duration: message.duration ?? null,\r\n };\r\n}\r\n", "import type { BridgeRequest } from \"../shared/protocol\";\r\nimport {\r\n serialize,\r\n serializeChat,\r\n serializeContact,\r\n serializeGroupMetadata,\r\n serializeMessage,\r\n} from \"./serializer\";\r\n\r\nconst WS_URL = \"wss://wa-bridge.rul.sh/ws?bridge=1\";\r\n\r\nlet socket: WebSocket | undefined;\r\n\r\nfunction send(message: unknown) {\r\n if (socket?.readyState !== WebSocket.OPEN) {\r\n throw new Error(\"WebSocket is not connected\");\r\n }\r\n\r\n socket.send(JSON.stringify(message));\r\n}\r\n\r\nfunction reply(id: string, result: unknown) {\r\n send({\r\n type: \"response\",\r\n id,\r\n ok: true,\r\n result: serialize(result),\r\n });\r\n}\r\n\r\nfunction error(id: string, err: unknown) {\r\n send({\r\n type: \"response\",\r\n id,\r\n ok: false,\r\n error: {\r\n code: \"ERROR\",\r\n message: err instanceof Error ? err.message : String(err),\r\n },\r\n });\r\n}\r\n\r\nasync function handleRequest(request: BridgeRequest) {\r\n switch (request.method) {\r\n case \"status\":\r\n return {\r\n authenticated: WPP.conn.isAuthenticated(),\r\n ready: WPP.isReady,\r\n };\r\n\r\n case \"listChats\": {\r\n const chats = await WPP.chat.list({\r\n ...(request.params?.limit != null\r\n ? { count: request.params.limit }\r\n : {}),\r\n ...(request.params?.groupsOnly ? { onlyGroups: true } : {}),\r\n ...(request.params?.usersOnly ? { onlyUsers: true } : {}),\r\n });\r\n\r\n return chats.filter(Boolean).map(serializeChat);\r\n }\r\n\r\n case \"listContacts\": {\r\n const contacts = await WPP.contact.list();\r\n\r\n const result = request.params?.limit\r\n ? contacts.slice(0, request.params.limit)\r\n : contacts;\r\n\r\n return result.filter(Boolean).map(serializeContact);\r\n }\r\n\r\n case \"listGroups\": {\r\n const groups = await WPP.group.getAllGroups();\r\n\r\n return groups.filter(Boolean).map(serializeGroupMetadata);\r\n }\r\n\r\n case \"getMessages\": {\r\n const messages = await WPP.chat.getMessages(request.params.chatId, {\r\n count: request.params.count ?? 50,\r\n direction: request.params.direction,\r\n id: request.params.id,\r\n });\r\n\r\n return messages.filter(Boolean).map(serializeMessage);\r\n }\r\n\r\n case \"searchMessages\":\r\n return searchMessages(\r\n request.params.query,\r\n request.params.chatId,\r\n request.params.limit ?? 100,\r\n );\r\n\r\n case \"sendText\": {\r\n const result = await WPP.chat.sendTextMessage(\r\n request.params.chatId,\r\n request.params.text,\r\n {\r\n delay: request.params.options?.delay,\r\n quotedMsg: request.params.options?.quotedMsg,\r\n mentionedList: request.params.options?.mentionedList,\r\n waitForAck: request.params.options?.waitForAck,\r\n },\r\n );\r\n\r\n return serialize(result);\r\n }\r\n\r\n case \"getMedia\":\r\n const media = await WPP.chat\r\n .downloadMedia(request.params)\r\n .then(WPP.util.blobToBase64);\r\n return media;\r\n\r\n default:\r\n throw new Error(`Unknown method: ${(request as any).method}`);\r\n }\r\n}\r\n\r\n/**\r\n * WA-JS doesn't need a separate \"global search\" API for our\r\n * first implementation. Search the messages loaded from chats.\r\n *\r\n * Later we can optimize this using WhatsApp's internal message\r\n * store/index if necessary.\r\n */\r\nasync function searchMessages(query: string, chatId?: string, limit = 100) {\r\n const normalized = query.toLowerCase();\r\n\r\n let chats;\r\n\r\n if (chatId) {\r\n const chat = WPP.chat.get(chatId);\r\n chats = chat ? [chat] : [];\r\n } else {\r\n chats = await WPP.chat.list();\r\n }\r\n\r\n const results: any[] = [];\r\n\r\n for (const chat of chats) {\r\n if (results.length >= limit) {\r\n break;\r\n }\r\n\r\n const id = chat?.id?._serialized ?? chat?.id ?? chat?.wid?._serialized;\r\n\r\n if (!id) {\r\n continue;\r\n }\r\n\r\n try {\r\n const messages = await WPP.chat.getMessages(id, {\r\n count: 100,\r\n });\r\n\r\n for (const message of messages) {\r\n const body = String(message.body ?? message.caption ?? \"\");\r\n\r\n if (body.toLowerCase().includes(normalized)) {\r\n results.push({\r\n chatId: id,\r\n message: serialize(message),\r\n });\r\n\r\n if (results.length >= limit) {\r\n break;\r\n }\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\"Failed searching chat\", id, err);\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n\r\nfunction connect() {\r\n if (\r\n socket &&\r\n (socket.readyState === WebSocket.OPEN ||\r\n socket.readyState === WebSocket.CONNECTING)\r\n ) {\r\n return;\r\n }\r\n\r\n socket = new WebSocket(WS_URL);\r\n\r\n socket.onopen = () => {\r\n console.log(\"[WA Bridge] connected\");\r\n\r\n send({\r\n type: \"event\",\r\n event: \"ready\",\r\n data: {\r\n authenticated: WPP.conn.isAuthenticated(),\r\n },\r\n });\r\n };\r\n\r\n socket.onmessage = async (event) => {\r\n try {\r\n const request = JSON.parse(event.data) as BridgeRequest;\r\n\r\n if (request.type !== \"request\") {\r\n return;\r\n }\r\n\r\n try {\r\n const result = await handleRequest(request);\r\n\r\n reply(request.id, JSON.stringify(result));\r\n } catch (err) {\r\n error(request.id, err);\r\n }\r\n } catch (err) {\r\n console.error(\"[WA Bridge] invalid message\", err);\r\n }\r\n };\r\n\r\n socket.onclose = (event) => {\r\n console.log(\"[WA Bridge] disconnected\", {\r\n code: event.code,\r\n reason: event.reason,\r\n wasClean: event.wasClean,\r\n });\r\n\r\n socket = undefined;\r\n setTimeout(connect, 3000);\r\n };\r\n\r\n socket.onerror = (event) => {\r\n console.error(\"[WA Bridge] websocket error\", event);\r\n };\r\n}\r\n\r\nfunction start() {\r\n WPP.loader.onReady(() => {\r\n console.log(\"[WA Bridge] WA-JS ready\");\r\n\r\n connect();\r\n\r\n /*\r\n * Forward new-message events to Node.\r\n */\r\n WPP.on(\"chat.new_message\", (message: unknown) => {\r\n if (socket?.readyState !== WebSocket.OPEN) {\r\n return;\r\n }\r\n\r\n send({\r\n type: \"event\",\r\n event: \"message\",\r\n data: serializeMessage(message),\r\n });\r\n });\r\n });\r\n}\r\n\r\nstart();\r\n"], - "mappings": "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", + "sourcesContent": ["import type {\r\n ChatDTO,\r\n ContactDTO,\r\n GroupMetadataDTO,\r\n GroupParticipantDTO,\r\n LocationDTO,\r\n MessageDTO,\r\n MessageIdDTO,\r\n SerializedId,\r\n} from \"../shared/dto\";\r\n\r\n/**\r\n * WA models contain a lot of internal properties and sometimes\r\n * circular references. Never JSON.stringify them directly.\r\n */\r\nexport function serialize(value: any): any {\r\n if (value == null) {\r\n return value;\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return value.map(serialize);\r\n }\r\n\r\n if (typeof value !== \"object\") {\r\n return value;\r\n }\r\n\r\n const result: Record = {};\r\n\r\n for (const key of Object.keys(value)) {\r\n const v = value[key];\r\n\r\n if (\r\n typeof v === \"string\" ||\r\n typeof v === \"number\" ||\r\n typeof v === \"boolean\" ||\r\n v === null\r\n ) {\r\n result[key] = v;\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\nfunction safeProperty(object: unknown, key: string): T | undefined {\r\n try {\r\n if (object && typeof object === \"object\") {\r\n return (object as any)[key];\r\n }\r\n } catch {\r\n return undefined;\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nexport function safeGet(\r\n object: unknown,\r\n key: string,\r\n): T | undefined {\r\n if (object == null || typeof object !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n try {\r\n return (object as any)[key];\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nexport function pick(\r\n source: any,\r\n keys: readonly string[],\r\n): Partial {\r\n const result: Record = {};\r\n\r\n for (const key of keys) {\r\n const value = safeGet(source, key);\r\n\r\n if (value !== undefined) {\r\n result[key] = serialize(value);\r\n }\r\n }\r\n\r\n return result as Partial;\r\n}\r\n\r\nexport function serializeId(id: any): SerializedId | string | null {\r\n if (id == null) {\r\n return null;\r\n }\r\n\r\n if (typeof id === \"string\") {\r\n return id;\r\n }\r\n\r\n if (typeof id !== \"object\") {\r\n return String(id);\r\n }\r\n\r\n const result: SerializedId = {};\r\n\r\n for (const key of [\r\n \"_serialized\",\r\n \"user\",\r\n \"server\",\r\n \"domain\",\r\n \"device\",\r\n \"agent\",\r\n \"fromMe\",\r\n \"remote\",\r\n \"participant\",\r\n ]) {\r\n const value = safeGet(id, key);\r\n\r\n if (value !== undefined) {\r\n result[key] = serialize(value) as any;\r\n }\r\n }\r\n\r\n // Some WA objects expose useful fields that aren't\r\n // enumerable.\r\n if (!result._serialized && typeof safeGet(id, \"toString\") === \"function\") {\r\n try {\r\n const serialized = id.toString();\r\n\r\n if (serialized && serialized !== \"[object Object]\") {\r\n result._serialized = serialized;\r\n }\r\n } catch {}\r\n }\r\n\r\n return Object.keys(result).length ? result : String(id);\r\n}\r\n\r\nexport function serializeChat(chat: any): ChatDTO {\r\n const rawPreviewMessage = safeProperty(chat, \"previewMessage\");\r\n\r\n return {\r\n id: serializeId(chat.id),\r\n accountLid: chat.accountLid,\r\n name: chat.name,\r\n formattedTitle: chat.formattedTitle,\r\n\r\n isGroup: chat.isGroup,\r\n isUser: chat.isUser,\r\n isReadOnly: chat.isReadOnly,\r\n archived: chat.archived,\r\n unreadCount: chat.unreadCount,\r\n previewMessage: rawPreviewMessage\r\n ? serializeMessage(rawPreviewMessage)\r\n : null,\r\n\r\n timestamp: chat.t,\r\n lastMessage: chat.lastMessage ? serialize(chat.lastMessage) : null,\r\n };\r\n}\r\n\r\nexport function serializeContact(contact: any): ContactDTO {\r\n return {\r\n id: serializeId(contact.id),\r\n\r\n accountLid: contact.accountLid ?? null,\r\n name: contact.name ?? null,\r\n pushname: contact.pushname ?? null,\r\n shortName: contact.shortName ?? null,\r\n formattedName: contact.formattedName ?? null,\r\n notifyName: contact.notifyName ?? null,\r\n number: contact.number ?? null,\r\n\r\n isMe: contact.isMe ?? false,\r\n isUser: contact.isUser ?? false,\r\n isBusiness: contact.isBusiness ?? false,\r\n isEnterprise: contact.isEnterprise ?? false,\r\n isGroup: contact.isGroup ?? false,\r\n isMyContact: contact.isMyContact ?? false,\r\n isBlocked: contact.isBlocked ?? false,\r\n\r\n profilePicThumbObj: contact.profilePicThumbObj\r\n ? serialize(contact.profilePicThumbObj)\r\n : undefined,\r\n };\r\n}\r\n\r\nexport function serializeGroupParticipant(\r\n participant: any,\r\n): GroupParticipantDTO {\r\n return {\r\n id: serializeId(participant.id),\r\n\r\n isAdmin: participant.isAdmin ?? false,\r\n isSuperAdmin: participant.isSuperAdmin ?? false,\r\n isSuperParticipant: participant.isSuperParticipant ?? false,\r\n isBusiness: participant.isBusiness ?? false,\r\n };\r\n}\r\n\r\nexport function serializeGroupMetadata(metadata: any): GroupMetadataDTO {\r\n return {\r\n id: serializeId(metadata.id),\r\n\r\n subject: metadata.subject ?? null,\r\n subjectOwner: serializeId(metadata.subjectOwner),\r\n subjectTime: metadata.subjectTime ?? null,\r\n creation: metadata.creation ?? null,\r\n owner: serializeId(metadata.owner),\r\n\r\n participants: Array.isArray(metadata.participants)\r\n ? (metadata.participants\r\n .map(serializeGroupParticipant)\r\n .filter(Boolean) as GroupParticipantDTO[])\r\n : [],\r\n\r\n size: metadata.size ?? metadata.participants?.length ?? 0,\r\n restrict: metadata.restrict ?? false,\r\n announce: metadata.announce ?? false,\r\n isCommunity: metadata.isCommunity ?? false,\r\n isCommunityAnnounce: metadata.isCommunityAnnounce ?? false,\r\n };\r\n}\r\nexport function serializeMessageId(id: any): MessageIdDTO | null {\r\n if (!id) {\r\n return null;\r\n }\r\n\r\n return {\r\n id: id.id ?? null,\r\n _serialized: id._serialized ?? null,\r\n fromMe: id.fromMe ?? false,\r\n remote: serializeId(id.remote),\r\n participant: serializeId(id.participant),\r\n };\r\n}\r\nexport function serializeLocation(location: any): LocationDTO | null {\r\n if (!location) {\r\n return null;\r\n }\r\n\r\n return {\r\n latitude: location.latitude ?? null,\r\n longitude: location.longitude ?? null,\r\n description: location.description ?? null,\r\n address: location.address ?? null,\r\n url: location.url ?? null,\r\n name: location.name ?? null,\r\n };\r\n}\r\nexport function serializeMessageLink(link: any): any {\r\n return {\r\n link: link?.link ?? null,\r\n isSuspicious: link?.isSuspicious ?? false,\r\n };\r\n}\r\nexport function serializeMessage(message: any): MessageDTO {\r\n const quotedMsgId = safeProperty(message, \"quotedMsgId\");\r\n const hasQuoted = !!quotedMsgId;\r\n let quotedMsg = null;\r\n\r\n if (hasQuoted) {\r\n const rawQuoted = safeProperty(message, \"quotedMsg\");\r\n if (rawQuoted) {\r\n quotedMsg = serializeMessage(rawQuoted);\r\n }\r\n }\r\n\r\n return {\r\n id: serializeMessageId(message.id)!,\r\n chatId: message.chatId ?? message.id?.remote?._serialized ?? null,\r\n from: serializeId(message.from),\r\n to: serializeId(message.to),\r\n author: serializeId(message.author),\r\n fromMe: message.id?.fromMe || message.fromMe || false,\r\n body: message.body ?? null,\r\n caption: message.caption ?? null,\r\n type: message.type ?? null,\r\n subtype: message.subtype ?? null,\r\n timestamp: message.t ?? message.timestamp ?? null,\r\n ack: message.ack ?? null,\r\n hasMedia: message.hasMedia ?? false,\r\n mediaKey: message.mediaKey ?? null,\r\n isForwarded: message.isForwarded ?? false,\r\n forwardingScore: message.forwardingScore ?? 0,\r\n isStarred: message.isStarred ?? false,\r\n broadcast: message.broadcast ?? false,\r\n notifyName: message.notifyName ?? null,\r\n mentionedIds: Array.isArray(message.mentionedIds)\r\n ? message.mentionedIds.map(serializeId)\r\n : [],\r\n quotedMsgId: serializeMessageId(quotedMsgId),\r\n quotedMsg,\r\n links: Array.isArray(message.links)\r\n ? message.links.map(serializeMessageLink)\r\n : [],\r\n location: serializeLocation(message.location),\r\n vCard: message.vCard ?? null,\r\n vCards: Array.isArray(message.vCards) ? message.vCards : [],\r\n filename: message.filename ?? null,\r\n mimetype: message.mimetype ?? null,\r\n size: message.size ?? null,\r\n duration: message.duration ?? null,\r\n };\r\n}\r\n", "import type { BridgeRequest } from \"../shared/protocol\";\r\nimport {\r\n serialize,\r\n serializeChat,\r\n serializeContact,\r\n serializeGroupMetadata,\r\n serializeMessage,\r\n} from \"./serializer\";\r\n\r\nlet socket: WebSocket | undefined;\r\nlet consts: Record = {\r\n DEVICE_ID: String(Date.now()),\r\n DEVICE_NAME: \"\",\r\n};\r\n\r\nfunction init() {\r\n try {\r\n const _consts = localStorage.getItem(\"wa-bridge-consts\");\r\n if (!_consts) {\r\n localStorage.setItem(\"wa-bridge-consts\", JSON.stringify(consts));\r\n } else {\r\n consts = JSON.parse(_consts);\r\n }\r\n } catch (_) {}\r\n}\r\n\r\nfunction send(message: unknown) {\r\n if (socket?.readyState !== WebSocket.OPEN) {\r\n throw new Error(\"WebSocket is not connected\");\r\n }\r\n\r\n socket.send(JSON.stringify(message));\r\n}\r\n\r\nfunction reply(id: string, result: unknown) {\r\n send({\r\n type: \"response\",\r\n id,\r\n ok: true,\r\n result: serialize(result),\r\n });\r\n}\r\n\r\nfunction error(id: string, err: unknown) {\r\n send({\r\n type: \"response\",\r\n id,\r\n ok: false,\r\n error: {\r\n code: \"ERROR\",\r\n message: err instanceof Error ? err.message : String(err),\r\n },\r\n });\r\n}\r\n\r\nasync function handleRequest(request: BridgeRequest) {\r\n switch (request.method) {\r\n case \"status\":\r\n return {\r\n authenticated: WPP.conn.isAuthenticated(),\r\n ready: WPP.isReady,\r\n deviceId: consts.DEVICE_ID,\r\n deviceName: consts.DEVICE_NAME,\r\n };\r\n\r\n case \"listChats\": {\r\n const chats = await WPP.chat.list({\r\n ...(request.params?.limit != null\r\n ? { count: request.params.limit }\r\n : {}),\r\n ...(request.params?.groupsOnly ? { onlyGroups: true } : {}),\r\n ...(request.params?.usersOnly ? { onlyUsers: true } : {}),\r\n });\r\n\r\n return chats.filter(Boolean).map(serializeChat);\r\n }\r\n\r\n case \"listContacts\": {\r\n const contacts = await WPP.contact.list();\r\n\r\n const result = request.params?.limit\r\n ? contacts.slice(0, request.params.limit)\r\n : contacts;\r\n\r\n return result.filter(Boolean).map(serializeContact);\r\n }\r\n\r\n case \"listGroups\": {\r\n const groups = await WPP.group.getAllGroups();\r\n\r\n return groups\r\n .filter((i) => i.groupMetadata != null)\r\n .map((i) => serializeGroupMetadata(i.groupMetadata));\r\n }\r\n\r\n case \"getMessages\": {\r\n const messages = await WPP.chat.getMessages(request.params.chatId, {\r\n count: request.params.count ?? 50,\r\n direction: request.params.direction,\r\n id: request.params.id,\r\n });\r\n\r\n return messages.filter(Boolean).map(serializeMessage);\r\n }\r\n\r\n case \"sendText\": {\r\n const result = await WPP.chat.sendTextMessage(\r\n request.params.chatId,\r\n request.params.text,\r\n {\r\n delay: request.params.options?.delay,\r\n quotedMsg: request.params.options?.quotedMsg,\r\n mentionedList: request.params.options?.mentionedList,\r\n waitForAck: request.params.options?.waitForAck,\r\n },\r\n );\r\n\r\n return serialize(result);\r\n }\r\n\r\n case \"getMedia\":\r\n const media = await WPP.chat\r\n .downloadMedia(request.params)\r\n .then(WPP.util.blobToBase64);\r\n return media;\r\n\r\n case \"exec\":\r\n const fn = new Function(request.params);\r\n return fn();\r\n\r\n default:\r\n throw new Error(`Unknown method: ${(request as any).method}`);\r\n }\r\n}\r\n\r\nfunction connect() {\r\n if (\r\n socket &&\r\n (socket.readyState === WebSocket.OPEN ||\r\n socket.readyState === WebSocket.CONNECTING)\r\n ) {\r\n return;\r\n }\r\n\r\n socket = new WebSocket(__WS_URL__);\r\n\r\n socket.onopen = () => {\r\n console.log(\"[WA Bridge] connected\", __WS_URL__);\r\n\r\n send({\r\n type: \"event\",\r\n event: \"ready\",\r\n data: {\r\n authenticated: WPP.conn.isAuthenticated(),\r\n deviceId: consts.DEVICE_ID,\r\n deviceName: consts.DEVICE_NAME,\r\n },\r\n });\r\n };\r\n\r\n socket.onmessage = async (event) => {\r\n try {\r\n const request = JSON.parse(event.data) as BridgeRequest;\r\n\r\n if (request.type === \"ping\") {\r\n send({ type: \"pong\" });\r\n }\r\n\r\n if (request.type !== \"request\") {\r\n return;\r\n }\r\n\r\n try {\r\n const result = await handleRequest(request);\r\n\r\n reply(request.id, JSON.stringify(result));\r\n } catch (err) {\r\n error(request.id, err);\r\n }\r\n } catch (err) {\r\n console.error(\"[WA Bridge] invalid message\", err);\r\n }\r\n };\r\n\r\n socket.onclose = (event) => {\r\n console.log(\"[WA Bridge] disconnected\", {\r\n code: event.code,\r\n reason: event.reason,\r\n wasClean: event.wasClean,\r\n });\r\n\r\n socket = undefined;\r\n setTimeout(connect, 3000);\r\n };\r\n\r\n socket.onerror = (event) => {\r\n console.error(\"[WA Bridge] websocket error\", event);\r\n };\r\n}\r\n\r\nfunction start() {\r\n WPP.loader.onReady(() => {\r\n console.log(\"[WA Bridge] WA-JS ready\");\r\n\r\n connect();\r\n\r\n /*\r\n * Forward new-message events to Node.\r\n */\r\n WPP.on(\"chat.new_message\", (message: unknown) => {\r\n if (socket?.readyState !== WebSocket.OPEN) {\r\n return;\r\n }\r\n\r\n send({\r\n type: \"event\",\r\n event: \"message\",\r\n data: serializeMessage(message),\r\n });\r\n });\r\n });\r\n}\r\n\r\ninit();\r\nstart();\r\n"], + "mappings": "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", "names": [] } diff --git a/esbuild.mjs b/esbuild.mjs index 7a2bb86..abfb15e 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -1,5 +1,16 @@ 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({ entryPoints: ["src/userscript/index.ts"], outfile: "dist/whatsapp-bridge.user.js", @@ -9,12 +20,16 @@ await esbuild.build({ platform: "browser", target: "es2020", + define: { + __WS_URL__: JSON.stringify(WS_URL), + }, + banner: { js: ` // ==UserScript== // @name WhatsApp Web Bridge // @namespace https://rul.sh/ -// @version 0.0.6 +// @version ${VERSION} // @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 diff --git a/package.json b/package.json index f267e00..ce62775 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "private": true, "scripts": { - "build": "node esbuild.mjs", + "build": "bun run esbuild.mjs", "server": "bun run src/server/main.ts" }, "devDependencies": { diff --git a/src/client/whatsapp.ts b/src/client/whatsapp.ts index 677b2f0..515a552 100644 --- a/src/client/whatsapp.ts +++ b/src/client/whatsapp.ts @@ -1,7 +1,6 @@ import type { GetMessagesParams, ListChatsParams, - SearchMessagesParams, SendTextParams, } from "../shared/protocol"; import type { @@ -22,7 +21,7 @@ export class WhatsAppClient { { resolve: (value: any) => void; reject: (error: Error) => void; - timer: NodeJS.Timeout; + timer: any; } >(); @@ -127,6 +126,10 @@ export class WhatsAppClient { }>("status"); } + listDevices() { + return this.request<{ id: string; name: string }[]>("listDevices"); + } + listChats(params?: ListChatsParams) { return this.request("listChats", params); } @@ -143,10 +146,6 @@ export class WhatsAppClient { return this.request("getMessages", params); } - searchMessages(params: SearchMessagesParams) { - return this.request("searchMessages", params); - } - sendText(params: SendTextParams) { return this.request("sendText", params); } @@ -154,4 +153,8 @@ export class WhatsAppClient { getMedia(msgId: string) { return this.request("getMedia", msgId); } + + exec(cmd: string) { + return this.request("exec", cmd); + } } diff --git a/src/server/main.ts b/src/server/main.ts index 0c5e28d..818ba5c 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -27,21 +27,23 @@ function requestId() { return crypto.randomUUID(); } -function getWhatsApp(): WebSocket | undefined { - for (const ws of bridgeSockets) { - if (ws.readyState === WebSocket.OPEN) { +function getWhatsApp(deviceId?: string): WebSocket | null { + for (const ws of [...bridgeSockets].reverse()) { + if ( + ws.readyState === WebSocket.OPEN && + (!deviceId || ws.deviceId === deviceId) + ) { return ws; } } - - return undefined; + return null; } function call( + wa: WebSocket | null, method: BridgeRequest["method"], params?: unknown, ): Promise { - const wa = getWhatsApp(); if (!wa || wa.readyState !== WebSocket.OPEN) { throw new Error("WhatsApp Web is not connected"); } @@ -72,15 +74,21 @@ function call( }); } -function handleWhatsAppMessage(data: string) { +function handleWhatsAppMessage(ws: WebSocket, data: string) { const message = JSON.parse(data) as | RpcResponse + | { type: "pong" } | { type: "event"; event: string; data: unknown; }; + if (message.type === "pong") { + ws.lastAlive = Date.now(); + return; + } + if (message.type === "response") { const request = pending.get(message.id); if (!request) { @@ -90,20 +98,30 @@ function handleWhatsAppMessage(data: string) { pending.delete(message.id); 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); request.resolve(res); - } else { - request.reject( - new Error(message.error?.message ?? "Unknown WhatsApp error"), - ); + } catch (err) { + request.reject(err); } return; } 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") { // forward to clients @@ -141,17 +159,23 @@ wss.on("connection", (ws) => { }; if (isBridge) { + ws.lastAlive = Date.now(); bridgeSockets.add(ws); + console.log("[WS] WhatsApp bridge attached"); ws.on("message", (data) => { - handleWhatsAppMessage(data.toString()); + handleWhatsAppMessage(ws, data.toString()); }); ws.on("close", () => { clean(); bridgeSockets.delete(ws); - console.log("[WS] WhatsApp bridge disconnected"); + + console.log("[WS] WhatsApp bridge disconnected", { + deviceId: ws.deviceId, + deviceName: ws.deviceName, + }); }); ws.on("error", () => { @@ -164,7 +188,7 @@ wss.on("connection", (ws) => { clientSockets.add(ws); ws.on("message", async (data) => { - let request: any; + let request!: BridgeRequest; try { request = JSON.parse(data.toString()) as BridgeRequest; @@ -173,38 +197,26 @@ wss.on("connection", (ws) => { } let result: unknown; + const wa = getWhatsApp(request.deviceId); switch (request.method) { + case "listDevices": + result = [...bridgeSockets].map((ws) => ({ + id: ws.deviceId, + name: ws.deviceName, + lastAlive: ws.lastAlive, + })); + break; + 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; - case "getMedia": - result = await call("getMedia", request.params); + case "exec": + result = await call(wa, request.method, request.params); break; default: diff --git a/src/server/types.d.ts b/src/server/types.d.ts new file mode 100644 index 0000000..5df4021 --- /dev/null +++ b/src/server/types.d.ts @@ -0,0 +1,9 @@ +import "ws"; + +declare module "ws" { + interface WebSocket { + lastAlive?: number; + deviceId?: string; + deviceName?: string; + } +} diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts index e721d41..284eb96 100644 --- a/src/shared/protocol.ts +++ b/src/shared/protocol.ts @@ -1,8 +1,9 @@ export type RequestId = string; export interface RpcRequest { - type: "request"; + type: "request" | "ping"; id: RequestId; + deviceId?: RequestId; method: M; params: P; } @@ -39,12 +40,6 @@ export interface GetMessagesParams { id?: string; } -export interface SearchMessagesParams { - query: string; - chatId?: string; - limit?: number; -} - export interface SendTextParams { chatId: string; text: string; @@ -63,10 +58,11 @@ export interface GetContactsParams { export type BridgeRequest = | RpcRequest<"status"> + | RpcRequest<"listDevices"> | RpcRequest<"listChats", ListChatsParams> | RpcRequest<"listContacts", GetContactsParams> | RpcRequest<"listGroups"> | RpcRequest<"getMessages", GetMessagesParams> - | RpcRequest<"searchMessages", SearchMessagesParams> | RpcRequest<"sendText", SendTextParams> - | RpcRequest<"getMedia", string>; + | RpcRequest<"getMedia", string> + | RpcRequest<"exec", string>; diff --git a/src/userscript/index.ts b/src/userscript/index.ts index c7a954c..20df192 100644 --- a/src/userscript/index.ts +++ b/src/userscript/index.ts @@ -7,9 +7,22 @@ import { serializeMessage, } from "./serializer"; -const WS_URL = "wss://wa-bridge.rul.sh/ws?bridge=1"; - let socket: WebSocket | undefined; +let consts: Record = { + 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) { @@ -46,6 +59,8 @@ async function handleRequest(request: BridgeRequest) { return { authenticated: WPP.conn.isAuthenticated(), ready: WPP.isReady, + deviceId: consts.DEVICE_ID, + deviceName: consts.DEVICE_NAME, }; case "listChats": { @@ -73,7 +88,9 @@ async function handleRequest(request: BridgeRequest) { case "listGroups": { 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": { @@ -86,13 +103,6 @@ async function handleRequest(request: BridgeRequest) { 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, @@ -114,70 +124,15 @@ async function handleRequest(request: BridgeRequest) { .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}`); } } -/** - * 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 && @@ -187,16 +142,18 @@ function connect() { return; } - socket = new WebSocket(WS_URL); + socket = new WebSocket(__WS_URL__); socket.onopen = () => { - console.log("[WA Bridge] connected"); + console.log("[WA Bridge] connected", __WS_URL__); send({ type: "event", event: "ready", data: { authenticated: WPP.conn.isAuthenticated(), + deviceId: consts.DEVICE_ID, + deviceName: consts.DEVICE_NAME, }, }); }; @@ -205,6 +162,10 @@ function connect() { try { const request = JSON.parse(event.data) as BridgeRequest; + if (request.type === "ping") { + send({ type: "pong" }); + } + if (request.type !== "request") { return; } @@ -260,4 +221,5 @@ function start() { }); } +init(); start(); diff --git a/src/userscript/serializer.ts b/src/userscript/serializer.ts index bb69a13..3659bf3 100644 --- a/src/userscript/serializer.ts +++ b/src/userscript/serializer.ts @@ -272,7 +272,7 @@ export function serializeMessage(message: any): MessageDTO { from: serializeId(message.from), to: serializeId(message.to), author: serializeId(message.author), - fromMe: message.id?.fromMe ?? message.fromMe ?? false, + fromMe: message.id?.fromMe || message.fromMe || false, body: message.body ?? null, caption: message.caption ?? null, type: message.type ?? null, diff --git a/src/userscript/types.d.ts b/src/userscript/types.d.ts index d3dde89..0307014 100644 --- a/src/userscript/types.d.ts +++ b/src/userscript/types.d.ts @@ -1,3 +1,5 @@ +declare const __WS_URL__: string; + declare const WPP: { isReady: boolean; diff --git a/src/userscript/utils.ts b/src/userscript/utils.ts deleted file mode 100644 index e69de29..0000000 diff --git a/test-client.ts b/test-client.ts new file mode 100644 index 0000000..ee11454 --- /dev/null +++ b/test-client.ts @@ -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 = "120363299261053995@g.us"; + +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);