mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Updates
-Removed unread messages while connecting -Added KeyedDB integration -pdf mimetype fix + mimetype can be string now
This commit is contained in:
@@ -17,11 +17,12 @@ async function example() {
|
|||||||
client.logLevel = MessageLogLevel.info // set to unhandled to see what kind of stuff you can implement
|
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)
|
// 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('oh hello ' + user.name + ' (' + user.id + ')')
|
||||||
console.log('you have ' + unread.length + ' unread messages')
|
console.log('you have ' + chats.all().length + ' chats & ' + contacts.length + ' contacts')
|
||||||
console.log('you have ' + chats.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
|
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
|
fs.writeFileSync('./auth_info.json', JSON.stringify(authInfo, null, '\t')) // save this info to a file
|
||||||
|
|||||||
52
README.md
52
README.md
@@ -34,35 +34,53 @@ Set the phone number you can randomly send messages to in a `.env` file with `TE
|
|||||||
|
|
||||||
## Connecting
|
## Connecting
|
||||||
``` ts
|
``` ts
|
||||||
const client = new WAClient()
|
import { WAClient } from '@adiwajshing/baileys'
|
||||||
|
|
||||||
client.connect()
|
async function connectToWhatsApp () {
|
||||||
.then (([user, chats, contacts, unread]) => {
|
const client = new WAClient()
|
||||||
|
const [user, chats, contacts] = await client.connect ()
|
||||||
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
||||||
console.log ("you have " + unread.length + " unread messages")
|
|
||||||
console.log ("you have " + chats.length + " chats")
|
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 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:
|
If you don't want to wait for WhatsApp to send all your chats while connecting, you can use the following function:
|
||||||
|
|
||||||
``` ts
|
``` ts
|
||||||
const client = new WAClient()
|
import { WAClient } from '@adiwajshing/baileys'
|
||||||
client.connectSlim() // does not wait for chats & contacts
|
|
||||||
.then (user => {
|
async function connectToWhatsApp () {
|
||||||
|
const client = new WAClient()
|
||||||
|
const user = await client.connectSlim ()
|
||||||
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
console.log ("oh hello " + user.name + " (" + user.id + ")")
|
||||||
|
|
||||||
client.receiveChatsAndContacts () // wait for chats & contacts in the background
|
client.receiveChatsAndContacts () // wait for chats & contacts in the background
|
||||||
.then (([chats, contacts, unread]) => {
|
.then (([chats, contacts]) => {
|
||||||
console.log ("you have " + unread.length + " unread messages")
|
console.log ("you have " + chats.all().length + " chats and " + contacts.length + " contacts")
|
||||||
console.log ("you have " + chats.length + " chats")
|
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
.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
|
## Saving & Restoring Sessions
|
||||||
|
|
||||||
You obviously don't want to keep scanning the QR code every time you want to connect.
|
You obviously don't want to keep scanning the QR code every time you want to connect.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"url": "git@github.com:adiwajshing/baileys.git"
|
"url": "git@github.com:adiwajshing/baileys.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@adiwajshing/keyed-db": "github:adiwajshing/keyed-db",
|
||||||
"curve25519-js": "0.0.4",
|
"curve25519-js": "0.0.4",
|
||||||
"futoin-hkdf": "^1.3.2",
|
"futoin-hkdf": "^1.3.2",
|
||||||
"jimp": "^0.14.0",
|
"jimp": "^0.14.0",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export enum Mimetype {
|
|||||||
png = 'image/png',
|
png = 'image/png',
|
||||||
mp4 = 'video/mp4',
|
mp4 = 'video/mp4',
|
||||||
gif = 'video/gif',
|
gif = 'video/gif',
|
||||||
pdf = 'appliction/pdf',
|
pdf = 'application/pdf',
|
||||||
ogg = 'audio/ogg; codecs=opus',
|
ogg = 'audio/ogg; codecs=opus',
|
||||||
/** for stickers */
|
/** for stickers */
|
||||||
webp = 'image/webp',
|
webp = 'image/webp',
|
||||||
@@ -66,7 +66,7 @@ export interface MessageOptions {
|
|||||||
timestamp?: Date
|
timestamp?: Date
|
||||||
caption?: string
|
caption?: string
|
||||||
thumbnail?: string
|
thumbnail?: string
|
||||||
mimetype?: Mimetype
|
mimetype?: Mimetype | string
|
||||||
validateID?: boolean,
|
validateID?: boolean,
|
||||||
filename?: string
|
filename?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import WS from 'ws'
|
import WS from 'ws'
|
||||||
|
import KeyedDB from '@adiwajshing/keyed-db'
|
||||||
import * as Utils from './Utils'
|
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 WAConnectionValidator from './Validation'
|
||||||
import Decoder from '../Binary/Decoder'
|
import Decoder from '../Binary/Decoder'
|
||||||
|
|
||||||
@@ -9,13 +10,13 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
* Connect to WhatsAppWeb
|
* Connect to WhatsAppWeb
|
||||||
* @param [authInfo] credentials or path to credentials to log back in
|
* @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
|
* @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) {
|
async connect(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) {
|
||||||
try {
|
try {
|
||||||
const userInfo = await this.connectSlim(authInfo, timeoutMs)
|
const userInfo = await this.connectSlim(authInfo, timeoutMs)
|
||||||
const chats = await this.receiveChatsAndContacts(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) {
|
} catch (error) {
|
||||||
this.close ()
|
this.close ()
|
||||||
throw error
|
throw error
|
||||||
@@ -66,13 +67,11 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
/**
|
/**
|
||||||
* Sets up callbacks to receive chats, contacts & unread messages.
|
* Sets up callbacks to receive chats, contacts & unread messages.
|
||||||
* Must be called immediately after connect
|
* Must be called immediately after connect
|
||||||
* @returns [chats, contacts, unreadMessages]
|
* @returns [chats, contacts]
|
||||||
*/
|
*/
|
||||||
async receiveChatsAndContacts(timeoutMs: number = null) {
|
async receiveChatsAndContacts(timeoutMs: number = null) {
|
||||||
let chats: Array<WAChat> = []
|
let contacts: WAContact[] = []
|
||||||
let contacts: Array<WAContact> = []
|
const chats: KeyedDB<WAChat> = new KeyedDB (Utils.waChatUniqueKey, value => value.jid)
|
||||||
let unreadMessages: Array<WAMessage> = []
|
|
||||||
let chatMap: Record<string, {index: number, count: number}> = {}
|
|
||||||
|
|
||||||
let receivedContacts = false
|
let receivedContacts = false
|
||||||
let receivedMessages = false
|
let receivedMessages = false
|
||||||
@@ -91,25 +90,17 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
const chatUpdate = json => {
|
const chatUpdate = json => {
|
||||||
receivedMessages = true
|
receivedMessages = true
|
||||||
const isLast = json[1].last
|
const isLast = json[1].last
|
||||||
json = json[2]
|
const messages = json[2] as WANode[]
|
||||||
if (json) {
|
|
||||||
for (let k = json.length - 1; k >= 0; k--) {
|
if (messages) {
|
||||||
const message = json[k][2]
|
messages.reverse().forEach (([, __, message]: ['message', null, WAMessage]) => {
|
||||||
const jid = message.key.remoteJid.replace('@s.whatsapp.net', '@c.us')
|
const jid = message.key.remoteJid.replace('@s.whatsapp.net', '@c.us')
|
||||||
const index = chatMap[jid]
|
const chat = chats.get(jid)
|
||||||
if (!message.key.fromMe && index && index.count > 0) {
|
chat?.messages.unshift (message)
|
||||||
// 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 ()
|
|
||||||
}
|
}
|
||||||
|
// 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
|
// wait for actual messages to load, "last" is the most recent message, "before" contains prior messages
|
||||||
this.registerCallback(['action', 'add:last'], chatUpdate)
|
this.registerCallback(['action', 'add:last'], chatUpdate)
|
||||||
@@ -120,14 +111,13 @@ export default class WAConnectionConnector extends WAConnectionValidator {
|
|||||||
let json = await this.registerCallbackOneTime(['response', 'type:chat'])
|
let json = await this.registerCallbackOneTime(['response', 'type:chat'])
|
||||||
if (json[1].duplicate) json = await this.registerCallbackOneTime (['response', 'type:chat'])
|
if (json[1].duplicate) json = await this.registerCallbackOneTime (['response', 'type:chat'])
|
||||||
|
|
||||||
json[2].forEach(chat => {
|
json[2].forEach(([_, chat]: [any, WAChat]) => {
|
||||||
chat[1].count = parseInt(chat[1].count)
|
chat.count = +chat.count
|
||||||
chat[1].messages = []
|
chat.messages = []
|
||||||
chats.push(chat[1]) // chats data (log json to see what it looks like)
|
chats.insert (chat) // 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
|
|
||||||
})
|
})
|
||||||
if (chats.length > 0) return waitForConvos()
|
|
||||||
|
if (chats.all().length > 0) return waitForConvos()
|
||||||
}
|
}
|
||||||
const waitForContacts = async () => {
|
const waitForContacts = async () => {
|
||||||
let json = await this.registerCallbackOneTime(['response', 'type:contacts'])
|
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
|
// wait for the chats & contacts to load
|
||||||
const promise = Promise.all([waitForChats(), waitForContacts()])
|
const promise = Promise.all([waitForChats(), waitForContacts()])
|
||||||
await Utils.promiseTimeout (timeoutMs, promise)
|
await Utils.promiseTimeout (timeoutMs, promise)
|
||||||
return [chats, contacts, unreadMessages] as [WAChat[], WAContact[], WAMessage[]]
|
|
||||||
|
return [chats, contacts] as [KeyedDB<WAChat>, WAContact[]]
|
||||||
}
|
}
|
||||||
private onMessageRecieved(message) {
|
private onMessageRecieved(message) {
|
||||||
if (message[0] === '!') {
|
if (message[0] === '!') {
|
||||||
|
|||||||
@@ -50,25 +50,25 @@ describe('Test Connect', () => {
|
|||||||
})
|
})
|
||||||
it('should reconnect', async () => {
|
it('should reconnect', async () => {
|
||||||
const conn = new WAConnection()
|
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)
|
||||||
assert.ok(user.id)
|
assert.ok(user.id)
|
||||||
|
|
||||||
assert.ok(chats)
|
assert.ok(chats)
|
||||||
if (chats.length > 0) {
|
|
||||||
assert.ok(chats[0].jid)
|
const chatArray = chats.all()
|
||||||
assert.ok(chats[0].count !== null)
|
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)
|
assert.ok(contacts)
|
||||||
if (contacts.length > 0) {
|
if (contacts.length > 0) {
|
||||||
assert.ok(contacts[0].jid)
|
assert.ok(contacts[0].jid)
|
||||||
}
|
}
|
||||||
assert.ok(unread)
|
|
||||||
if (unread.length > 0) {
|
|
||||||
assert.ok(unread[0].key)
|
|
||||||
}
|
|
||||||
|
|
||||||
await conn.logout()
|
await conn.logout()
|
||||||
await assert.rejects(async () => conn.connectSlim(auth), 'reconnect should have failed')
|
await assert.rejects(async () => conn.connectSlim(auth), 'reconnect should have failed')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as Crypto from 'crypto'
|
|||||||
import HKDF from 'futoin-hkdf'
|
import HKDF from 'futoin-hkdf'
|
||||||
import Decoder from '../Binary/Decoder'
|
import Decoder from '../Binary/Decoder'
|
||||||
import {platform, release} from 'os'
|
import {platform, release} from 'os'
|
||||||
import { BaileysError } from './Constants'
|
import { BaileysError, WAChat } from './Constants'
|
||||||
import UserAgent from 'user-agents'
|
import UserAgent from 'user-agents'
|
||||||
|
|
||||||
const platformMap = {
|
const platformMap = {
|
||||||
@@ -18,6 +18,13 @@ export const Browsers = {
|
|||||||
/** The appropriate browser based on your OS & release */
|
/** The appropriate browser based on your OS & release */
|
||||||
appropriate: browser => [ platformMap [platform()] || 'Ubuntu', browser, release() ] as [string, string, string]
|
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) {
|
export function userAgentString (browser) {
|
||||||
const agent = new UserAgent (new RegExp(browser))
|
const agent = new UserAgent (new RegExp(browser))
|
||||||
return agent.toString ()
|
return agent.toString ()
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"declaration": true
|
"declaration": true,
|
||||||
|
"lib": ["es2019", "esnext.array"]
|
||||||
},
|
},
|
||||||
"include": ["src/*/*.ts"],
|
"include": ["src/*/*.ts"],
|
||||||
"exclude": ["node_modules", "src/*/Tests.ts"]
|
"exclude": ["node_modules", "src/*/Tests.ts"]
|
||||||
|
|||||||
Reference in New Issue
Block a user