-Removed unread messages while connecting
-Added KeyedDB integration
-pdf mimetype fix + mimetype can be string now
This commit is contained in:
Adhiraj
2020-07-31 17:32:15 +05:30
parent 44bf842724
commit b06f53401f
8 changed files with 85 additions and 66 deletions

View File

@@ -17,11 +17,12 @@ async function example() {
client.logLevel = MessageLogLevel.info // set to unhandled to see what kind of stuff you can implement
// connect or timeout in 20 seconds (loads the auth file credentials if present)
const [user, chats, contacts, unread] = await client.connect('./auth_info.json', 20 * 1000)
const [user, chats, contacts] = await client.connect('./auth_info.json', 20 * 1000)
const unread = chats.all().flatMap (chat => chat.messages.slice(chat.messages.length-chat.count))
console.log('oh hello ' + user.name + ' (' + user.id + ')')
console.log('you have ' + unread.length + ' unread messages')
console.log('you have ' + chats.length + ' chats & ' + contacts.length + ' contacts')
console.log('you have ' + chats.all().length + ' chats & ' + contacts.length + ' contacts')
console.log ('you have ' + unread.length + ' unread messages')
const authInfo = client.base64EncodedAuthInfo() // get all the auth info we need to restore this session
fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file

View File

@@ -34,35 +34,53 @@ Set the phone number you can randomly send messages to in a `.env` file with `TE
## Connecting
``` ts
const client = new WAClient()
import { WAClient } from '@adiwajshing/baileys'
client.connect()
.then (([user, chats, contacts, unread]) => {
async function connectToWhatsApp () {
const client = new WAClient()
const [user, chats, contacts] = await client.connect ()
console.log ("oh hello " + user.name + " (" + user.id + ")")
console.log ("you have " + unread.length + " unread messages")
console.log ("you have " + chats.length + " chats")
})
.catch (err => console.log("unexpected error: " + err) )
// every chat object has a list of most recent messages
// can use that to retreive all your pending unread messages
// the 'count' property a chat object reflects the number of unread messages
// the 'count' property is -1 if the entire thread has been marked unread
const unread = chats.all().flatMap (chat => chat.messages.slice(chat.messages.length-chat.count))
console.log ("you have " + unread.length + " unread messages")
}
// run in main file
connectToWhatsApp ()
.catch (err => console.log("unexpected error: " + err) ) // catch any errors
```
If the connection is successful, you will see a QR code printed on your terminal screen, scan it with WhatsApp on your phone and you'll be logged in!
If you don't want to wait for WhatsApp to send all your chats while connecting, you can use the following function:
``` ts
const client = new WAClient()
client.connectSlim() // does not wait for chats & contacts
.then (user => {
import { WAClient } from '@adiwajshing/baileys'
async function connectToWhatsApp () {
const client = new WAClient()
const user = await client.connectSlim ()
console.log ("oh hello " + user.name + " (" + user.id + ")")
client.receiveChatsAndContacts () // wait for chats & contacts in the background
.then (([chats, contacts, unread]) => {
console.log ("you have " + unread.length + " unread messages")
console.log ("you have " + chats.length + " chats")
.then (([chats, contacts]) => {
console.log ("you have " + chats.all().length + " chats and " + contacts.length + " contacts")
})
})
.catch (err => console.log("unexpected error: " + err))
}
// run in main file
connectToWhatsApp ()
.catch (err => console.log("unexpected error: " + err) ) // catch any errors
```
Do note, the `chats` object returned is now a [KeyedDB](https://github.com/adiwajshing/keyed-db). This is done for the following reasons:
- Most applications require chats to be ordered in descending order of time. (`KeyedDB` does this in `log(N)` time)
- Most applications require pagination of chats (Use `chats.paginated()`)
- Most applications require **O(1)** access to chats via the chat ID. (Use `chats.get(jid)` with `KeyedDB`)
## Saving & Restoring Sessions
You obviously don't want to keep scanning the QR code every time you want to connect.

View File

@@ -30,6 +30,7 @@
"url": "git@github.com:adiwajshing/baileys.git"
},
"dependencies": {
"@adiwajshing/keyed-db": "github:adiwajshing/keyed-db",
"curve25519-js": "0.0.4",
"futoin-hkdf": "^1.3.2",
"jimp": "^0.14.0",

View File

@@ -55,7 +55,7 @@ export enum Mimetype {
png = 'image/png',
mp4 = 'video/mp4',
gif = 'video/gif',
pdf = 'appliction/pdf',
pdf = 'application/pdf',
ogg = 'audio/ogg; codecs=opus',
/** for stickers */
webp = 'image/webp',
@@ -66,7 +66,7 @@ export interface MessageOptions {
timestamp?: Date
caption?: string
thumbnail?: string
mimetype?: Mimetype
mimetype?: Mimetype | string
validateID?: boolean,
filename?: string
}

View File

@@ -1,6 +1,7 @@
import WS from 'ws'
import KeyedDB from '@adiwajshing/keyed-db'
import * as Utils from './Utils'
import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact, MessageLogLevel } from './Constants'
import { AuthenticationCredentialsBase64, UserMetaData, WAMessage, WAChat, WAContact, MessageLogLevel, WANode } from './Constants'
import WAConnectionValidator from './Validation'
import Decoder from '../Binary/Decoder'
@@ -9,13 +10,13 @@ export default class WAConnectionConnector extends WAConnectionValidator {
* Connect to WhatsAppWeb
* @param [authInfo] credentials or path to credentials to log back in
* @param [timeoutMs] timeout after which the connect will fail, set to null for an infinite timeout
* @return returns [userMetaData, chats, contacts, unreadMessages]
* @return returns [userMetaData, chats, contacts]
*/
async connect(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) {
try {
const userInfo = await this.connectSlim(authInfo, timeoutMs)
const chats = await this.receiveChatsAndContacts(timeoutMs)
return [userInfo, ...chats] as [UserMetaData, WAChat[], WAContact[], WAMessage[]]
return [userInfo, ...chats] as [UserMetaData, KeyedDB<WAChat>, WAContact[]]
} catch (error) {
this.close ()
throw error
@@ -66,13 +67,11 @@ export default class WAConnectionConnector extends WAConnectionValidator {
/**
* Sets up callbacks to receive chats, contacts & unread messages.
* Must be called immediately after connect
* @returns [chats, contacts, unreadMessages]
* @returns [chats, contacts]
*/
async receiveChatsAndContacts(timeoutMs: number = null) {
let chats: Array<WAChat> = []
let contacts: Array<WAContact> = []
let unreadMessages: Array<WAMessage> = []
let chatMap: Record<string, {index: number, count: number}> = {}
let contacts: WAContact[] = []
const chats: KeyedDB<WAChat> = new KeyedDB (Utils.waChatUniqueKey, value => value.jid)
let receivedContacts = false
let receivedMessages = false
@@ -91,25 +90,17 @@ export default class WAConnectionConnector extends WAConnectionValidator {
const chatUpdate = json => {
receivedMessages = true
const isLast = json[1].last
json = json[2]
if (json) {
for (let k = json.length - 1; k >= 0; k--) {
const message = json[k][2]
const messages = json[2] as WANode[]
if (messages) {
messages.reverse().forEach (([, __, message]: ['message', null, WAMessage]) => {
const jid = message.key.remoteJid.replace('@s.whatsapp.net', '@c.us')
const index = chatMap[jid]
if (!message.key.fromMe && index && index.count > 0) {
// only forward if the message is from the sender
unreadMessages.push(message)
index.count -= 1 // reduce
}
if (index) {
chats[index.index].messages.push (message)
}
}
}
if (isLast && receivedContacts) { // if received contacts before messages
convoResolve ()
const chat = chats.get(jid)
chat?.messages.unshift (message)
})
}
// if received contacts before messages
if (isLast && receivedContacts) convoResolve ()
}
// wait for actual messages to load, "last" is the most recent message, "before" contains prior messages
this.registerCallback(['action', 'add:last'], chatUpdate)
@@ -120,14 +111,13 @@ export default class WAConnectionConnector extends WAConnectionValidator {
let json = await this.registerCallbackOneTime(['response', 'type:chat'])
if (json[1].duplicate) json = await this.registerCallbackOneTime (['response', 'type:chat'])
json[2].forEach(chat => {
chat[1].count = parseInt(chat[1].count)
chat[1].messages = []
chats.push(chat[1]) // chats data (log json to see what it looks like)
// store the number of unread messages for each sender
chatMap[chat[1].jid] = {index: chats.length-1, count: chat[1].count} //chat[1].count
json[2].forEach(([_, chat]: [any, WAChat]) => {
chat.count = +chat.count
chat.messages = []
chats.insert (chat) // chats data (log json to see what it looks like)
})
if (chats.length > 0) return waitForConvos()
if (chats.all().length > 0) return waitForConvos()
}
const waitForContacts = async () => {
let json = await this.registerCallbackOneTime(['response', 'type:contacts'])
@@ -142,7 +132,8 @@ export default class WAConnectionConnector extends WAConnectionValidator {
// wait for the chats & contacts to load
const promise = Promise.all([waitForChats(), waitForContacts()])
await Utils.promiseTimeout (timeoutMs, promise)
return [chats, contacts, unreadMessages] as [WAChat[], WAContact[], WAMessage[]]
return [chats, contacts] as [KeyedDB<WAChat>, WAContact[]]
}
private onMessageRecieved(message) {
if (message[0] === '!') {

View File

@@ -50,25 +50,25 @@ describe('Test Connect', () => {
})
it('should reconnect', async () => {
const conn = new WAConnection()
const [user, chats, contacts, unread] = await conn.connect(auth, 20*1000)
const [user, chats, contacts] = await conn.connect(auth, 20*1000)
assert.ok(user)
assert.ok(user.id)
assert.ok(chats)
if (chats.length > 0) {
assert.ok(chats[0].jid)
assert.ok(chats[0].count !== null)
const chatArray = chats.all()
if (chatArray.length > 0) {
assert.ok(chatArray[0].jid)
assert.ok(chatArray[0].count !== null)
if (chatArray[0].messages.length > 0) {
assert.ok(chatArray[0].messages[0])
}
}
assert.ok(contacts)
if (contacts.length > 0) {
assert.ok(contacts[0].jid)
}
assert.ok(unread)
if (unread.length > 0) {
assert.ok(unread[0].key)
}
await conn.logout()
await assert.rejects(async () => conn.connectSlim(auth), 'reconnect should have failed')
})

View File

@@ -2,7 +2,7 @@ import * as Crypto from 'crypto'
import HKDF from 'futoin-hkdf'
import Decoder from '../Binary/Decoder'
import {platform, release} from 'os'
import { BaileysError } from './Constants'
import { BaileysError, WAChat } from './Constants'
import UserAgent from 'user-agents'
const platformMap = {
@@ -18,6 +18,13 @@ export const Browsers = {
/** The appropriate browser based on your OS & release */
appropriate: browser => [ platformMap [platform()] || 'Ubuntu', browser, release() ] as [string, string, string]
}
function hashCode(s: string) {
for(var i = 0, h = 0; i < s.length; i++)
h = Math.imul(31, h) + s.charCodeAt(i) | 0;
return h;
}
export const waChatUniqueKey = (c: WAChat) => ((+c.t*1000) + (hashCode(c.jid)%1000))*-1 // -1 to sort descending
export function userAgentString (browser) {
const agent = new UserAgent (new RegExp(browser))
return agent.toString ()

View File

@@ -9,7 +9,8 @@
"noImplicitThis": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true
"declaration": true,
"lib": ["es2019", "esnext.array"]
},
"include": ["src/*/*.ts"],
"exclude": ["node_modules", "src/*/Tests.ts"]