mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
finalize multi-device
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
import { WAConnection, MessageOptions, MessageType, unixTimestampSeconds, toNumber, GET_MESSAGE_ID, waMessageKey } from '../WAConnection'
|
||||
import * as assert from 'assert'
|
||||
import {promises as fs} from 'fs'
|
||||
|
||||
require ('dotenv').config () // dotenv to load test jid
|
||||
export const testJid = process.env.TEST_JID || '1234@s.whatsapp.net' // set TEST_JID=xyz@s.whatsapp.net in a .env file in the root directory
|
||||
|
||||
export const makeConnection = () => {
|
||||
const conn = new WAConnection()
|
||||
conn.connectOptions.maxIdleTimeMs = 15_000
|
||||
conn.logger.level = 'debug'
|
||||
|
||||
let evCounts = {}
|
||||
|
||||
conn.on ('close', ({ isReconnecting }) => {
|
||||
!isReconnecting && console.log ('Events registered: ', evCounts)
|
||||
})
|
||||
|
||||
const onM = conn.on
|
||||
conn.on = (...args: any[]) => {
|
||||
evCounts[args[0]] = (evCounts[args[0]] || 0) + 1
|
||||
return onM.apply (conn, args)
|
||||
}
|
||||
const offM = conn.off
|
||||
conn.off = (...args: any[]) => {
|
||||
evCounts[args[0]] = (evCounts[args[0]] || 0) - 1
|
||||
if (evCounts[args[0]] <= 0) delete evCounts[args[0]]
|
||||
return offM.apply (conn, args)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
export async function sendAndRetrieveMessage(conn: WAConnection, content, type: MessageType, options: MessageOptions = {}, recipientJid = testJid) {
|
||||
const response = await conn.sendMessage(recipientJid, content, type, options)
|
||||
const {messages} = await conn.loadMessages(recipientJid, 10)
|
||||
const message = messages.find (m => m.key.id === response.key.id)
|
||||
assert.ok(message)
|
||||
|
||||
const chat = conn.chats.get(recipientJid)
|
||||
|
||||
assert.ok (chat.messages.get(GET_MESSAGE_ID(message.key)))
|
||||
assert.ok (chat.t >= (unixTimestampSeconds()-5) )
|
||||
return message
|
||||
}
|
||||
export const WAConnectionTest = (name: string, func: (conn: WAConnection) => void) => (
|
||||
describe(name, () => {
|
||||
const conn = new WAConnection()
|
||||
conn.connectOptions.maxIdleTimeMs = 30_000
|
||||
conn.logger.level = 'debug'
|
||||
|
||||
before(async () => {
|
||||
const file = './auth_info.json'
|
||||
await conn.loadAuthInfo(file).connect()
|
||||
await fs.writeFile(file, JSON.stringify(conn.base64EncodedAuthInfo(), null, '\t'))
|
||||
})
|
||||
after(() => conn.close())
|
||||
|
||||
afterEach (() => assertChatDBIntegrity (conn))
|
||||
|
||||
func(conn)
|
||||
})
|
||||
)
|
||||
export const assertChatDBIntegrity = (conn: WAConnection) => {
|
||||
conn.chats.all ().forEach (chat => (
|
||||
assert.deepStrictEqual (
|
||||
[...chat.messages.all()].sort ((m1, m2) => waMessageKey.compare(waMessageKey.key(m1), waMessageKey.key(m2))),
|
||||
chat.messages.all()
|
||||
)
|
||||
))
|
||||
conn.chats.all ().forEach (chat => (
|
||||
assert.deepStrictEqual (
|
||||
chat.messages.all().filter (m => chat.messages.all().filter(m1 => m1.key.id === m.key.id).length > 1),
|
||||
[]
|
||||
)
|
||||
))
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import { strict as assert } from 'assert'
|
||||
import Encoder from '../Binary/Encoder'
|
||||
import Decoder from '../Binary/Decoder'
|
||||
|
||||
describe('Binary Coding Tests', () => {
|
||||
const testVectors: [string, Object][] = [
|
||||
[
|
||||
'f806092f5a0a10f804f80234fc6c0a350a1b39313735323938373131313740732e77686174736170702e6e657410011a143345423030393637354537454433374141424632122b0a292a7069616e6f20726f6f6d2074696d696e6773206172653a2a0a20363a3030414d2d31323a3030414d18b3faa7f3052003f80234fc4c0a410a1b39313735323938373131313740732e77686174736170702e6e657410001a20304643454335333330463634393239433645394132434646443242433845414418bdfaa7f305c00101f80234fc930a350a1b39313735323938373131313740732e77686174736170702e6e657410011a14334542303033433742353339414644303937353312520a50536f727279206672656e2c204920636f756c646e277420756e6465727374616e6420274c69627261272e2054797065202768656c702720746f206b6e6f77207768617420616c6c20492063616e20646f18c1faa7f3052003f80234fc540a410a1b39313735323938373131313740732e77686174736170702e6e657410001a20413132333042384436423041314437393345433241453245413043313638443812090a076c69627261727918c2faa7f305',
|
||||
[
|
||||
'action',
|
||||
{ last: 'true', add: 'before' },
|
||||
[
|
||||
[
|
||||
'message',
|
||||
null,
|
||||
{
|
||||
key: { remoteJid: '917529871117@s.whatsapp.net', fromMe: true, id: '3EB009675E7ED37AABF2' },
|
||||
message: { conversation: '*piano room timings are:*\n 6:00AM-12:00AM' },
|
||||
messageTimestamp: '1584004403',
|
||||
status: 'DELIVERY_ACK',
|
||||
},
|
||||
],
|
||||
[
|
||||
'message',
|
||||
null,
|
||||
{
|
||||
key: {
|
||||
remoteJid: '917529871117@s.whatsapp.net',
|
||||
fromMe: false,
|
||||
id: '0FCEC5330F64929C6E9A2CFFD2BC8EAD',
|
||||
},
|
||||
messageTimestamp: '1584004413',
|
||||
messageStubType: 'REVOKE',
|
||||
},
|
||||
],
|
||||
[
|
||||
'message',
|
||||
null,
|
||||
{
|
||||
key: { remoteJid: '917529871117@s.whatsapp.net', fromMe: true, id: '3EB003C7B539AFD09753' },
|
||||
message: {
|
||||
conversation:
|
||||
"Sorry fren, I couldn't understand 'Libra'. Type 'help' to know what all I can do",
|
||||
},
|
||||
messageTimestamp: '1584004417',
|
||||
status: 'DELIVERY_ACK',
|
||||
},
|
||||
],
|
||||
[
|
||||
'message',
|
||||
null,
|
||||
{
|
||||
key: {
|
||||
remoteJid: '917529871117@s.whatsapp.net',
|
||||
fromMe: false,
|
||||
id: 'A1230B8D6B0A1D793EC2AE2EA0C168D8',
|
||||
},
|
||||
message: { conversation: 'library' },
|
||||
messageTimestamp: '1584004418',
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'f8063f2dfafc0831323334353637385027fc0431323334f801f80228fc0701020304050607',
|
||||
[
|
||||
'picture',
|
||||
{jid: '12345678@c.us', id: '1234'},
|
||||
[['image', null, Buffer.from([1,2,3,4,5,6,7])]]
|
||||
]
|
||||
]
|
||||
]
|
||||
const encoder = new Encoder()
|
||||
const decoder = new Decoder()
|
||||
|
||||
it('should decode strings', () => {
|
||||
testVectors.forEach(pair => {
|
||||
const buff = Buffer.from(pair[0], 'hex')
|
||||
const decoded = decoder.read(buff)
|
||||
//console.log((decoded[2][0][2]))
|
||||
assert.deepStrictEqual(decoded, pair[1])
|
||||
|
||||
const encoded = encoder.write(decoded)
|
||||
assert.deepStrictEqual(encoded, buff)
|
||||
})
|
||||
console.log('all coding tests passed')
|
||||
})
|
||||
})
|
||||
@@ -1,407 +0,0 @@
|
||||
import * as assert from 'assert'
|
||||
import {WAConnection} from '../WAConnection'
|
||||
import { AuthenticationCredentialsBase64, BaileysError, ReconnectMode, DisconnectReason, WAChat, WAContact } from '../WAConnection/Constants'
|
||||
import { delay } from '../WAConnection/Utils'
|
||||
import { assertChatDBIntegrity, makeConnection, testJid } from './Common'
|
||||
|
||||
describe('QR Generation', () => {
|
||||
it('should generate QR', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.connectOptions.maxRetries = 0
|
||||
|
||||
let calledQR = 0
|
||||
conn.removeAllListeners ('qr')
|
||||
conn.on ('qr', () => calledQR += 1)
|
||||
|
||||
await conn.connect()
|
||||
.then (() => assert.fail('should not have succeeded'))
|
||||
.catch (error => {})
|
||||
assert.deepStrictEqual (
|
||||
Object.keys(conn.eventNames()).filter(key => key.startsWith('TAG:')),
|
||||
[]
|
||||
)
|
||||
assert.ok(calledQR >= 2, 'QR not called')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Test Connect', () => {
|
||||
let auth: AuthenticationCredentialsBase64
|
||||
it('should connect', async () => {
|
||||
console.log('please be ready to scan with your phone')
|
||||
|
||||
const conn = makeConnection ()
|
||||
|
||||
let credentialsUpdateCalled = false
|
||||
conn.on ('credentials-updated', () => credentialsUpdateCalled = true)
|
||||
|
||||
await conn.connect ()
|
||||
assert.ok(conn.user?.jid)
|
||||
assert.ok(conn.user?.phone)
|
||||
assert.ok (conn.user?.imgUrl || conn.user.imgUrl === '')
|
||||
assert.ok (credentialsUpdateCalled)
|
||||
|
||||
assertChatDBIntegrity (conn)
|
||||
|
||||
conn.close()
|
||||
auth = conn.base64EncodedAuthInfo()
|
||||
})
|
||||
it('should restore session', async () => {
|
||||
const conn = makeConnection ()
|
||||
|
||||
let credentialsUpdateCalled = false
|
||||
conn.on ('credentials-updated', () => credentialsUpdateCalled = true)
|
||||
|
||||
await conn.loadAuthInfo (auth).connect ()
|
||||
assert.ok(conn.user)
|
||||
assert.ok(conn.user.jid)
|
||||
assert.ok (credentialsUpdateCalled)
|
||||
|
||||
assertChatDBIntegrity (conn)
|
||||
await conn.logout()
|
||||
conn.loadAuthInfo(auth)
|
||||
|
||||
await conn.connect()
|
||||
.then (() => assert.fail('should not have reconnected'))
|
||||
.catch (err => {
|
||||
assert.ok (err instanceof BaileysError)
|
||||
assert.ok ((err as BaileysError).status >= 400)
|
||||
})
|
||||
conn.close()
|
||||
})
|
||||
it ('should disconnect & reconnect phone', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.logger.level = 'debug'
|
||||
await conn.loadAuthInfo('./auth_info.json').connect ()
|
||||
assert.strictEqual (conn.phoneConnected, true)
|
||||
|
||||
try {
|
||||
const waitForEvent = expect => new Promise (resolve => {
|
||||
conn.on ('connection-phone-change', ({connected}) => {
|
||||
if (connected === expect) {
|
||||
conn.removeAllListeners ('connection-phone-change')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
console.log ('disconnect your phone from the internet')
|
||||
await delay (10_000)
|
||||
console.log ('phone should be disconnected now, testing...')
|
||||
|
||||
const messagesPromise = Promise.all (
|
||||
[
|
||||
conn.loadMessages (testJid, 50),
|
||||
conn.getStatus (testJid),
|
||||
conn.getProfilePicture (testJid).catch (() => '')
|
||||
]
|
||||
)
|
||||
|
||||
await waitForEvent (false)
|
||||
|
||||
console.log ('reconnect your phone to the internet')
|
||||
await waitForEvent (true)
|
||||
|
||||
console.log ('reconnected successfully')
|
||||
|
||||
const final = await messagesPromise
|
||||
assert.ok (final)
|
||||
} finally {
|
||||
conn.close ()
|
||||
}
|
||||
})
|
||||
})
|
||||
describe ('Reconnects', () => {
|
||||
const verifyConnectionOpen = async (conn: WAConnection) => {
|
||||
assert.ok (conn.user.jid)
|
||||
let failed = false
|
||||
// check that the connection stays open
|
||||
conn.on ('close', ({reason}) => {
|
||||
if(reason !== DisconnectReason.intentional) failed = true
|
||||
})
|
||||
await delay (60*1000)
|
||||
|
||||
const status = await conn.getStatus ()
|
||||
assert.ok (status)
|
||||
assert.ok (!conn['debounceTimeout']) // this should be null
|
||||
|
||||
conn.close ()
|
||||
|
||||
if (failed) assert.fail ('should not have closed again')
|
||||
}
|
||||
it('should dispose correctly on bad_session', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.autoReconnect = ReconnectMode.onAllErrors
|
||||
conn.loadAuthInfo ('./auth_info.json')
|
||||
|
||||
let gotClose0 = false
|
||||
let gotClose1 = false
|
||||
|
||||
conn.on ('ws-close', ({ reason }) => {
|
||||
gotClose0 = true
|
||||
})
|
||||
conn.on ('close', ({ reason }) => {
|
||||
if (reason === DisconnectReason.badSession) gotClose1 = true
|
||||
})
|
||||
setTimeout (() => conn['conn'].emit ('message', Buffer.from('some-tag,sdjjij1jo2ejo1je')), 1500)
|
||||
await conn.connect ()
|
||||
|
||||
setTimeout (() => conn['conn'].emit ('message', Buffer.from('some-tag,sdjjij1jo2ejo1je')), 1500)
|
||||
|
||||
await new Promise (resolve => {
|
||||
conn.on ('open', resolve)
|
||||
})
|
||||
|
||||
assert.ok (gotClose0, 'did not receive bad_session close initially')
|
||||
assert.ok (gotClose1, 'did not receive bad_session close')
|
||||
|
||||
conn.close ()
|
||||
})
|
||||
/**
|
||||
* the idea is to test closing the connection at multiple points in the connection
|
||||
* and see if the library cleans up resources correctly
|
||||
*/
|
||||
it('should cleanup correctly', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.autoReconnect = ReconnectMode.onAllErrors
|
||||
conn.loadAuthInfo ('./auth_info.json')
|
||||
|
||||
let timeout = 0.1
|
||||
while (true) {
|
||||
let tmout = setTimeout (() => conn.close(), timeout*1000)
|
||||
try {
|
||||
await conn.connect ()
|
||||
clearTimeout (tmout)
|
||||
break
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
// exponentially increase the timeout disconnect
|
||||
timeout *= 2
|
||||
}
|
||||
await verifyConnectionOpen (conn)
|
||||
})
|
||||
/**
|
||||
* the idea is to test closing the connection at multiple points in the connection
|
||||
* and see if the library cleans up resources correctly
|
||||
*/
|
||||
it('should disrupt connect loop', async () => {
|
||||
const conn = makeConnection ()
|
||||
|
||||
conn.autoReconnect = ReconnectMode.onAllErrors
|
||||
conn.loadAuthInfo ('./auth_info.json')
|
||||
|
||||
let timeout = 1000
|
||||
let tmout
|
||||
const endConnection = async () => {
|
||||
while (!conn['conn']) {
|
||||
await delay(100)
|
||||
}
|
||||
conn['conn'].close ()
|
||||
|
||||
while (conn['conn']) {
|
||||
await delay(100)
|
||||
}
|
||||
|
||||
timeout *= 2
|
||||
tmout = setTimeout (endConnection, timeout)
|
||||
}
|
||||
tmout = setTimeout (endConnection, timeout)
|
||||
|
||||
await conn.connect ()
|
||||
clearTimeout (tmout)
|
||||
|
||||
await verifyConnectionOpen (conn)
|
||||
})
|
||||
it ('should reconnect on broken connection', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.autoReconnect = ReconnectMode.onConnectionLost
|
||||
|
||||
await conn.loadAuthInfo('./auth_info.json').connect ()
|
||||
assert.strictEqual (conn.phoneConnected, true)
|
||||
|
||||
try {
|
||||
const closeConn = () => conn['conn']?.terminate ()
|
||||
|
||||
const task = new Promise (resolve => {
|
||||
let closes = 0
|
||||
conn.on ('close', ({reason, isReconnecting}) => {
|
||||
console.log (`closed: ${reason}`)
|
||||
assert.ok (reason)
|
||||
assert.ok (isReconnecting)
|
||||
closes += 1
|
||||
|
||||
// let it fail reconnect a few times
|
||||
if (closes >= 1) {
|
||||
conn.removeAllListeners ('close')
|
||||
conn.removeAllListeners ('connecting')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
conn.on ('connecting', () => {
|
||||
// close again
|
||||
delay (3500).then (closeConn)
|
||||
})
|
||||
})
|
||||
|
||||
closeConn ()
|
||||
await task
|
||||
|
||||
await new Promise (resolve => {
|
||||
conn.on ('open', () => {
|
||||
conn.removeAllListeners ('open')
|
||||
resolve(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
conn.close ()
|
||||
|
||||
conn.on ('connecting', () => assert.fail('should not connect'))
|
||||
await delay (2000)
|
||||
} finally {
|
||||
conn.removeAllListeners ('connecting')
|
||||
conn.removeAllListeners ('close')
|
||||
conn.removeAllListeners ('open')
|
||||
conn.close ()
|
||||
}
|
||||
})
|
||||
it ('should reconnect & stay connected', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.autoReconnect = ReconnectMode.onConnectionLost
|
||||
|
||||
await conn.loadAuthInfo('./auth_info.json').connect ()
|
||||
assert.strictEqual (conn.phoneConnected, true)
|
||||
|
||||
await delay (30*1000)
|
||||
|
||||
conn['conn']?.terminate ()
|
||||
|
||||
conn.on ('close', () => {
|
||||
assert.ok (!conn['lastSeen'])
|
||||
console.log ('connection closed')
|
||||
})
|
||||
await new Promise (resolve => conn.on ('open', resolve))
|
||||
await verifyConnectionOpen (conn)
|
||||
})
|
||||
})
|
||||
|
||||
describe ('Pending Requests', () => {
|
||||
it ('should correctly send updates for chats', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.pendingRequestTimeoutMs = null
|
||||
conn.loadAuthInfo('./auth_info.json')
|
||||
|
||||
const task = new Promise(resolve => conn.once('chats-received', resolve))
|
||||
await conn.connect ()
|
||||
await task
|
||||
|
||||
conn.close ()
|
||||
|
||||
const oldChat = conn.chats.all()[0]
|
||||
oldChat.archive = 'true' // mark the first chat as archived
|
||||
oldChat.modify_tag = '1234' // change modify tag to detect change
|
||||
|
||||
const promise = new Promise(resolve => conn.once('chats-update', resolve))
|
||||
|
||||
const result = await conn.connect ()
|
||||
assert.ok (!result.newConnection)
|
||||
|
||||
const chats = await promise as Partial<WAChat>[]
|
||||
const chat = chats.find (c => c.jid === oldChat.jid)
|
||||
assert.ok (chat)
|
||||
|
||||
assert.ok ('archive' in chat)
|
||||
assert.strictEqual (Object.keys(chat).length, 3)
|
||||
assert.strictEqual (Object.keys(chats).length, 1)
|
||||
|
||||
conn.close ()
|
||||
})
|
||||
it ('should correctly send updates for contacts', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.pendingRequestTimeoutMs = null
|
||||
conn.loadAuthInfo('./auth_info.json')
|
||||
|
||||
const task: any = new Promise(resolve => conn.once('contacts-received', resolve))
|
||||
await conn.connect ()
|
||||
const initialResult = await task
|
||||
assert.strictEqual(
|
||||
initialResult.updatedContacts.length,
|
||||
Object.keys(conn.contacts).length
|
||||
)
|
||||
|
||||
|
||||
conn.close ()
|
||||
|
||||
const [jid] = Object.keys(conn.contacts)
|
||||
const oldContact = conn.contacts[jid]
|
||||
oldContact.name = 'Lol'
|
||||
oldContact.index = 'L'
|
||||
|
||||
const promise = new Promise(resolve => conn.once('contacts-received', resolve))
|
||||
|
||||
const result = await conn.connect ()
|
||||
assert.ok (!result.newConnection)
|
||||
|
||||
const {updatedContacts} = await promise as { updatedContacts: Partial<WAContact>[] }
|
||||
const contact = updatedContacts.find (c => c.jid === jid)
|
||||
assert.ok (contact)
|
||||
|
||||
assert.ok ('name' in contact)
|
||||
assert.strictEqual (Object.keys(contact).length, 3)
|
||||
assert.strictEqual (Object.keys(updatedContacts).length, 1)
|
||||
|
||||
conn.close ()
|
||||
})
|
||||
it('should queue requests when closed', async () => {
|
||||
const conn = makeConnection ()
|
||||
//conn.pendingRequestTimeoutMs = null
|
||||
|
||||
await conn.loadAuthInfo('./auth_info.json').connect ()
|
||||
|
||||
await delay (2000)
|
||||
|
||||
conn.close ()
|
||||
|
||||
const task: Promise<any> = conn.query({json: ['query', 'Status', conn.user.jid]})
|
||||
|
||||
await delay (2000)
|
||||
|
||||
conn.connect ()
|
||||
const json = await task
|
||||
|
||||
assert.ok (json.status)
|
||||
|
||||
conn.close ()
|
||||
})
|
||||
it('[MANUAL] should receive query response after phone disconnect', async () => {
|
||||
const conn = makeConnection ()
|
||||
await conn.loadAuthInfo('./auth_info.json').connect ()
|
||||
|
||||
console.log(`disconnect your phone from the internet!`)
|
||||
await delay(5000)
|
||||
const task = conn.loadMessages(testJid, 50)
|
||||
setTimeout(() => console.log('reconnect your phone!'), 20_000)
|
||||
|
||||
const result = await task
|
||||
assert.ok(result.messages[0])
|
||||
assert.ok(!conn['phoneCheckInterval']) // should be undefined
|
||||
|
||||
conn.close ()
|
||||
})
|
||||
it('should re-execute query on connection closed error', async () => {
|
||||
const conn = makeConnection ()
|
||||
//conn.pendingRequestTimeoutMs = 10_000
|
||||
await conn.loadAuthInfo('./auth_info.json').connect ()
|
||||
const task: Promise<any> = conn.query({json: ['query', 'Status', conn.user.jid], waitForOpen: true})
|
||||
|
||||
await delay(20)
|
||||
conn['onMessageRecieved']('1234,["Pong",false]') // fake cancel the connection
|
||||
|
||||
await delay(2000)
|
||||
|
||||
const json = await task
|
||||
|
||||
assert.ok (json.status)
|
||||
|
||||
conn.close ()
|
||||
})
|
||||
})
|
||||
@@ -1,193 +0,0 @@
|
||||
import { MessageType, GroupSettingChange, delay, ChatModification, whatsappID } from '../WAConnection'
|
||||
import * as assert from 'assert'
|
||||
import { WAConnectionTest, testJid, sendAndRetrieveMessage } from './Common'
|
||||
|
||||
WAConnectionTest('Groups', (conn) => {
|
||||
let gid: string
|
||||
it('should create a group', async () => {
|
||||
const response = await conn.groupCreate('Cool Test Group', [testJid])
|
||||
assert.ok (conn.chats.get(response.gid))
|
||||
|
||||
const {chats} = await conn.loadChats(10, null)
|
||||
assert.strictEqual (chats[0].jid, response.gid) // first chat should be new group
|
||||
|
||||
gid = response.gid
|
||||
|
||||
console.log('created group: ' + JSON.stringify(response))
|
||||
})
|
||||
it('should retrieve group invite code', async () => {
|
||||
const code = await conn.groupInviteCode(gid)
|
||||
assert.ok(code)
|
||||
assert.strictEqual(typeof code, 'string')
|
||||
})
|
||||
it('should joined group via invite code', async () => {
|
||||
const response = await conn.acceptInvite(gid)
|
||||
assert.ok(response.status)
|
||||
assert.strictEqual(response.status, response.gid)
|
||||
})
|
||||
it('should retrieve group metadata', async () => {
|
||||
const metadata = await conn.groupMetadata(gid)
|
||||
assert.strictEqual(metadata.id, gid)
|
||||
assert.strictEqual(metadata.participants.filter((obj) => obj.jid.split('@')[0] === testJid.split('@')[0]).length, 1)
|
||||
assert.ok(conn.chats.get(gid))
|
||||
assert.ok(conn.chats.get(gid).metadata)
|
||||
})
|
||||
it('should update the group description', async () => {
|
||||
const newDesc = 'Wow this was set from Baileys'
|
||||
|
||||
const waitForEvent = new Promise (resolve => (
|
||||
conn.once ('group-update', ({jid, desc}) => {
|
||||
if (jid === gid && desc) {
|
||||
assert.strictEqual(desc, newDesc)
|
||||
assert.strictEqual(
|
||||
conn.chats.get(jid).metadata.desc,
|
||||
newDesc
|
||||
)
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
))
|
||||
await conn.groupUpdateDescription (gid, newDesc)
|
||||
await waitForEvent
|
||||
|
||||
const metadata = await conn.groupMetadata(gid)
|
||||
assert.strictEqual(metadata.desc, newDesc)
|
||||
})
|
||||
it('should send a message on the group', async () => {
|
||||
await sendAndRetrieveMessage(conn, 'Hello!', MessageType.text, {}, gid)
|
||||
})
|
||||
it('should delete a message on the group', async () => {
|
||||
const message = await sendAndRetrieveMessage(conn, 'Hello!', MessageType.text, {}, gid)
|
||||
await delay(1500)
|
||||
await conn.deleteMessage(message.key)
|
||||
})
|
||||
it('should quote a message on the group', async () => {
|
||||
const {messages} = await conn.loadMessages (gid, 100)
|
||||
const quotableMessage = messages.find (m => m.message)
|
||||
assert.ok (quotableMessage, 'need at least one message')
|
||||
|
||||
const response = await conn.sendMessage(gid, 'hello', MessageType.extendedText, {quoted: quotableMessage})
|
||||
const loaded = await conn.loadMessages(gid, 10)
|
||||
const message = loaded.messages.find (m => m.key.id === response.key.id)?.message?.extendedTextMessage
|
||||
assert.ok(message)
|
||||
assert.strictEqual (message.contextInfo.stanzaId, quotableMessage.key.id)
|
||||
})
|
||||
it('should update the subject', async () => {
|
||||
const subject = 'Baileyz ' + Math.floor(Math.random()*5)
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('chat-update', ({jid, name}) => {
|
||||
if (jid === gid) {
|
||||
assert.strictEqual(name, subject)
|
||||
assert.strictEqual(conn.chats.get(jid).name, subject)
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
await conn.groupUpdateSubject(gid, subject)
|
||||
await waitForEvent
|
||||
|
||||
const metadata = await conn.groupMetadata(gid)
|
||||
assert.strictEqual(metadata.subject, subject)
|
||||
})
|
||||
|
||||
it('should update the group settings', async () => {
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('group-update', ({jid, announce}) => {
|
||||
if (jid === gid) {
|
||||
assert.strictEqual (announce, 'true')
|
||||
assert.strictEqual(conn.chats.get(gid).metadata.announce, announce)
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
await conn.groupSettingChange (gid, GroupSettingChange.messageSend, true)
|
||||
|
||||
await waitForEvent
|
||||
conn.removeAllListeners ('group-update')
|
||||
|
||||
await delay (2000)
|
||||
await conn.groupSettingChange (gid, GroupSettingChange.settingsChange, true)
|
||||
})
|
||||
|
||||
it('should promote someone', async () => {
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('group-participants-update', ({ jid, action, participants }) => {
|
||||
if (jid === gid) {
|
||||
assert.strictEqual (action, 'promote')
|
||||
console.log(participants)
|
||||
console.log(conn.chats.get(jid).metadata)
|
||||
assert.ok(
|
||||
conn.chats.get(jid).metadata.participants.find(({ jid, isAdmin }) => (
|
||||
whatsappID(jid) === whatsappID(participants[0]) && isAdmin
|
||||
)),
|
||||
)
|
||||
resolve(undefined)
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
await conn.groupMakeAdmin(gid, [ testJid ])
|
||||
await waitForEvent
|
||||
})
|
||||
|
||||
it('should remove someone from a group', async () => {
|
||||
const metadata = await conn.groupMetadata (gid)
|
||||
if (metadata.participants.find(({jid}) => whatsappID(jid) === testJid)) {
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('group-participants-update', ({jid, participants, action}) => {
|
||||
if (jid === gid) {
|
||||
assert.strictEqual (participants[0], testJid)
|
||||
assert.strictEqual (action, 'remove')
|
||||
assert.deepStrictEqual(
|
||||
conn.chats.get(jid).metadata.participants.find(p => whatsappID(p.jid) === whatsappID(participants[0])),
|
||||
undefined
|
||||
)
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await conn.groupRemove(gid, [testJid])
|
||||
await waitForEvent
|
||||
} else console.log(`could not find testJid`)
|
||||
})
|
||||
|
||||
it('should leave the group', async () => {
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('chat-update', ({jid, read_only}) => {
|
||||
if (jid === gid) {
|
||||
assert.strictEqual (read_only, 'true')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
await conn.groupLeave(gid)
|
||||
await waitForEvent
|
||||
|
||||
await conn.groupMetadataMinimal (gid)
|
||||
})
|
||||
it('should archive the group', async () => {
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('chat-update', ({jid, archive}) => {
|
||||
if (jid === gid) {
|
||||
assert.strictEqual (archive, 'true')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
await conn.modifyChat(gid, ChatModification.archive)
|
||||
await waitForEvent
|
||||
})
|
||||
it('should delete the group', async () => {
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('chat-update', (chat) => {
|
||||
if (chat.jid === gid) {
|
||||
assert.strictEqual (chat['delete'], 'true')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
await conn.modifyChat(gid, 'delete')
|
||||
await waitForEvent
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import { deepStrictEqual, strictEqual } from 'assert'
|
||||
import { createWriteStream } from 'fs'
|
||||
import { readFile } from 'fs/promises'
|
||||
import { proto } from '../../WAMessage'
|
||||
import { MessageType } from '../WAConnection'
|
||||
import { aesEncrypWithIV, decryptMediaMessageBuffer, encryptedStream, getMediaKeys, getStream, hmacSign, sha256 } from '../WAConnection/Utils'
|
||||
import { WAConnectionTest } from './Common'
|
||||
|
||||
describe('Media Download Tests', () => {
|
||||
|
||||
it('should encrypt media streams correctly', async function() {
|
||||
const url = './Media/meme.jpeg'
|
||||
const streamValues = await encryptedStream({ url }, MessageType.image)
|
||||
|
||||
const buffer = await readFile(url)
|
||||
const mediaKeys = getMediaKeys(streamValues.mediaKey, MessageType.image)
|
||||
|
||||
const enc = aesEncrypWithIV(buffer, mediaKeys.cipherKey, mediaKeys.iv)
|
||||
const mac = hmacSign(Buffer.concat([mediaKeys.iv, enc]), mediaKeys.macKey).slice(0, 10)
|
||||
const body = Buffer.concat([enc, mac]) // body is enc + mac
|
||||
const fileSha256 = sha256(buffer)
|
||||
const fileEncSha256 = sha256(body)
|
||||
|
||||
deepStrictEqual(streamValues.fileSha256, fileSha256)
|
||||
strictEqual(streamValues.fileLength, buffer.length)
|
||||
deepStrictEqual(streamValues.mac, mac)
|
||||
deepStrictEqual(await readFile(streamValues.encBodyPath), body)
|
||||
deepStrictEqual(streamValues.fileEncSha256, fileEncSha256)
|
||||
|
||||
})
|
||||
})
|
||||
/*
|
||||
WAConnectionTest('Media Upload', conn => {
|
||||
|
||||
it('should upload the same file', async () => {
|
||||
const FILES = [
|
||||
{ url: './Media/meme.jpeg', type: MessageType.image },
|
||||
{ url: './Media/ma_gif.mp4', type: MessageType.video },
|
||||
{ url: './Media/sonata.mp3', type: MessageType.audio },
|
||||
]
|
||||
})
|
||||
|
||||
})*/
|
||||
@@ -1,268 +0,0 @@
|
||||
import { MessageType, Mimetype, delay, promiseTimeout, WA_MESSAGE_STATUS_TYPE, generateMessageID, WAMessage } from '../WAConnection'
|
||||
import { promises as fs } from 'fs'
|
||||
import * as assert from 'assert'
|
||||
import { WAConnectionTest, testJid, sendAndRetrieveMessage } from './Common'
|
||||
|
||||
WAConnectionTest('Messages', conn => {
|
||||
|
||||
it('should send a text message', async () => {
|
||||
const message = await sendAndRetrieveMessage(conn, 'hello fren', MessageType.text)
|
||||
assert.strictEqual(message.message.conversation || message.message.extendedTextMessage?.text, 'hello fren')
|
||||
})
|
||||
it('should send a pending message', async () => {
|
||||
const message = await sendAndRetrieveMessage(conn, 'hello fren', MessageType.text, { waitForAck: false })
|
||||
|
||||
await new Promise(resolve => conn.on('chat-update', update => {
|
||||
if (update.jid === testJid &&
|
||||
update.messages &&
|
||||
update.messages.first.key.id === message.key.id &&
|
||||
update.messages.first.status === WA_MESSAGE_STATUS_TYPE.SERVER_ACK) {
|
||||
resolve(undefined)
|
||||
}
|
||||
}))
|
||||
|
||||
})
|
||||
it('should forward a message', async () => {
|
||||
let {messages} = await conn.loadMessages (testJid, 1)
|
||||
await conn.forwardMessage (testJid, messages[0], true)
|
||||
|
||||
messages = (await conn.loadMessages (testJid, 1)).messages
|
||||
const message = messages.slice (-1)[0]
|
||||
const content = message.message[ Object.keys(message.message)[0] ]
|
||||
assert.strictEqual (content?.contextInfo?.isForwarded, true)
|
||||
})
|
||||
it('should send a link preview', async () => {
|
||||
const text = 'hello this is from https://www.github.com/adiwajshing/Baileys'
|
||||
const message = await sendAndRetrieveMessage(conn, text, MessageType.text, { detectLinks: true })
|
||||
const received = message.message.extendedTextMessage
|
||||
|
||||
assert.strictEqual(received.text, text)
|
||||
assert.ok (received.canonicalUrl)
|
||||
assert.ok (received.title)
|
||||
assert.ok (received.description)
|
||||
})
|
||||
it('should quote a message', async () => {
|
||||
const quoted = (await conn.loadMessages(testJid, 2)).messages[0]
|
||||
const message = await sendAndRetrieveMessage(conn, 'hello fren 2', MessageType.extendedText, { quoted })
|
||||
assert.strictEqual(
|
||||
message.message.extendedTextMessage.contextInfo.stanzaId,
|
||||
quoted.key.id
|
||||
)
|
||||
assert.strictEqual(
|
||||
message.message.extendedTextMessage.contextInfo.participant,
|
||||
quoted.key.fromMe ? conn.user.jid : quoted.key.id
|
||||
)
|
||||
})
|
||||
it('should upload media successfully', async () => {
|
||||
const content = await fs.readFile('./Media/sonata.mp3')
|
||||
// run 10 uploads
|
||||
for (let i = 0; i < 10;i++) {
|
||||
await conn.prepareMessageContent (content, MessageType.audio, { filename: 'audio.mp3', mimetype: Mimetype.mp4Audio })
|
||||
}
|
||||
})
|
||||
it('should send a gif', async () => {
|
||||
const message = await sendAndRetrieveMessage(conn, { url: './Media/ma_gif.mp4' }, MessageType.video, { mimetype: Mimetype.gif })
|
||||
|
||||
await conn.downloadAndSaveMediaMessage(message,'./Media/received_vid')
|
||||
})
|
||||
it('should send an audio', async () => {
|
||||
const content = await fs.readFile('./Media/sonata.mp3')
|
||||
const message = await sendAndRetrieveMessage(conn, content, MessageType.audio, { mimetype: Mimetype.mp4Audio })
|
||||
// check duration was okay
|
||||
assert.ok (message.message.audioMessage.seconds > 0)
|
||||
await conn.downloadAndSaveMediaMessage(message,'./Media/received_aud')
|
||||
})
|
||||
it('should send an audio as a voice note', async () => {
|
||||
const content = await fs.readFile('./Media/sonata.mp3')
|
||||
const message = await sendAndRetrieveMessage(conn, content, MessageType.audio, { mimetype: Mimetype.mp4Audio, ptt: true })
|
||||
|
||||
assert.ok (message.message.audioMessage.seconds > 0)
|
||||
assert.strictEqual (message.message?.audioMessage?.ptt, true)
|
||||
await conn.downloadAndSaveMediaMessage(message,'./Media/received_aud')
|
||||
})
|
||||
it('should send a jpeg image', async () => {
|
||||
const message = await sendAndRetrieveMessage(conn, { url: './Media/meme.jpeg' }, MessageType.image)
|
||||
assert.ok(message.message.imageMessage.jpegThumbnail.length > 0)
|
||||
const msg = await conn.downloadMediaMessage(message)
|
||||
assert.deepStrictEqual(msg, await fs.readFile('./Media/meme.jpeg'))
|
||||
})
|
||||
it('should send a remote jpeg image', async () => {
|
||||
const message = await sendAndRetrieveMessage(
|
||||
conn,
|
||||
{ url: 'https://www.memestemplates.com/wp-content/uploads/2020/05/tom-with-phone.jpg' },
|
||||
MessageType.image
|
||||
)
|
||||
assert.ok (message.message?.imageMessage?.jpegThumbnail)
|
||||
await conn.downloadMediaMessage(message)
|
||||
})
|
||||
it('should send a png image', async () => {
|
||||
const content = await fs.readFile('./Media/icon.png')
|
||||
const message = await sendAndRetrieveMessage(conn, content, MessageType.image, { mimetype: 'image/png' })
|
||||
assert.ok (message.message?.imageMessage?.jpegThumbnail)
|
||||
await conn.downloadMediaMessage(message)
|
||||
})
|
||||
it('should send a sticker', async () => {
|
||||
const content = await fs.readFile('./Media/octopus.webp')
|
||||
const message = await sendAndRetrieveMessage(conn, content, MessageType.sticker)
|
||||
|
||||
await conn.downloadMediaMessage(message)
|
||||
})
|
||||
/*it('should send an interactive message', async () => {
|
||||
|
||||
console.log (
|
||||
JSON.stringify(await conn.loadMessages (testJid, 5), null, '\t')
|
||||
)
|
||||
const message = conn.prepareMessageFromContent (
|
||||
testJid,
|
||||
{
|
||||
templateMessage: {
|
||||
fourRowTemplate: {
|
||||
content: {
|
||||
namespace: 'my-namespace',
|
||||
localizableParams: [
|
||||
|
||||
],
|
||||
params: ['hello!']
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
index: 0,
|
||||
quickReplyButton: {
|
||||
displayText: {
|
||||
params: ['my name jeff']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
quickReplyButton: {
|
||||
displayText: {
|
||||
params: ['my name NOT jeff'],
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{}
|
||||
)
|
||||
await conn.relayWAMessage (message)
|
||||
})*/
|
||||
it('should send an image & quote', async () => {
|
||||
const quoted = (await conn.loadMessages(testJid, 2)).messages[0]
|
||||
const content = await fs.readFile('./Media/meme.jpeg')
|
||||
const message = await sendAndRetrieveMessage(conn, content, MessageType.image, { quoted })
|
||||
|
||||
await conn.downloadMediaMessage(message) // check for successful decoding
|
||||
assert.strictEqual(message.message.imageMessage.contextInfo.stanzaId, quoted.key.id)
|
||||
})
|
||||
it('should send a message & delete it', async () => {
|
||||
const message = await sendAndRetrieveMessage(conn, 'hello fren', MessageType.text)
|
||||
await delay (2000)
|
||||
await conn.deleteMessage (testJid, message.key)
|
||||
})
|
||||
it('should clear the most recent message', async () => {
|
||||
const {messages} = await conn.loadMessages (testJid, 1)
|
||||
await delay (2000)
|
||||
await conn.clearMessage (messages[0].key)
|
||||
})
|
||||
it('should send media after close', async () => {
|
||||
const content = await fs.readFile('./Media/octopus.webp')
|
||||
await sendAndRetrieveMessage(conn, content, MessageType.sticker)
|
||||
|
||||
conn.close ()
|
||||
|
||||
await conn.connect ()
|
||||
|
||||
const content2 = await fs.readFile('./Media/cat.jpeg')
|
||||
await sendAndRetrieveMessage(conn, content2, MessageType.image)
|
||||
})
|
||||
it('should fail to send a text message', async () => {
|
||||
const JID = '1234-1234@g.us'
|
||||
const messageId = generateMessageID()
|
||||
conn.sendMessage(JID, 'hello', MessageType.text, { messageId })
|
||||
|
||||
await new Promise(resolve => (
|
||||
conn.on ('chat-update', async update => {
|
||||
console.log(messageId, update.messages?.first)
|
||||
if (
|
||||
update.jid === JID &&
|
||||
update.messages?.first.key.id === messageId &&
|
||||
update.messages.first.status === WA_MESSAGE_STATUS_TYPE.ERROR) {
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
))
|
||||
conn.removeAllListeners('chat-update')
|
||||
})
|
||||
it('should maintain message integrity', async () => {
|
||||
// loading twice does not alter the results
|
||||
const results = await Promise.all ([
|
||||
conn.loadMessages (testJid, 50),
|
||||
conn.loadMessages (testJid, 50)
|
||||
])
|
||||
assert.strictEqual (results[0].messages.length, results[1].messages.length)
|
||||
for (let i = 0; i < results[1].messages.length;i++) {
|
||||
assert.deepStrictEqual (
|
||||
results[0].messages[i].key,
|
||||
results[1].messages[i].key,
|
||||
`failed equal at ${i}`
|
||||
)
|
||||
}
|
||||
assert.ok (results[0].messages.length <= 50)
|
||||
|
||||
// check if messages match server
|
||||
let msgs = await conn.fetchMessagesFromWA (testJid, 50)
|
||||
for (let i = 0; i < results[1].messages.length;i++) {
|
||||
assert.deepStrictEqual (
|
||||
results[0].messages[i].key,
|
||||
msgs[i].key,
|
||||
`failed equal at ${i}`
|
||||
)
|
||||
}
|
||||
// check with some arbitary cursors
|
||||
let cursor = results[0].messages.slice(-1)[0].key
|
||||
|
||||
msgs = await conn.fetchMessagesFromWA (testJid, 20, cursor)
|
||||
let {messages} = await conn.loadMessages (testJid, 20, cursor)
|
||||
for (let i = 0; i < messages.length;i++) {
|
||||
assert.deepStrictEqual (
|
||||
messages[i].key,
|
||||
msgs[i].key,
|
||||
`failed equal at ${i}`
|
||||
)
|
||||
}
|
||||
for (let i = 0; i < 3;i++) {
|
||||
cursor = results[0].messages[i].key
|
||||
|
||||
msgs = await conn.fetchMessagesFromWA (testJid, 20, cursor)
|
||||
messages = (await conn.loadMessages (testJid, 20, cursor)).messages
|
||||
for (let i = 0; i < messages.length;i++) {
|
||||
assert.deepStrictEqual (messages[i].key, msgs[i].key, `failed equal at ${i}`)
|
||||
}
|
||||
|
||||
cursor = msgs[0].key
|
||||
|
||||
msgs = await conn.fetchMessagesFromWA (testJid, 20, cursor)
|
||||
messages = (await conn.loadMessages (testJid, 20, cursor)).messages
|
||||
for (let i = 0; i < messages.length;i++) {
|
||||
assert.deepStrictEqual (messages[i].key, msgs[i].key, `failed equal at ${i}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should deliver a message', async () => {
|
||||
const response = await conn.sendMessage(testJid, 'My Name Jeff', MessageType.text)
|
||||
const waitForUpdate =
|
||||
promiseTimeout(15000, resolve => {
|
||||
conn.on('chat-update', update => {
|
||||
if (update.messages?.first.key.id === response.key.id) {
|
||||
resolve(update.messages.first)
|
||||
}
|
||||
})
|
||||
}) as Promise<WAMessage>
|
||||
|
||||
const m = await waitForUpdate
|
||||
assert.ok (m.status >= WA_MESSAGE_STATUS_TYPE.DELIVERY_ACK)
|
||||
})
|
||||
})
|
||||
@@ -1,430 +0,0 @@
|
||||
import { Presence, ChatModification, delay, newMessagesDB, WA_DEFAULT_EPHEMERAL, MessageType, WAMessage } from '../WAConnection'
|
||||
import { promises as fs } from 'fs'
|
||||
import * as assert from 'assert'
|
||||
import got from 'got'
|
||||
import { WAConnectionTest, testJid, sendAndRetrieveMessage } from './Common'
|
||||
|
||||
WAConnectionTest('Misc', conn => {
|
||||
|
||||
it('should tell if someone has an account on WhatsApp', async () => {
|
||||
const response = await conn.isOnWhatsApp(testJid)
|
||||
assert.strictEqual(response, true)
|
||||
|
||||
const responseFail = await conn.isOnWhatsApp('abcd@s.whatsapp.net')
|
||||
assert.strictEqual(responseFail, false)
|
||||
})
|
||||
it('should return the status', async () => {
|
||||
const response = await conn.getStatus(testJid)
|
||||
assert.strictEqual(typeof response.status, 'string')
|
||||
})
|
||||
it('should update status', async () => {
|
||||
const newStatus = 'v cool status'
|
||||
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.on ('contact-update', ({jid, status}) => {
|
||||
if (jid === conn.user.jid) {
|
||||
assert.strictEqual (status, newStatus)
|
||||
conn.removeAllListeners ('contact-update')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const response = await conn.getStatus()
|
||||
assert.strictEqual(typeof response.status, 'string')
|
||||
|
||||
await delay (1000)
|
||||
|
||||
await conn.setStatus (newStatus)
|
||||
const response2 = await conn.getStatus()
|
||||
assert.strictEqual (response2.status, newStatus)
|
||||
|
||||
await waitForEvent
|
||||
|
||||
await delay (1000)
|
||||
|
||||
await conn.setStatus (response.status) // update back
|
||||
})
|
||||
it('should update profile name', async () => {
|
||||
const newName = 'v cool name'
|
||||
|
||||
await delay (1000)
|
||||
|
||||
const originalName = conn.user.name!
|
||||
|
||||
const waitForEvent = new Promise<void> (resolve => {
|
||||
conn.on ('contact-update', ({name}) => {
|
||||
assert.strictEqual (name, newName)
|
||||
conn.removeAllListeners ('contact-update')
|
||||
resolve ()
|
||||
})
|
||||
})
|
||||
|
||||
await conn.updateProfileName (newName)
|
||||
|
||||
await waitForEvent
|
||||
|
||||
await delay (1000)
|
||||
|
||||
assert.strictEqual (conn.user.name, newName)
|
||||
|
||||
await delay (1000)
|
||||
|
||||
await conn.updateProfileName (originalName) // update back
|
||||
})
|
||||
it('should return the stories', async () => {
|
||||
await conn.getStories()
|
||||
})
|
||||
|
||||
it('should return the profile picture correctly', async () => {
|
||||
// wait for chats
|
||||
await new Promise(resolve => (
|
||||
conn.once('initial-data-received', resolve)
|
||||
))
|
||||
const pictures = await Promise.all(
|
||||
conn.chats.all().slice(0, 15).map(({ jid }) => (
|
||||
conn.getProfilePicture(jid)
|
||||
.catch(err => '')
|
||||
))
|
||||
)
|
||||
// pictures should return correctly
|
||||
const truePictures = pictures.filter(pp => !!pp)
|
||||
assert.strictEqual(
|
||||
new Set(truePictures).size,
|
||||
truePictures.length
|
||||
)
|
||||
})
|
||||
|
||||
it('should change the profile picture', async () => {
|
||||
await delay (5000)
|
||||
|
||||
const ppUrl = await conn.getProfilePicture(conn.user.jid)
|
||||
const {rawBody: oldPP} = await got(ppUrl)
|
||||
|
||||
const newPP = await fs.readFile('./Media/cat.jpeg')
|
||||
await conn.updateProfilePicture(conn.user.jid, newPP)
|
||||
|
||||
await delay (10000)
|
||||
|
||||
await conn.updateProfilePicture (conn.user.jid, oldPP) // revert back
|
||||
})
|
||||
it('should send typing indicator', async () => {
|
||||
const response = await conn.updatePresence(testJid, Presence.composing)
|
||||
assert.ok(response)
|
||||
})
|
||||
it('should change a chat read status', async () => {
|
||||
const jids = conn.chats.all ().map (c => c.jid)
|
||||
for (let jid of jids.slice(0, 5)) {
|
||||
console.log (`changing read status for ${jid}`)
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.once ('chat-update', ({jid: tJid, count}) => {
|
||||
if (jid === tJid) {
|
||||
assert.ok (count < 0)
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
await conn.chatRead (jid, 'unread')
|
||||
await waitForEvent
|
||||
|
||||
await delay (5000)
|
||||
|
||||
await conn.chatRead (jid, 'read')
|
||||
}
|
||||
})
|
||||
it('should archive & unarchive', async () => {
|
||||
// wait for chats
|
||||
await new Promise(resolve => (
|
||||
conn.once('chats-received', ({ }) => resolve(undefined))
|
||||
))
|
||||
|
||||
const idx = conn.chats.all().findIndex(chat => chat.jid === testJid)
|
||||
await conn.modifyChat (testJid, ChatModification.archive)
|
||||
const idx2 = conn.chats.all().findIndex(chat => chat.jid === testJid)
|
||||
assert.ok(idx < idx2) // should move further down the array
|
||||
|
||||
await delay (2000)
|
||||
await conn.modifyChat (testJid, ChatModification.unarchive)
|
||||
const idx3 = conn.chats.all().findIndex(chat => chat.jid === testJid)
|
||||
assert.strictEqual(idx, idx3) // should be back there
|
||||
})
|
||||
it('should archive & unarchive on new message', async () => {
|
||||
// wait for chats
|
||||
await new Promise(resolve => (
|
||||
conn.once('chats-received', ({ }) => resolve(undefined))
|
||||
))
|
||||
|
||||
const idx = conn.chats.all().findIndex(chat => chat.jid === testJid)
|
||||
await conn.modifyChat (testJid, ChatModification.archive)
|
||||
const idx2 = conn.chats.all().findIndex(chat => chat.jid === testJid)
|
||||
assert.ok(idx < idx2) // should move further down the array
|
||||
|
||||
await delay (2000)
|
||||
await sendAndRetrieveMessage(conn, 'test', MessageType.text)
|
||||
// should be unarchived
|
||||
const idx3 = conn.chats.all().findIndex(chat => chat.jid === testJid)
|
||||
assert.strictEqual(idx, idx3) // should be back there
|
||||
})
|
||||
it('should pin & unpin a chat', async () => {
|
||||
await conn.modifyChat (testJid, ChatModification.pin)
|
||||
await delay (2000)
|
||||
await conn.modifyChat (testJid, ChatModification.unpin)
|
||||
})
|
||||
it('should mute & unmute a chat', async () => {
|
||||
const waitForEvent = new Promise (resolve => {
|
||||
conn.on ('chat-update', ({jid, mute}) => {
|
||||
if (jid === testJid ) {
|
||||
assert.ok (mute)
|
||||
conn.removeAllListeners ('chat-update')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
await conn.modifyChat (testJid, ChatModification.mute, 8*60*60*1000) // 8 hours in the future
|
||||
await waitForEvent
|
||||
await delay (2000)
|
||||
await conn.modifyChat (testJid, ChatModification.unmute)
|
||||
})
|
||||
it('should star/unstar messages', async () => {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
await conn.sendMessage(testJid, `Message ${i}`, MessageType.text)
|
||||
await delay(1000)
|
||||
}
|
||||
|
||||
let response = await conn.loadMessages(testJid, 5)
|
||||
let starred = response.messages.filter(m => m.starred)
|
||||
assert.strictEqual(starred.length, 0)
|
||||
|
||||
conn.starMessage(response.messages[2].key)
|
||||
await delay(2000)
|
||||
conn.starMessage(response.messages[4].key)
|
||||
await delay(2000)
|
||||
|
||||
response = await conn.loadMessages(testJid, 5)
|
||||
starred = response.messages.filter(m => m.starred)
|
||||
assert.strictEqual(starred.length, 2)
|
||||
await delay(2000)
|
||||
|
||||
conn.starMessage(response.messages[2].key, 'unstar')
|
||||
await delay(2000)
|
||||
|
||||
response = await conn.loadMessages(testJid, 5)
|
||||
starred = response.messages.filter(m => m.starred)
|
||||
assert.strictEqual(starred.length, 1)
|
||||
})
|
||||
it('should clear a chat', async () => {
|
||||
// Uses chat with yourself to avoid losing chats
|
||||
const selfJid = conn.user.jid
|
||||
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
await conn.sendMessage(selfJid, `Message ${i}`, MessageType.text)
|
||||
await delay(1000)
|
||||
}
|
||||
|
||||
let response = await conn.loadMessages(selfJid, 50)
|
||||
const initialCount = response.messages.length
|
||||
|
||||
assert.ok(response.messages.length >= 0)
|
||||
|
||||
conn.starMessage(response.messages[2].key)
|
||||
await delay(2000)
|
||||
conn.starMessage(response.messages[4].key)
|
||||
await delay(2000)
|
||||
|
||||
await conn.modifyChat(selfJid, ChatModification.clear)
|
||||
await delay(2000)
|
||||
|
||||
response = await conn.loadMessages(selfJid, 50)
|
||||
await delay(2000)
|
||||
assert.ok(response.messages.length < initialCount)
|
||||
assert.ok(response.messages.length > 1)
|
||||
|
||||
await conn.modifyChat(selfJid, ChatModification.clear, true)
|
||||
await delay(2000)
|
||||
|
||||
response = await conn.loadMessages(selfJid, 50)
|
||||
assert.strictEqual(response.messages.length, 1)
|
||||
})
|
||||
it('should return search results', async () => {
|
||||
const jids = [null, testJid]
|
||||
for (let i in jids) {
|
||||
let response = await conn.searchMessages('Hello', jids[i], 25, 1)
|
||||
assert.ok (response.messages)
|
||||
assert.ok (response.messages.length >= 0)
|
||||
|
||||
response = await conn.searchMessages('剛剛試咗😋一個字', jids[i], 25, 1)
|
||||
assert.ok (response.messages)
|
||||
assert.ok (response.messages.length >= 0)
|
||||
}
|
||||
})
|
||||
|
||||
it('should load a single message', async () => {
|
||||
const {messages} = await conn.loadMessages (testJid, 25)
|
||||
for (var message of messages) {
|
||||
const loaded = await conn.loadMessage (testJid, message.key.id)
|
||||
assert.strictEqual (loaded.key.id, message.key.id, `loaded message ${JSON.stringify(message)} incorrectly`)
|
||||
await delay (500)
|
||||
}
|
||||
})
|
||||
// open the other phone and look at the updates to really verify stuff
|
||||
it('should send presence updates', async () => {
|
||||
conn.shouldLogMessages = true
|
||||
conn.requestPresenceUpdate(testJid)
|
||||
|
||||
const sequence = [ Presence.available, Presence.composing, Presence.paused, Presence.recording, Presence.paused, Presence.unavailable ]
|
||||
for (const presence of sequence) {
|
||||
await delay(5000)
|
||||
await conn.updatePresence(presence !== Presence.unavailable ? testJid : null, presence)
|
||||
//console.log(conn.messageLog.slice(-1))
|
||||
console.log('sent update ', presence)
|
||||
}
|
||||
})
|
||||
it('should generate link previews correctly', async () => {
|
||||
await conn.generateLinkPreview ('hello this is from https://www.github.com/adiwajshing/Baileys')
|
||||
// two links should fail
|
||||
await assert.rejects (
|
||||
conn.generateLinkPreview ('I sent links to https://teachyourselfcs.com/ and https://www.fast.ai/')
|
||||
)
|
||||
})
|
||||
// this test requires quite a few messages with the test JID
|
||||
it('should detect overlaps and clear messages accordingly', async () => {
|
||||
// wait for chats
|
||||
await new Promise(resolve => (
|
||||
conn.once('initial-data-received', resolve)
|
||||
))
|
||||
|
||||
conn.maxCachedMessages = 100
|
||||
|
||||
const chat = conn.chats.get(testJid)
|
||||
const oldCount = chat.messages.length
|
||||
console.log(`test chat has ${oldCount} pre-loaded messages`)
|
||||
// load 100 messages
|
||||
await conn.loadMessages(testJid, 100, undefined)
|
||||
assert.strictEqual(chat.messages.length, 100)
|
||||
|
||||
conn.close()
|
||||
// remove all latest messages
|
||||
chat.messages = newMessagesDB( chat.messages.all().slice(0, 20) )
|
||||
|
||||
const task = new Promise(resolve => (
|
||||
conn.on('initial-data-received', ({ chatsWithMissingMessages }) => {
|
||||
assert.strictEqual(Object.keys(chatsWithMissingMessages).length, 1)
|
||||
const missing = chatsWithMissingMessages.find(({ jid }) => jid === testJid)
|
||||
assert.ok(missing, 'missing message not detected')
|
||||
assert.strictEqual(
|
||||
conn.chats.get(testJid).messages.length,
|
||||
missing.count
|
||||
)
|
||||
assert.strictEqual(missing.count, oldCount)
|
||||
resolve(undefined)
|
||||
})
|
||||
))
|
||||
|
||||
await conn.connect()
|
||||
|
||||
await task
|
||||
})
|
||||
|
||||
it('should toggle disappearing messages', async () => {
|
||||
let chat = conn.chats.get(testJid)
|
||||
if (!chat) {
|
||||
// wait for chats
|
||||
await new Promise(resolve => (
|
||||
conn.once('chats-received', resolve)
|
||||
))
|
||||
chat = conn.chats.get(testJid)
|
||||
}
|
||||
|
||||
const waitForChatUpdate = (ephemeralOn: boolean) => (
|
||||
new Promise(resolve => (
|
||||
conn.on('chat-update', ({ jid, ephemeral }) => {
|
||||
if (jid === testJid && typeof ephemeral !== 'undefined') {
|
||||
assert.strictEqual(!!(+ephemeral), ephemeralOn)
|
||||
assert.strictEqual(!!(+chat.ephemeral), ephemeralOn)
|
||||
resolve(undefined)
|
||||
conn.removeAllListeners('chat-update')
|
||||
}
|
||||
})
|
||||
))
|
||||
)
|
||||
const toggleDisappearingMessages = async (on: boolean) => {
|
||||
const update = waitForChatUpdate(on)
|
||||
await conn.toggleDisappearingMessages(testJid, on ? WA_DEFAULT_EPHEMERAL : 0)
|
||||
await update
|
||||
}
|
||||
|
||||
if (!chat.eph_setting_ts) {
|
||||
await toggleDisappearingMessages(true)
|
||||
}
|
||||
|
||||
await delay(1000)
|
||||
|
||||
let msg = await sendAndRetrieveMessage(
|
||||
conn,
|
||||
'This will go poof 😱',
|
||||
MessageType.text
|
||||
)
|
||||
assert.ok(msg.message?.ephemeralMessage)
|
||||
|
||||
const contextInfo = msg.message?.ephemeralMessage?.message?.extendedTextMessage?.contextInfo
|
||||
assert.strictEqual(contextInfo.expiration, chat.ephemeral)
|
||||
assert.strictEqual(+contextInfo.ephemeralSettingTimestamp, +chat.eph_setting_ts)
|
||||
// test message deletion
|
||||
await conn.deleteMessage(testJid, msg.key)
|
||||
|
||||
await delay(1000)
|
||||
|
||||
await toggleDisappearingMessages(false)
|
||||
|
||||
await delay(1000)
|
||||
|
||||
msg = await sendAndRetrieveMessage(
|
||||
conn,
|
||||
'This will not go poof 😔',
|
||||
MessageType.text
|
||||
)
|
||||
assert.ok(msg.message.extendedTextMessage)
|
||||
})
|
||||
it('should block & unblock a user', async () => {
|
||||
const blockedCount = conn.blocklist.length;
|
||||
|
||||
const waitForEventAdded = new Promise<void> (resolve => {
|
||||
conn.once ('blocklist-update', ({added}) => {
|
||||
assert.ok (added.length)
|
||||
resolve ()
|
||||
})
|
||||
})
|
||||
|
||||
await conn.blockUser (testJid, 'add')
|
||||
assert.strictEqual(conn.blocklist.length, blockedCount + 1);
|
||||
await waitForEventAdded
|
||||
|
||||
await delay (2000)
|
||||
const waitForEventRemoved = new Promise<void> (resolve => {
|
||||
conn.once ('blocklist-update', ({removed}) => {
|
||||
assert.ok (removed.length)
|
||||
resolve ()
|
||||
})
|
||||
})
|
||||
|
||||
await conn.blockUser (testJid, 'remove')
|
||||
assert.strictEqual(conn.blocklist.length, blockedCount);
|
||||
await waitForEventRemoved
|
||||
})
|
||||
it('should exit an invalid query', async () => {
|
||||
// try and send an already sent message
|
||||
let msg: WAMessage
|
||||
await conn.findMessage(testJid, 5, m => {
|
||||
if(m.key.fromMe) {
|
||||
msg = m
|
||||
return true
|
||||
}
|
||||
})
|
||||
try {
|
||||
await conn.relayWAMessage(msg)
|
||||
assert.fail('should not have sent')
|
||||
} catch(error) {
|
||||
assert.strictEqual(error.status, 422)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,111 +0,0 @@
|
||||
import { strict as assert } from 'assert'
|
||||
import { Mutex } from '../WAConnection/Mutex'
|
||||
|
||||
const DEFAULT_WAIT = 1000
|
||||
|
||||
class MyClass {
|
||||
didDoWork = false
|
||||
values: { [k: string]: number } = {}
|
||||
counter = 0
|
||||
|
||||
@Mutex ()
|
||||
async myFunction () {
|
||||
if (this.didDoWork) return
|
||||
|
||||
await new Promise (resolve => setTimeout(resolve, DEFAULT_WAIT))
|
||||
if (this.didDoWork) {
|
||||
throw new Error ('work already done')
|
||||
}
|
||||
this.didDoWork = true
|
||||
}
|
||||
@Mutex (key => key)
|
||||
async myKeyedFunction (key: string) {
|
||||
if (!this.values[key]) {
|
||||
await new Promise (resolve => setTimeout(resolve, DEFAULT_WAIT))
|
||||
if (this.values[key]) throw new Error ('value already set for ' + key)
|
||||
this.values[key] = Math.floor(Math.random ()*100)
|
||||
}
|
||||
return this.values[key]
|
||||
}
|
||||
@Mutex (key => key)
|
||||
async myQueingFunction (key: string) {
|
||||
await new Promise (resolve => setTimeout(resolve, DEFAULT_WAIT))
|
||||
}
|
||||
@Mutex ()
|
||||
async myErrorFunction () {
|
||||
await new Promise (resolve => setTimeout(resolve, 100))
|
||||
this.counter += 1
|
||||
if (this.counter % 2 === 0) {
|
||||
throw new Error ('failed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe ('garbage', () => {
|
||||
it ('should only execute once', async () => {
|
||||
const stuff = new MyClass ()
|
||||
const start = new Date ()
|
||||
await Promise.all ([...Array(1000)].map(() => stuff.myFunction ()))
|
||||
const diff = new Date ().getTime()-start.getTime()
|
||||
assert.ok (diff < DEFAULT_WAIT*1.25)
|
||||
})
|
||||
it ('should only execute once based on the key', async () => {
|
||||
const stuff = new MyClass ()
|
||||
const start = new Date ()
|
||||
/*
|
||||
In this test, the mutex will lock the function based on the key.
|
||||
|
||||
So, if the function with argument `key1` is underway
|
||||
and another function with key `key1` is called,
|
||||
the call is blocked till the first function completes.
|
||||
However, if argument `key2` is passed, the function is allowed to pass.
|
||||
*/
|
||||
const keys = ['key1', 'key2', 'key3']
|
||||
const duplicates = 1000
|
||||
const results = await Promise.all (
|
||||
keys.flatMap (key => (
|
||||
[...Array(duplicates)].map(() => stuff.myKeyedFunction (key))
|
||||
))
|
||||
)
|
||||
assert.deepStrictEqual (
|
||||
results.slice(0, duplicates).filter (r => r !== results[0]),
|
||||
[]
|
||||
)
|
||||
|
||||
const diff = new Date ().getTime()-start.getTime()
|
||||
assert.ok (diff < DEFAULT_WAIT*1.25)
|
||||
})
|
||||
it ('should execute operations in a queue', async () => {
|
||||
const stuff = new MyClass ()
|
||||
const start = new Date ()
|
||||
|
||||
const keys = ['key1', 'key2', 'key3']
|
||||
|
||||
await Promise.all (
|
||||
keys.flatMap (key => (
|
||||
[...Array(2)].map(() => stuff.myQueingFunction (key))
|
||||
))
|
||||
)
|
||||
|
||||
const diff = new Date ().getTime()-start.getTime()
|
||||
assert.ok (diff < DEFAULT_WAIT*2.2 && diff > DEFAULT_WAIT*1.5)
|
||||
})
|
||||
it ('should throw an error on selected items', async () => {
|
||||
const stuff = new MyClass ()
|
||||
const start = new Date ()
|
||||
|
||||
const WAIT = 100
|
||||
const FUNCS = 40
|
||||
const results = await Promise.all (
|
||||
[...Array(FUNCS)].map(() => stuff.myErrorFunction ().catch(err => err.message))
|
||||
)
|
||||
|
||||
const diff = new Date ().getTime()-start.getTime()
|
||||
assert.ok (diff < WAIT*FUNCS*1.1)
|
||||
|
||||
assert.strictEqual (
|
||||
results.filter (r => r === 'failed').length,
|
||||
FUNCS/2 // half should fail
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,112 +0,0 @@
|
||||
|
||||
describe ('Reconnects', () => {
|
||||
const verifyConnectionOpen = async (conn: Connection) => {
|
||||
expect((await conn.getState()).user).toBeDefined()
|
||||
let failed = false
|
||||
// check that the connection stays open
|
||||
conn.ev.on('state.update', ({ connection, lastDisconnect }) => {
|
||||
if(connection === 'close' && !!lastDisconnect.error) {
|
||||
failed = true
|
||||
}
|
||||
})
|
||||
await delay (60*1000)
|
||||
conn.close ()
|
||||
|
||||
expect(failed).toBe(false)
|
||||
}
|
||||
it('should dispose correctly on bad_session', async () => {
|
||||
const conn = makeConnection({
|
||||
reconnectMode: 'on-any-error',
|
||||
credentials: './auth_info.json',
|
||||
maxRetries: 2,
|
||||
connectCooldownMs: 500
|
||||
})
|
||||
let gotClose0 = false
|
||||
let gotClose1 = false
|
||||
|
||||
const openPromise = conn.open()
|
||||
|
||||
conn.getSocket().ev.once('ws-close', () => {
|
||||
gotClose0 = true
|
||||
})
|
||||
conn.ev.on('state.update', ({ lastDisconnect }) => {
|
||||
//@ts-ignore
|
||||
if(lastDisconnect?.error?.output?.statusCode === DisconnectReason.badSession) {
|
||||
gotClose1 = true
|
||||
}
|
||||
})
|
||||
setTimeout (() => conn.getSocket().ws.emit ('message', Buffer.from('some-tag,sdjjij1jo2ejo1je')), 1500)
|
||||
await openPromise
|
||||
|
||||
console.log('opened connection')
|
||||
|
||||
await delay(1000)
|
||||
conn.getSocket().ws.emit ('message', Buffer.from('some-tag,sdjjij1jo2ejo1je'))
|
||||
|
||||
await delay(2000)
|
||||
await conn.waitForConnection()
|
||||
|
||||
conn.close()
|
||||
|
||||
expect(gotClose0).toBe(true)
|
||||
expect(gotClose1).toBe(true)
|
||||
}, 20_000)
|
||||
/**
|
||||
* the idea is to test closing the connection at multiple points in the connection
|
||||
* and see if the library cleans up resources correctly
|
||||
*/
|
||||
it('should cleanup correctly', async () => {
|
||||
const conn = makeConnection({
|
||||
reconnectMode: 'on-any-error',
|
||||
credentials: './auth_info.json'
|
||||
})
|
||||
let timeoutMs = 100
|
||||
while (true) {
|
||||
let tmout = setTimeout (() => {
|
||||
conn.close()
|
||||
}, timeoutMs)
|
||||
try {
|
||||
await conn.open()
|
||||
clearTimeout (tmout)
|
||||
break
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
// exponentially increase the timeout disconnect
|
||||
timeoutMs *= 2
|
||||
}
|
||||
await verifyConnectionOpen(conn)
|
||||
}, 120_000)
|
||||
/**
|
||||
* the idea is to test closing the connection at multiple points in the connection
|
||||
* and see if the library cleans up resources correctly
|
||||
*/
|
||||
it('should disrupt connect loop', async () => {
|
||||
const conn = makeConnection({
|
||||
reconnectMode: 'on-any-error',
|
||||
credentials: './auth_info.json'
|
||||
})
|
||||
|
||||
let timeout = 1000
|
||||
let tmout
|
||||
const endConnection = async () => {
|
||||
while (!conn.getSocket()) {
|
||||
await delay(100)
|
||||
}
|
||||
conn.getSocket().end(Boom.preconditionRequired('conn close'))
|
||||
|
||||
while (conn.getSocket()) {
|
||||
await delay(100)
|
||||
}
|
||||
|
||||
timeout *= 2
|
||||
tmout = setTimeout (endConnection, timeout)
|
||||
}
|
||||
tmout = setTimeout (endConnection, timeout)
|
||||
|
||||
await conn.open()
|
||||
clearTimeout (tmout)
|
||||
|
||||
await verifyConnectionOpen(conn)
|
||||
}, 120_000)
|
||||
})
|
||||
@@ -1,95 +0,0 @@
|
||||
import BinaryNode from '../BinaryNode'
|
||||
|
||||
describe('Binary Coding Tests', () => {
|
||||
|
||||
const TEST_VECTORS: [string, BinaryNode][] = [
|
||||
[
|
||||
'f806092f5a0a10f804f80234fc6c0a350a1b39313735323938373131313740732e77686174736170702e6e657410011a143345423030393637354537454433374141424632122b0a292a7069616e6f20726f6f6d2074696d696e6773206172653a2a0a20363a3030414d2d31323a3030414d18b3faa7f3052003f80234fc4c0a410a1b39313735323938373131313740732e77686174736170702e6e657410001a20304643454335333330463634393239433645394132434646443242433845414418bdfaa7f305c00101f80234fc930a350a1b39313735323938373131313740732e77686174736170702e6e657410011a14334542303033433742353339414644303937353312520a50536f727279206672656e2c204920636f756c646e277420756e6465727374616e6420274c69627261272e2054797065202768656c702720746f206b6e6f77207768617420616c6c20492063616e20646f18c1faa7f3052003f80234fc540a410a1b39313735323938373131313740732e77686174736170702e6e657410001a20413132333042384436423041314437393345433241453245413043313638443812090a076c69627261727918c2faa7f305',
|
||||
new BinaryNode(
|
||||
'action',
|
||||
{ last: 'true', add: 'before' },
|
||||
[
|
||||
new BinaryNode(
|
||||
'message',
|
||||
{},
|
||||
{
|
||||
key: { remoteJid: '917529871117@s.whatsapp.net', fromMe: true, id: '3EB009675E7ED37AABF2' },
|
||||
message: { conversation: '*piano room timings are:*\n 6:00AM-12:00AM' },
|
||||
messageTimestamp: '1584004403',
|
||||
status: 'DELIVERY_ACK',
|
||||
} as any
|
||||
),
|
||||
new BinaryNode(
|
||||
'message',
|
||||
{},
|
||||
{
|
||||
key: {
|
||||
remoteJid: '917529871117@s.whatsapp.net',
|
||||
fromMe: false,
|
||||
id: '0FCEC5330F64929C6E9A2CFFD2BC8EAD',
|
||||
},
|
||||
messageTimestamp: '1584004413',
|
||||
messageStubType: 'REVOKE',
|
||||
} as any
|
||||
),
|
||||
new BinaryNode(
|
||||
'message',
|
||||
{},
|
||||
{
|
||||
key: { remoteJid: '917529871117@s.whatsapp.net', fromMe: true, id: '3EB003C7B539AFD09753' },
|
||||
message: {
|
||||
conversation:
|
||||
"Sorry fren, I couldn't understand 'Libra'. Type 'help' to know what all I can do",
|
||||
},
|
||||
messageTimestamp: '1584004417',
|
||||
status: 'DELIVERY_ACK',
|
||||
} as any
|
||||
),
|
||||
new BinaryNode(
|
||||
'message',
|
||||
{},
|
||||
{
|
||||
key: {
|
||||
remoteJid: '917529871117@s.whatsapp.net',
|
||||
fromMe: false,
|
||||
id: 'A1230B8D6B0A1D793EC2AE2EA0C168D8',
|
||||
},
|
||||
message: { conversation: 'library' },
|
||||
messageTimestamp: '1584004418',
|
||||
} as any
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
[
|
||||
'f8063f2dfafc0831323334353637385027fc0431323334f801f80228fc0701020304050607',
|
||||
new BinaryNode(
|
||||
'picture',
|
||||
{jid: '12345678@s.whatsapp.net', id: '1234'},
|
||||
[
|
||||
new BinaryNode(
|
||||
'image',
|
||||
{},
|
||||
Buffer.from([1,2,3,4,5,6,7])
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
it('should encode/decode strings', () => {
|
||||
for(const [input, output] of TEST_VECTORS) {
|
||||
const buff = Buffer.from(input, 'hex')
|
||||
const node = BinaryNode.from(buff)
|
||||
expect(
|
||||
JSON.parse(JSON.stringify(node))
|
||||
).toStrictEqual(
|
||||
JSON.parse(JSON.stringify(output))
|
||||
)
|
||||
expect(
|
||||
node.toBuffer().toString('hex')
|
||||
).toStrictEqual(
|
||||
input
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,94 +0,0 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import P from 'pino'
|
||||
import BinaryNode from '../BinaryNode'
|
||||
import makeConnection from '../Connection'
|
||||
import { delay } from '../Utils/generics'
|
||||
|
||||
describe('QR Generation', () => {
|
||||
it('should generate QR', async () => {
|
||||
const QR_GENS = 1
|
||||
const {ev} = makeConnection({
|
||||
maxRetries: 0,
|
||||
maxQRCodes: QR_GENS,
|
||||
logger: P({ level: 'trace' })
|
||||
})
|
||||
let calledQR = 0
|
||||
ev.on('connection.update', ({ qr }) => {
|
||||
if(qr) calledQR += 1
|
||||
})
|
||||
|
||||
await expect(open()).rejects.toThrowError('Too many QR codes')
|
||||
expect(calledQR).toBeGreaterThanOrEqual(QR_GENS)
|
||||
}, 60_000)
|
||||
})
|
||||
|
||||
describe('Test Connect', () => {
|
||||
|
||||
const logger = P({ level: 'trace' })
|
||||
|
||||
it('should connect', async () => {
|
||||
|
||||
logger.info('please be ready to scan with your phone')
|
||||
|
||||
const conn = makeConnection({
|
||||
logger,
|
||||
printQRInTerminal: true
|
||||
})
|
||||
await conn.waitForConnection(true)
|
||||
const { user, isNewLogin } = await conn.getState()
|
||||
expect(user).toHaveProperty('jid')
|
||||
expect(user).toHaveProperty('name')
|
||||
expect(isNewLogin).toBe(true)
|
||||
|
||||
conn.end(undefined)
|
||||
}, 65_000)
|
||||
|
||||
it('should restore session', async () => {
|
||||
let conn = makeConnection({
|
||||
printQRInTerminal: true,
|
||||
logger,
|
||||
})
|
||||
await conn.waitForConnection(true)
|
||||
conn.end(undefined)
|
||||
|
||||
await delay(2500)
|
||||
|
||||
conn = makeConnection({
|
||||
printQRInTerminal: true,
|
||||
logger,
|
||||
})
|
||||
await conn.waitForConnection(true)
|
||||
|
||||
const { user, isNewLogin, qr } = await conn.getState()
|
||||
expect(user).toHaveProperty('jid')
|
||||
expect(user).toHaveProperty('name')
|
||||
expect(isNewLogin).toBe(false)
|
||||
expect(qr).toBe(undefined)
|
||||
|
||||
conn.end(undefined)
|
||||
}, 65_000)
|
||||
|
||||
it('should logout', async () => {
|
||||
let conn = makeConnection({
|
||||
printQRInTerminal: true,
|
||||
logger,
|
||||
})
|
||||
await conn.waitForConnection(true)
|
||||
|
||||
const { user, qr } = await conn.getState()
|
||||
expect(user).toHaveProperty('jid')
|
||||
expect(user).toHaveProperty('name')
|
||||
expect(qr).toBe(undefined)
|
||||
|
||||
const credentials = conn.getAuthInfo()
|
||||
await conn.logout()
|
||||
|
||||
conn = makeConnection({
|
||||
credentials,
|
||||
logger
|
||||
})
|
||||
await expect(
|
||||
conn.waitForConnection()
|
||||
).rejects.toThrowError('Unexpected error in login')
|
||||
}, 65_000)
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
describe('Message Generation', () => {
|
||||
|
||||
it('should generate a text message', () => {
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,165 +0,0 @@
|
||||
import BinaryNode from '../BinaryNode'
|
||||
import makeConnection from '../makeConnection'
|
||||
import { delay } from '../WAConnection/Utils'
|
||||
|
||||
describe('Queries', () => {
|
||||
/*it ('should correctly send updates for chats', async () => {
|
||||
const conn = makeConnection({
|
||||
pendingRequestTimeoutMs: undefined,
|
||||
credentials: './auth_info.json'
|
||||
})
|
||||
const task = new Promise(resolve => conn.once('chats-received', resolve))
|
||||
await conn.connect ()
|
||||
await task
|
||||
|
||||
conn.close ()
|
||||
|
||||
const oldChat = conn.chats.all()[0]
|
||||
oldChat.archive = 'true' // mark the first chat as archived
|
||||
oldChat.modify_tag = '1234' // change modify tag to detect change
|
||||
|
||||
const promise = new Promise(resolve => conn.once('chats-update', resolve))
|
||||
|
||||
const result = await conn.connect ()
|
||||
assert.ok (!result.newConnection)
|
||||
|
||||
const chats = await promise as Partial<WAChat>[]
|
||||
const chat = chats.find (c => c.jid === oldChat.jid)
|
||||
assert.ok (chat)
|
||||
|
||||
assert.ok ('archive' in chat)
|
||||
assert.strictEqual (Object.keys(chat).length, 3)
|
||||
assert.strictEqual (Object.keys(chats).length, 1)
|
||||
|
||||
conn.close ()
|
||||
})
|
||||
it ('should correctly send updates for contacts', async () => {
|
||||
const conn = makeConnection ()
|
||||
conn.pendingRequestTimeoutMs = null
|
||||
conn.loadAuthInfo('./auth_info.json')
|
||||
|
||||
const task: any = new Promise(resolve => conn.once('contacts-received', resolve))
|
||||
await conn.connect ()
|
||||
const initialResult = await task
|
||||
assert.strictEqual(
|
||||
initialResult.updatedContacts.length,
|
||||
Object.keys(conn.contacts).length
|
||||
)
|
||||
|
||||
|
||||
conn.close ()
|
||||
|
||||
const [jid] = Object.keys(conn.contacts)
|
||||
const oldContact = conn.contacts[jid]
|
||||
oldContact.name = 'Lol'
|
||||
oldContact.index = 'L'
|
||||
|
||||
const promise = new Promise(resolve => conn.once('contacts-received', resolve))
|
||||
|
||||
const result = await conn.connect ()
|
||||
assert.ok (!result.newConnection)
|
||||
|
||||
const {updatedContacts} = await promise as { updatedContacts: Partial<WAContact>[] }
|
||||
const contact = updatedContacts.find (c => c.jid === jid)
|
||||
assert.ok (contact)
|
||||
|
||||
assert.ok ('name' in contact)
|
||||
assert.strictEqual (Object.keys(contact).length, 3)
|
||||
assert.strictEqual (Object.keys(updatedContacts).length, 1)
|
||||
|
||||
conn.close ()
|
||||
})*/
|
||||
it('should queue requests when closed', async () => {
|
||||
const conn = makeConnection({
|
||||
credentials: './auth_info.json'
|
||||
})
|
||||
await conn.open()
|
||||
await delay(2000)
|
||||
|
||||
conn.close()
|
||||
const { user: { jid } } = await conn.getState()
|
||||
const task: Promise<any> = conn.query({
|
||||
json: ['query', 'Status', jid]
|
||||
})
|
||||
|
||||
await delay(2000)
|
||||
|
||||
conn.open()
|
||||
const json = await task
|
||||
|
||||
expect(json.status).toBeDefined()
|
||||
|
||||
conn.close()
|
||||
}, 65_000)
|
||||
|
||||
it('[MANUAL] should recieve query response after phone disconnect', async () => {
|
||||
const conn = makeConnection ({
|
||||
printQRInTerminal: true,
|
||||
credentials: './auth_info.json'
|
||||
})
|
||||
await conn.open()
|
||||
const { phoneConnected } = await conn.getState()
|
||||
expect(phoneConnected).toBe(true)
|
||||
|
||||
try {
|
||||
const waitForEvent = expect => new Promise (resolve => {
|
||||
conn.ev.on('state.update', ({phoneConnected}) => {
|
||||
if (phoneConnected === expect) {
|
||||
conn.ev.removeAllListeners('state.update')
|
||||
resolve(undefined)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
console.log('disconnect your phone from the internet')
|
||||
await delay(10_000)
|
||||
console.log('phone should be disconnected now, testing...')
|
||||
|
||||
const query = conn.query({
|
||||
json: new BinaryNode(
|
||||
'query',
|
||||
{
|
||||
epoch: conn.getSocket().currentEpoch().toString(),
|
||||
type: 'message',
|
||||
jid: '1234@s.whatsapp.net',
|
||||
kind: 'before',
|
||||
count: '10',
|
||||
}
|
||||
),
|
||||
requiresPhoneConnection: true,
|
||||
expect200: false
|
||||
})
|
||||
await waitForEvent(false)
|
||||
|
||||
console.log('reconnect your phone to the internet')
|
||||
await waitForEvent(true)
|
||||
|
||||
console.log('reconnected successfully')
|
||||
|
||||
await expect(query).resolves.toBeDefined()
|
||||
} finally {
|
||||
conn.close()
|
||||
}
|
||||
}, 65_000)
|
||||
|
||||
it('should re-execute query on connection closed error', async () => {
|
||||
const conn = makeConnection({
|
||||
credentials: './auth_info.json'
|
||||
})
|
||||
await conn.open()
|
||||
const { user: { jid } } = await conn.getState()
|
||||
const task: Promise<any> = conn.query({ json: ['query', 'Status', jid], waitForOpen: true })
|
||||
|
||||
await delay(20)
|
||||
// fake cancel the connection
|
||||
conn.getSocket().ev.emit('message', '1234,["Pong",false]')
|
||||
|
||||
await delay(2000)
|
||||
|
||||
const json = await task
|
||||
|
||||
expect(json.status).toBeDefined()
|
||||
|
||||
conn.close()
|
||||
}, 65_000)
|
||||
})
|
||||
Reference in New Issue
Block a user