Merge branch 'master' into call

This commit is contained in:
Rajeh Taher
2024-01-01 01:04:23 +02:00
committed by GitHub
16 changed files with 126 additions and 119 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: [purpshell, auties00, edgardmessias]

101
README.md
View File

@@ -376,65 +376,6 @@ const sentMsg = await sock.sendMessage(
} }
) )
// send a buttons message!
const buttons = [
{buttonId: 'id1', buttonText: {displayText: 'Button 1'}, type: 1},
{buttonId: 'id2', buttonText: {displayText: 'Button 2'}, type: 1},
{buttonId: 'id3', buttonText: {displayText: 'Button 3'}, type: 1}
]
const buttonMessage = {
text: "Hi it's button message",
footer: 'Hello World',
buttons: buttons,
headerType: 1
}
const sendMsg = await sock.sendMessage(id, buttonMessage)
//send a template message!
const templateButtons = [
{index: 1, urlButton: {displayText: '⭐ Star Baileys on GitHub!', url: 'https://github.com/adiwajshing/Baileys'}},
{index: 2, callButton: {displayText: 'Call me!', phoneNumber: '+1 (234) 5678-901'}},
{index: 3, quickReplyButton: {displayText: 'This is a reply, just like normal buttons!', id: 'id-like-buttons-message'}},
]
const templateMessage = {
text: "Hi it's a template message",
footer: 'Hello World',
templateButtons: templateButtons
}
const sendMsg = await sock.sendMessage(id, templateMessage)
// send a list message!
const sections = [
{
title: "Section 1",
rows: [
{title: "Option 1", rowId: "option1"},
{title: "Option 2", rowId: "option2", description: "This is a description"}
]
},
{
title: "Section 2",
rows: [
{title: "Option 3", rowId: "option3"},
{title: "Option 4", rowId: "option4", description: "This is a description V2"}
]
},
]
const listMessage = {
text: "This is a list",
footer: "nice footer, link: https://google.com",
title: "Amazing boldfaced list title",
buttonText: "Required, text on the button to view the list",
sections
}
const sendMsg = await sock.sendMessage(id, listMessage)
const reactionMessage = { const reactionMessage = {
react: { react: {
text: "💖", // use an empty string to remove the reaction text: "💖", // use an empty string to remove the reaction
@@ -489,39 +430,6 @@ await sock.sendMessage(
{ audio: { url: "./Media/audio.mp3" }, mimetype: 'audio/mp4' } { audio: { url: "./Media/audio.mp3" }, mimetype: 'audio/mp4' }
{ url: "Media/audio.mp3" }, // can send mp3, mp4, & ogg { url: "Media/audio.mp3" }, // can send mp3, mp4, & ogg
) )
// send a buttons message with image header!
const buttons = [
{buttonId: 'id1', buttonText: {displayText: 'Button 1'}, type: 1},
{buttonId: 'id2', buttonText: {displayText: 'Button 2'}, type: 1},
{buttonId: 'id3', buttonText: {displayText: 'Button 3'}, type: 1}
]
const buttonMessage = {
image: {url: 'https://example.com/image.jpeg'},
caption: "Hi it's button message",
footer: 'Hello World',
buttons: buttons,
headerType: 4
}
const sendMsg = await sock.sendMessage(id, buttonMessage)
//send a template message with an image **attached**!
const templateButtons = [
{index: 1, urlButton: {displayText: '⭐ Star Baileys on GitHub!', url: 'https://github.com/adiwajshing/Baileys'}},
{index: 2, callButton: {displayText: 'Call me!', phoneNumber: '+1 (234) 5678-901'}},
{index: 3, quickReplyButton: {displayText: 'This is a reply, just like normal buttons!', id: 'id-like-buttons-message'}},
]
const buttonMessage = {
text: "Hi it's a template message",
footer: 'Hello World',
templateButtons: templateButtons,
image: {url: 'https://example.com/image.jpeg'}
}
const sendMsg = await sock.sendMessage(id, templateMessage)
``` ```
### Notes ### Notes
@@ -698,6 +606,15 @@ WA uses an encrypted form of communication to send chat/app updates. This has be
}, },
'123456@s.whatsapp.net') '123456@s.whatsapp.net')
``` ```
- Star/unstar a message
``` ts
await sock.chatModify({
star: {
messages: [{ id: 'messageID', fromMe: true // or `false` }],
star: true // - true: Star Message; false: Unstar Message
}},'123456@s.whatsapp.net');
```
**Note:** if you mess up one of your updates, WA can log you out of all your devices and you'll have to log in again. **Note:** if you mess up one of your updates, WA can log you out of all your devices and you'll have to log in again.

View File

@@ -777,6 +777,18 @@ export const makeChatsSocket = (config: SocketConfig) => {
return appPatch(patch) return appPatch(patch)
} }
/**
* Star or Unstar a message
*/
const star = (jid: string, messages: { id: string, fromMe?: boolean }[], star: boolean) => {
return chatModify({
star: {
messages,
star
}
}, jid)
}
/** /**
* Adds label for the chats * Adds label for the chats
*/ */
@@ -999,6 +1011,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
addChatLabel, addChatLabel,
removeChatLabel, removeChatLabel,
addMessageLabel, addMessageLabel,
removeMessageLabel removeMessageLabel,
star
} }
} }

View File

@@ -398,7 +398,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
for(const { attrs } of blocklists) { for(const { attrs } of blocklists) {
const blocklist = [attrs.jid] const blocklist = [attrs.jid]
const type = (attrs.action === 'block') ? 'add' : 'remove' const type = (attrs.action === 'block') ? 'add' : 'remove'
ev.emit('blocklist.update', { blocklist, type }) ev.emit('blocklist.update', { blocklist, type })
} }
} }
@@ -541,7 +540,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const handleReceipt = async(node: BinaryNode) => { const handleReceipt = async(node: BinaryNode) => {
const { attrs, content } = node const { attrs, content } = node
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, authState.creds.me?.id) const isLid = attrs.from.includes('lid')
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, isLid ? authState.creds.me?.lid : authState.creds.me?.id)
const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient const remoteJid = !isNodeFromMe || isJidGroup(attrs.from) ? attrs.from : attrs.recipient
const fromMe = !attrs.recipient || (attrs.type === 'retry' && isNodeFromMe) const fromMe = !attrs.recipient || (attrs.type === 'retry' && isNodeFromMe)
@@ -664,9 +664,17 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
const { fullMessage: msg, category, author, decrypt } = decryptMessageNode( const { fullMessage: msg, category, author, decrypt } = decryptMessageNode(
node, node,
authState.creds.me!.id, authState.creds.me!.id,
authState.creds.me!.lid || '',
signalRepository, signalRepository,
logger, logger,
) )
if(msg.message?.protocolMessage?.type === proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER) {
if(node.attrs.sender_pn) {
ev.emit('chats.phoneNumberShare', { lid: node.attrs.from, jid: node.attrs.sender_pn })
}
}
if(shouldIgnoreJid(msg.key.remoteJid!)) { if(shouldIgnoreJid(msg.key.remoteJid!)) {
logger.debug({ key: msg.key }, 'ignored message') logger.debug({ key: msg.key }, 'ignored message')
await sendMessageAck(node) await sendMessageAck(node)

View File

@@ -312,12 +312,13 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const statusJid = 'status@broadcast' const statusJid = 'status@broadcast'
const isGroup = server === 'g.us' const isGroup = server === 'g.us'
const isStatus = jid === statusJid const isStatus = jid === statusJid
const isLid = server === 'lid'
msgId = msgId || generateMessageID() msgId = msgId || generateMessageID()
useUserDevicesCache = useUserDevicesCache !== false useUserDevicesCache = useUserDevicesCache !== false
const participants: BinaryNode[] = [] const participants: BinaryNode[] = []
const destinationJid = (!isStatus) ? jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net') : statusJid const destinationJid = (!isStatus) ? jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : 's.whatsapp.net') : statusJid
const binaryNodeContent: BinaryNode[] = [] const binaryNodeContent: BinaryNode[] = []
const devices: JidWithDevice[] = [] const devices: JidWithDevice[] = []
@@ -377,7 +378,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
devices.push(...additionalDevices) devices.push(...additionalDevices)
} }
const patched = await patchMessageBeforeSending(message, devices.map(d => jidEncode(d.user, 's.whatsapp.net', d.device))) const patched = await patchMessageBeforeSending(message, devices.map(d => jidEncode(d.user, isLid ? 'lid' : 's.whatsapp.net', d.device)))
const bytes = encodeWAMessage(patched) const bytes = encodeWAMessage(patched)
const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage( const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage(
@@ -391,7 +392,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const senderKeyJids: string[] = [] const senderKeyJids: string[] = []
// ensure a connection is established with every device // ensure a connection is established with every device
for(const { user, device } of devices) { for(const { user, device } of devices) {
const jid = jidEncode(user, 's.whatsapp.net', device) const jid = jidEncode(user, isLid ? 'lid' : 's.whatsapp.net', device)
if(!senderKeyMap[jid] || !!participant) { if(!senderKeyMap[jid] || !!participant) {
senderKeyJids.push(jid) senderKeyJids.push(jid)
// store that this person has had the sender keys sent to them // store that this person has had the sender keys sent to them
@@ -444,8 +445,8 @@ export const makeMessagesSocket = (config: SocketConfig) => {
const meJids: string[] = [] const meJids: string[] = []
const otherJids: string[] = [] const otherJids: string[] = []
for(const { user, device } of devices) { for(const { user, device } of devices) {
const jid = jidEncode(user, 's.whatsapp.net', device)
const isMe = user === meUser const isMe = user === meUser
const jid = jidEncode(isMe && isLid ? authState.creds?.me?.lid!.split(':')[0] || user : user, isLid ? 'lid' : 's.whatsapp.net', device)
if(isMe) { if(isMe) {
meJids.push(jid) meJids.push(jid)
} else { } else {
@@ -642,9 +643,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
relayMessage, relayMessage,
sendReceipt, sendReceipt,
sendReceipts, sendReceipts,
getButtonArgs,
readMessages, readMessages,
refreshMediaConn, refreshMediaConn,
waUploadToServer, waUploadToServer,
fetchPrivacySettings, fetchPrivacySettings,
updateMediaMessage: async(message: proto.IWebMessageInfo) => { updateMediaMessage: async(message: proto.IWebMessageInfo) => {
const content = assertMediaContent(message.message) const content = assertMediaContent(message.message)

View File

@@ -623,13 +623,15 @@ export const makeSocket = (config: SocketConfig) => {
} }
}) })
// login complete // login complete
ws.on('CB:success', async() => { ws.on('CB:success', async(node: BinaryNode) => {
await uploadPreKeysToServerIfRequired() await uploadPreKeysToServerIfRequired()
await sendPassiveIq('active') await sendPassiveIq('active')
logger.info('opened connection to WA') logger.info('opened connection to WA')
clearTimeout(qrTimer) // will never happen in all likelyhood -- but just in case WA sends success on first try clearTimeout(qrTimer) // will never happen in all likelyhood -- but just in case WA sends success on first try
ev.emit('creds.update', { me: { id: authState.creds.me!.id, lid: node.attrs.lid } })
ev.emit('connection.update', { connection: 'open' }) ev.emit('connection.update', { connection: 'open' })
}) })

View File

@@ -1,5 +1,6 @@
export interface Contact { export interface Contact {
id: string id: string
lid?: string
/** name of the contact, you have saved on your WA */ /** name of the contact, you have saved on your WA */
name?: string name?: string
/** name of the contact, the contact has set on their own on WA */ /** name of the contact, the contact has set on their own on WA */

View File

@@ -26,6 +26,7 @@ export type BaileysEventMap = {
'chats.upsert': Chat[] 'chats.upsert': Chat[]
/** update the given chats */ /** update the given chats */
'chats.update': ChatUpdate[] 'chats.update': ChatUpdate[]
'chats.phoneNumberShare': {lid: string, jid: string}
/** delete chats with given ID */ /** delete chats with given ID */
'chats.delete': string[] 'chats.delete': string[]
/** presence of contact in a chat updated */ /** presence of contact in a chat updated */
@@ -54,6 +55,7 @@ export type BaileysEventMap = {
'blocklist.set': { blocklist: string[] } 'blocklist.set': { blocklist: string[] }
'blocklist.update': { blocklist: string[], type: 'add' | 'remove' } 'blocklist.update': { blocklist: string[], type: 'add' | 'remove' }
/** Receive an update on a call, including when the call was received, rejected, accepted */ /** Receive an update on a call, including when the call was received, rejected, accepted */
'call': WACallEvent[] 'call': WACallEvent[]
'labels.edit': Label 'labels.edit': Label

View File

@@ -96,6 +96,14 @@ export type PollMessageOptions = {
messageSecret?: Uint8Array messageSecret?: Uint8Array
} }
type SharePhoneNumber = {
sharePhoneNumber: boolean
}
type RequestPhoneNumber = {
requestPhoneNumber: boolean
}
export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING export type MediaType = keyof typeof MEDIA_HKDF_KEY_MAPPING
export type AnyMediaMessageContent = ( export type AnyMediaMessageContent = (
({ ({
@@ -169,7 +177,7 @@ export type AnyRegularMessageContent = (
businessOwnerJid?: string businessOwnerJid?: string
body?: string body?: string
footer?: string footer?: string
} } | SharePhoneNumber | RequestPhoneNumber
) & ViewOnce ) & ViewOnce
export type AnyMessageContent = AnyRegularMessageContent | { export type AnyMessageContent = AnyRegularMessageContent | {

View File

@@ -583,6 +583,19 @@ export const chatModificationToAppPatch = (
apiVersion: 5, apiVersion: 5,
operation: OP.SET operation: OP.SET
} }
} else if('star' in mod) {
const key = mod.star.messages[0]
patch = {
syncAction: {
starAction: {
starred: !!mod.star.star
}
},
index: ['star', jid, key.id, key.fromMe ? '1' : '0', '0'],
type: 'regular_low',
apiVersion: 2,
operation: OP.SET
}
} else if('delete' in mod) { } else if('delete' in mod) {
patch = { patch = {
syncAction: { syncAction: {

View File

@@ -2,7 +2,7 @@ import { Boom } from '@hapi/boom'
import { Logger } from 'pino' import { Logger } from 'pino'
import { proto } from '../../WAProto' import { proto } from '../../WAProto'
import { SignalRepository, WAMessageKey } from '../Types' import { SignalRepository, WAMessageKey } from '../Types'
import { areJidsSameUser, BinaryNode, isJidBroadcast, isJidGroup, isJidStatusBroadcast, isJidUser } from '../WABinary' import { areJidsSameUser, BinaryNode, isJidBroadcast, isJidGroup, isJidStatusBroadcast, isJidUser, isLidUser } from '../WABinary'
import { unpadRandomMax16 } from './generics' import { unpadRandomMax16 } from './generics'
const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node' const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node'
@@ -15,7 +15,8 @@ type MessageType = 'chat' | 'peer_broadcast' | 'other_broadcast' | 'group' | 'di
*/ */
export function decodeMessageNode( export function decodeMessageNode(
stanza: BinaryNode, stanza: BinaryNode,
meId: string meId: string,
meLid: string
) { ) {
let msgType: MessageType let msgType: MessageType
let chatId: string let chatId: string
@@ -27,6 +28,7 @@ export function decodeMessageNode(
const recipient: string | undefined = stanza.attrs.recipient const recipient: string | undefined = stanza.attrs.recipient
const isMe = (jid: string) => areJidsSameUser(jid, meId) const isMe = (jid: string) => areJidsSameUser(jid, meId)
const isMeLid = (jid: string) => areJidsSameUser(jid, meLid)
if(isJidUser(from)) { if(isJidUser(from)) {
if(recipient) { if(recipient) {
@@ -39,6 +41,19 @@ export function decodeMessageNode(
chatId = from chatId = from
} }
msgType = 'chat'
author = from
} else if(isLidUser(from)) {
if(recipient) {
if(!isMeLid(from)) {
throw new Boom('receipient present, but msg not from me', { data: stanza })
}
chatId = recipient
} else {
chatId = from
}
msgType = 'chat' msgType = 'chat'
author = from author = from
} else if(isJidGroup(from)) { } else if(isJidGroup(from)) {
@@ -67,7 +82,7 @@ export function decodeMessageNode(
throw new Boom('Unknown message type', { data: stanza }) throw new Boom('Unknown message type', { data: stanza })
} }
const fromMe = isMe(stanza.attrs.participant || stanza.attrs.from) const fromMe = (isLidUser(from) ? isMeLid : isMe)(stanza.attrs.participant || stanza.attrs.from)
const pushname = stanza.attrs.notify const pushname = stanza.attrs.notify
const key: WAMessageKey = { const key: WAMessageKey = {
@@ -98,10 +113,11 @@ export function decodeMessageNode(
export const decryptMessageNode = ( export const decryptMessageNode = (
stanza: BinaryNode, stanza: BinaryNode,
meId: string, meId: string,
meLid: string,
repository: SignalRepository, repository: SignalRepository,
logger: Logger logger: Logger
) => { ) => {
const { fullMessage, author, sender } = decodeMessageNode(stanza, meId) const { fullMessage, author, sender } = decodeMessageNode(stanza, meId, meLid)
return { return {
fullMessage, fullMessage,
category: stanza.attrs.category, category: stanza.attrs.category,
@@ -183,4 +199,4 @@ export const decryptMessageNode = (
} }
} }
} }
} }

View File

@@ -449,6 +449,12 @@ export const generateWAMessageContent = async(
selectableOptionsCount: message.poll.selectableCount, selectableOptionsCount: message.poll.selectableCount,
options: message.poll.values.map(optionName => ({ optionName })), options: message.poll.values.map(optionName => ({ optionName })),
} }
} else if('sharePhoneNumber' in message) {
m.protocolMessage = {
type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
}
} else if('requestPhoneNumber' in message) {
m.requestPhoneNumberMessage = {}
} else { } else {
m = await prepareWAMessageMedia( m = await prepareWAMessageMedia(
message, message,

View File

@@ -263,6 +263,22 @@ const processMessage = async(
ephemeralSettingTimestamp: toNumber(message.messageTimestamp), ephemeralSettingTimestamp: toNumber(message.messageTimestamp),
ephemeralExpiration: protocolMsg.ephemeralExpiration || null ephemeralExpiration: protocolMsg.ephemeralExpiration || null
}) })
break
case proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE:
const response = protocolMsg.peerDataOperationRequestResponseMessage!
if(response) {
const { peerDataOperationResult } = response
for(const result of peerDataOperationResult!) {
const { placeholderMessageResendResponse: retryResponse } = result
if(retryResponse) {
const webMessageInfo = proto.WebMessageInfo.decode(retryResponse.webMessageInfoBytes!)
ev.emit('messages.update', [
{ key: webMessageInfo.key, update: { message: webMessageInfo.message } }
])
}
}
}
break break
} }
} else if(content?.reactionMessage) { } else if(content?.reactionMessage) {
@@ -387,4 +403,4 @@ const processMessage = async(
} }
} }
export default processMessage export default processMessage

View File

@@ -148,7 +148,7 @@ export const decodeDecompressedBinaryNode = (
const device = readByte() const device = readByte()
const user = readString(readByte()) const user = readString(readByte())
return jidEncode(user, 's.whatsapp.net', device, agent) return jidEncode(user, agent === 0 ? 's.whatsapp.net' : 'lid', device)
} }
const readString = (tag: number): string => { const readString = (tag: number): string => {
@@ -262,4 +262,4 @@ export const decodeDecompressedBinaryNode = (
export const decodeBinaryNode = (buff: Buffer): BinaryNode => { export const decodeBinaryNode = (buff: Buffer): BinaryNode => {
const decompBuff = decompressingIfRequired(buff) const decompBuff = decompressingIfRequired(buff)
return decodeDecompressedBinaryNode(decompBuff, constants) return decodeDecompressedBinaryNode(decompBuff, constants)
} }

View File

@@ -52,10 +52,10 @@ export const encodeBinaryNode = (
pushBytes(bytes) pushBytes(bytes)
} }
const writeJid = ({ agent, device, user, server }: FullJid) => { const writeJid = ({ domainType, device, user, server }: FullJid) => {
if(typeof agent !== 'undefined' || typeof device !== 'undefined') { if(typeof device !== 'undefined') {
pushByte(TAGS.AD_JID) pushByte(TAGS.AD_JID)
pushByte(agent || 0) pushByte(domainType || 0)
pushByte(device || 0) pushByte(device || 0)
writeString(user) writeString(user)
} else { } else {
@@ -233,4 +233,4 @@ export const encodeBinaryNode = (
} }
return Buffer.from(buffer) return Buffer.from(buffer)
} }

View File

@@ -4,7 +4,7 @@ export const SERVER_JID = 'server@c.us'
export const PSA_WID = '0@c.us' export const PSA_WID = '0@c.us'
export const STORIES_JID = 'status@broadcast' export const STORIES_JID = 'status@broadcast'
export type JidServer = 'c.us' | 'g.us' | 'broadcast' | 's.whatsapp.net' | 'call' export type JidServer = 'c.us' | 'g.us' | 'broadcast' | 's.whatsapp.net' | 'call' | 'lid'
export type JidWithDevice = { export type JidWithDevice = {
user: string user: string
@@ -13,7 +13,7 @@ export type JidWithDevice = {
export type FullJid = JidWithDevice & { export type FullJid = JidWithDevice & {
server: JidServer | string server: JidServer | string
agent?: number domainType?: number
} }
export const jidEncode = (user: string | number | null, server: JidServer, device?: number, agent?: number) => { export const jidEncode = (user: string | number | null, server: JidServer, device?: number, agent?: number) => {
@@ -30,12 +30,12 @@ export const jidDecode = (jid: string | undefined): FullJid | undefined => {
const userCombined = jid!.slice(0, sepIdx) const userCombined = jid!.slice(0, sepIdx)
const [userAgent, device] = userCombined.split(':') const [userAgent, device] = userCombined.split(':')
const [user, agent] = userAgent.split('_') const user = userAgent.split('_')[0]
return { return {
server, server,
user, user,
agent: agent ? +agent : undefined, domainType: server === 'lid' ? 1 : 0,
device: device ? +device : undefined device: device ? +device : undefined
} }
} }
@@ -46,6 +46,8 @@ export const areJidsSameUser = (jid1: string | undefined, jid2: string | undefin
) )
/** is the jid a user */ /** is the jid a user */
export const isJidUser = (jid: string | undefined) => (jid?.endsWith('@s.whatsapp.net')) export const isJidUser = (jid: string | undefined) => (jid?.endsWith('@s.whatsapp.net'))
/** is the jid a group */
export const isLidUser = (jid: string | undefined) => (jid?.endsWith('@lid'))
/** is the jid a broadcast */ /** is the jid a broadcast */
export const isJidBroadcast = (jid: string | undefined) => (jid?.endsWith('@broadcast')) export const isJidBroadcast = (jid: string | undefined) => (jid?.endsWith('@broadcast'))
/** is the jid a group */ /** is the jid a group */
@@ -61,4 +63,4 @@ export const jidNormalizedUser = (jid: string | undefined) => {
const { user, server } = result const { user, server } = result
return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : server as JidServer) return jidEncode(user, server === 'c.us' ? 's.whatsapp.net' : server as JidServer)
} }