mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
V2.1
Added features: -search messages -delete messages -archive chats -mark chats as unread Fixes -decoding audio/video messages
This commit is contained in:
42
README.md
42
README.md
@@ -174,7 +174,13 @@ To note:
|
||||
``` ts
|
||||
client.sendReadReceipt(id, messageID)
|
||||
```
|
||||
The id is in the same format as mentioned earlier. The message ID is the unique identifier of the message that you are marking as read. On a `WAMessage`, it can be accessed using ```messageID = message.key.id```.
|
||||
|
||||
Also, to mark a chat unread:
|
||||
``` ts
|
||||
client.markChatUnread(id)
|
||||
```
|
||||
|
||||
`id` is in the same format as mentioned earlier. The message ID is the unique identifier of the message that you are marking as read. On a `WAMessage`, it can be accessed using ```messageID = message.key.id```.
|
||||
|
||||
## Update Presence
|
||||
``` ts
|
||||
@@ -206,6 +212,19 @@ If you want to save & process some images, videos, documents or stickers you rec
|
||||
}
|
||||
```
|
||||
|
||||
## Deleting Messages
|
||||
|
||||
``` ts
|
||||
const jid = '1234@s.whatsapp.net' // can also be a group
|
||||
const response = await client.sendMessage (jid, 'hello!', MessageType.text) // send a message
|
||||
await client.deleteMessage (jid, {id: response.messageID, remoteJid: jid, fromMe: true}) // will delete the sent message!
|
||||
```
|
||||
|
||||
You can also archive a chat using:
|
||||
``` ts
|
||||
await client.archiveChat(jid)
|
||||
```
|
||||
|
||||
## Querying
|
||||
- To check if a given ID is on WhatsApp
|
||||
``` ts
|
||||
@@ -238,12 +257,15 @@ If you want to save & process some images, videos, documents or stickers you rec
|
||||
``` ts
|
||||
// the presence update is fetched and called here
|
||||
client.setOnPresenceUpdate (json => console.log(json.id + " presence is " + json.type))
|
||||
|
||||
await client.requestPresenceUpdate ("xyz@c.us")
|
||||
await client.requestPresenceUpdate ("xyz@c.us") // request the update
|
||||
```
|
||||
- To search through messages
|
||||
``` ts
|
||||
const response = await client.searchMessages ('so cool', 25, 0) // get 25 messages of the first page of results
|
||||
console.log (`got ${response.messages.length} messages in search`)
|
||||
```
|
||||
|
||||
Of course, replace ``` xyz ``` with an actual ID.
|
||||
Also, append ``` @s.whatsapp.net ``` for individuals & ``` @g.us ``` for groups.
|
||||
Append ``` @s.whatsapp.net ``` for individuals & ``` @g.us ``` for groups.
|
||||
|
||||
## Groups
|
||||
- To query the metadata of a group
|
||||
@@ -260,19 +282,17 @@ Also, append ``` @s.whatsapp.net ``` for individuals & ``` @g.us ``` for groups.
|
||||
```
|
||||
- To add people to a group
|
||||
``` ts
|
||||
// id & people to add to the group
|
||||
// id & people to add to the group (will throw error if it fails)
|
||||
const response = await client.groupAdd ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
||||
console.log("added successfully: " + (response.status===200))
|
||||
```
|
||||
- To make someone admin on a group
|
||||
``` ts
|
||||
const response = await client.groupMakeAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"]) // id & people to make admin
|
||||
console.log("made admin successfully: " + (response.status===200))
|
||||
// id & people to make admin (will throw error if it fails)
|
||||
await client.groupMakeAdmin ("abcd-xyz@g.us", ["abcd@s.whatsapp.net", "efgh@s.whatsapp.net"])
|
||||
```
|
||||
- To leave a group
|
||||
``` ts
|
||||
const response = await client.groupLeave ("abcd-xyz@g.us")
|
||||
console.log("left group successfully: " + (response.status===200))
|
||||
await client.groupLeave ("abcd-xyz@g.us") // (will throw error if it fails)
|
||||
```
|
||||
- To get the invite code for a group
|
||||
``` ts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@adiwajshing/baileys",
|
||||
"version": "2.0.1",
|
||||
"version": "2.1.0",
|
||||
"description": "WhatsApp Web API",
|
||||
"homepage": "https://github.com/adiwajshing/Baileys",
|
||||
"main": "lib/WAClient/WAClient.js",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import WAConnection from '../WAConnection/WAConnection'
|
||||
import { MessageStatus, MessageStatusUpdate, PresenceUpdate } from './Constants'
|
||||
import { MessageStatus, MessageStatusUpdate, PresenceUpdate, Presence } from './Constants'
|
||||
import {
|
||||
WAMessage,
|
||||
WANode,
|
||||
@@ -63,11 +63,25 @@ export default class WhatsAppWebBase extends WAConnection {
|
||||
}
|
||||
/** Query whether a given number is registered on WhatsApp */
|
||||
isOnWhatsApp = (jid: string) => this.query(['query', 'exist', jid]).then((m) => m.status === 200)
|
||||
/**
|
||||
* Tell someone about your presence -- online, typing, offline etc.
|
||||
* @param jid the ID of the person/group who you are updating
|
||||
* @param type your presence
|
||||
*/
|
||||
async updatePresence(jid: string, type: Presence) {
|
||||
const json = [
|
||||
'action',
|
||||
{ epoch: this.msgCount.toString(), type: 'set' },
|
||||
[['presence', { type: type, to: jid }, null]],
|
||||
]
|
||||
return this.queryExpecting200(json, [WAMetric.group, WAFlag.acknowledge]) as Promise<{ status: number }>
|
||||
}
|
||||
/** Request an update on the presence of a user */
|
||||
requestPresenceUpdate = (jid: string) => this.queryExpecting200(['action', 'presence', 'subscribe', jid])
|
||||
requestPresenceUpdate = async (jid: string) => this.queryExpecting200(['action', 'presence', 'subscribe', jid])
|
||||
/** Query the status of the person (see groupMetadata() for groups) */
|
||||
getStatus = (jid: string | null) =>
|
||||
this.query(['query', 'Status', jid || this.userMetaData.id]) as Promise<{ status: string }>
|
||||
async getStatus (jid?: string) {
|
||||
return this.query(['query', 'Status', jid || this.userMetaData.id]) as Promise<{ status: string }>
|
||||
}
|
||||
/** Get the URL to download the profile picture of a person/group */
|
||||
async getProfilePicture(jid: string | null) {
|
||||
const response = await this.queryExpecting200(['query', 'ProfilePicThumb', jid || this.userMetaData.id])
|
||||
@@ -85,6 +99,18 @@ export default class WhatsAppWebBase extends WAConnection {
|
||||
const json = ['query', { epoch: this.msgCount.toString(), type: 'chat' }, null]
|
||||
return this.query(json, [WAMetric.group, WAFlag.ignore]) // this has to be an encrypted query
|
||||
}
|
||||
/**
|
||||
* Archive a given chat
|
||||
* @param jid the ID of the person/group you are archiving
|
||||
*/
|
||||
async archiveChat(jid: string) {
|
||||
const json = [
|
||||
'action',
|
||||
{ epoch: this.msgCount.toString(), type: 'set' },
|
||||
[['chat', { type: 'archive', jid: jid }, null]],
|
||||
]
|
||||
return this.queryExpecting200(json, [WAMetric.group, WAFlag.acknowledge]) as Promise<{ status: number }>
|
||||
}
|
||||
/**
|
||||
* Check if your phone is connected
|
||||
* @param timeoutMs max time for the phone to respond
|
||||
|
||||
@@ -40,15 +40,13 @@ export const WAMessageType = function () {
|
||||
Object.keys(types).forEach(element => dict[ types[element] ] = element)
|
||||
return dict
|
||||
}()
|
||||
export const HKDFInfoKeys = (function () {
|
||||
|
||||
const dict: Record<string, string> = {}
|
||||
dict[MessageType.image] = 'WhatsApp Image Keys'
|
||||
dict[MessageType.video] = 'WhatsApp Audio Keys'
|
||||
dict[MessageType.document] = 'WhatsApp Document Keys'
|
||||
dict[MessageType.sticker] = 'WhatsApp Image Keys'
|
||||
return dict
|
||||
})()
|
||||
export const HKDFInfoKeys = {
|
||||
[MessageType.image]: 'WhatsApp Image Keys',
|
||||
[MessageType.audio]: 'WhatsApp Audio Keys',
|
||||
[MessageType.video]: 'WhatsApp Video Keys',
|
||||
[MessageType.document]: 'WhatsApp Document Keys',
|
||||
[MessageType.sticker]: 'WhatsApp Image Keys'
|
||||
}
|
||||
export enum Mimetype {
|
||||
jpeg = 'image/jpeg',
|
||||
mp4 = 'video/mp4',
|
||||
@@ -108,3 +106,4 @@ export interface WALocationMessage {
|
||||
address?: string
|
||||
}
|
||||
export type WAContactMessage = proto.ContactMessage
|
||||
export type WAMessageKey = proto.IMessageKey
|
||||
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
WALocationMessage,
|
||||
WAContactMessage,
|
||||
WASendMessageResponse,
|
||||
Presence,
|
||||
WAMessageKey,
|
||||
} from './Constants'
|
||||
import { generateMessageID, sha256, hmacSign, aesEncrypWithIV, randomBytes } from '../WAConnection/Utils'
|
||||
import { WAMessageContent, WAMetric, WAFlag } from '../WAConnection/Constants'
|
||||
import { WAMessageContent, WAMetric, WAFlag, WANode, WAMessage } from '../WAConnection/Constants'
|
||||
import { validateJIDForSending, generateThumbnail, getMediaKeys } from './Utils'
|
||||
import { proto } from '../../WAMessage/WAMessage'
|
||||
|
||||
export default class WhatsAppWebMessages extends WhatsAppWebBase {
|
||||
/**
|
||||
@@ -30,17 +31,52 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase {
|
||||
return this.queryExpecting200(json, [WAMetric.group, WAFlag.ignore]) // encrypt and send off
|
||||
}
|
||||
/**
|
||||
* Tell someone about your presence -- online, typing, offline etc.
|
||||
* @param jid the ID of the person/group who you are updating
|
||||
* @param type your presence
|
||||
* Search WhatsApp messages with a given text string
|
||||
* @param txt the search string
|
||||
* @param count number of results to return
|
||||
* @param page page number of results
|
||||
*/
|
||||
async updatePresence(jid: string, type: Presence) {
|
||||
async searchMessages(txt: string, count: number, page: number) {
|
||||
const json = [
|
||||
'query',
|
||||
{
|
||||
epoch: this.msgCount.toString(),
|
||||
type: 'search',
|
||||
search: txt,
|
||||
count: count.toString(),
|
||||
page: page.toString()
|
||||
},
|
||||
null,
|
||||
]
|
||||
const response: WANode = await this.queryExpecting200(json, [WAMetric.group, WAFlag.ignore]) // encrypt and send off
|
||||
const messages = response[2] ? response[2].map (row => row[2]) : []
|
||||
return { last: response[1]['last'] === 'true', messages: messages as WAMessage[] }
|
||||
}
|
||||
/**
|
||||
* Mark a given chat as unread
|
||||
* @param jid
|
||||
*/
|
||||
async markChatUnread (jid: string) {
|
||||
const json = [
|
||||
'action',
|
||||
{ epoch: this.msgCount.toString(), type: 'set' },
|
||||
[['presence', { type: type, to: jid }, null]],
|
||||
[['read', {jid: jid, type: 'false', count: '1'}, null]]
|
||||
]
|
||||
return this.queryExpecting200(json, [WAMetric.group, WAFlag.acknowledge]) as Promise<{ status: number }>
|
||||
return this.queryExpecting200(json, [WAMetric.group, WAFlag.ignore]) as Promise<{ status: number }>
|
||||
}
|
||||
/**
|
||||
* Delete a message in a chat
|
||||
* @param id the person or group where you're trying to delete the message
|
||||
* @param messageKey key of the message you want to delete
|
||||
*/
|
||||
async deleteMessage (id: string, messageKey: WAMessageKey) {
|
||||
const json: WAMessageContent = {
|
||||
protocolMessage: {
|
||||
key: messageKey,
|
||||
type: proto.ProtocolMessage.PROTOCOL_MESSAGE_TYPE.REVOKE
|
||||
}
|
||||
}
|
||||
return this.sendGenericMessage (id, json, {})
|
||||
}
|
||||
async sendMessage(
|
||||
id: string,
|
||||
@@ -168,7 +204,7 @@ export default class WhatsAppWebMessages extends WhatsAppWebBase {
|
||||
messageTimestamp: timestamp,
|
||||
participant: id.includes('@g.us') ? this.userMetaData.id : null,
|
||||
}
|
||||
const json = ['action', { epoch: this.msgCount.toString(), type: 'relay' }, [['message', null, messageJSON]]]
|
||||
const json = ['action', {epoch: this.msgCount.toString(), type: 'relay'}, [['message', null, messageJSON]]]
|
||||
const response = await this.queryExpecting200(json, [WAMetric.message, WAFlag.ignore], null, messageJSON.key.id)
|
||||
return { status: response.status as number, messageID: messageJSON.key.id } as WASendMessageResponse
|
||||
}
|
||||
|
||||
@@ -60,7 +60,14 @@ WAClientTest('Messages', (client) => {
|
||||
const file = await decodeMediaMessage(message.message, './Media/received_img')
|
||||
assert.strictEqual(message.message.imageMessage.contextInfo.stanzaId, messages[0].key.id)
|
||||
})
|
||||
it('should send a text message & delete it', async () => {
|
||||
const message = await sendAndRetreiveMessage(client, 'hello fren', MessageType.text)
|
||||
assert.strictEqual(message.message.conversation, 'hello fren')
|
||||
await createTimeout (2000)
|
||||
await client.deleteMessage (testJid, message.key)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Validate WhatsApp IDs', () => {
|
||||
it ('should correctly validate', () => {
|
||||
assert.doesNotThrow (() => validateJIDForSending ('12345@s.whatsapp.net'))
|
||||
@@ -102,12 +109,20 @@ WAClientTest('Misc', (client) => {
|
||||
assert.ok(response)
|
||||
assert.rejects(client.getProfilePicture('abcd@s.whatsapp.net'))
|
||||
})
|
||||
it('should mark a chat unread', async () => {
|
||||
const response = await client.markChatUnread(testJid)
|
||||
assert.ok(response)
|
||||
})
|
||||
it('should return search results', async () => {
|
||||
const response = await client.searchMessages('Adh', 25, 0)
|
||||
assert.ok (response.messages)
|
||||
assert.ok (response.messages.length >= 0)
|
||||
})
|
||||
})
|
||||
WAClientTest('Groups', (client) => {
|
||||
let gid: string
|
||||
it('should create a group', async () => {
|
||||
const response = await client.groupCreate('Cool Test Group', [testJid])
|
||||
assert.strictEqual(response.status, 200)
|
||||
gid = response.gid
|
||||
console.log('created group: ' + gid)
|
||||
})
|
||||
@@ -122,13 +137,11 @@ WAClientTest('Groups', (client) => {
|
||||
assert.strictEqual(metadata.participants.filter((obj) => obj.id.split('@')[0] === testJid.split('@')[0]).length, 1)
|
||||
})
|
||||
it('should send a message on the group', async () => {
|
||||
const r = await client.sendMessage(gid, 'hello', MessageType.text)
|
||||
assert.strictEqual(r.status, 200)
|
||||
await client.sendMessage(gid, 'hello', MessageType.text)
|
||||
})
|
||||
it('should update the subject', async () => {
|
||||
const subject = 'V Cool Title'
|
||||
const r = await client.groupUpdateSubject(gid, subject)
|
||||
assert.strictEqual(r.status, 200)
|
||||
await client.groupUpdateSubject(gid, subject)
|
||||
|
||||
const metadata = await client.groupMetadata(gid)
|
||||
assert.strictEqual(metadata.subject, subject)
|
||||
@@ -137,8 +150,10 @@ WAClientTest('Groups', (client) => {
|
||||
await client.groupRemove(gid, [testJid])
|
||||
})
|
||||
it('should leave the group', async () => {
|
||||
const response = await client.groupLeave(gid)
|
||||
assert.strictEqual(response.status, 200)
|
||||
await client.groupLeave(gid)
|
||||
})
|
||||
it('should archive the group', async () => {
|
||||
await client.archiveChat(gid)
|
||||
})
|
||||
})
|
||||
WAClientTest('Events', (client) => {
|
||||
|
||||
@@ -20,9 +20,9 @@ export function validateJIDForSending (jid: string) {
|
||||
}
|
||||
|
||||
/** Type of notification */
|
||||
export function getNotificationType(message: WAMessage): [string, string] {
|
||||
export function getNotificationType(message: WAMessage): [string, MessageType?] {
|
||||
if (message.message) {
|
||||
return ['message', Object.keys(message.message)[0]]
|
||||
return ['message', Object.keys(message.message)[0] as MessageType]
|
||||
} else if (message.messageStubType) {
|
||||
return [WAMessageType[message.messageStubType], null]
|
||||
} else {
|
||||
@@ -87,8 +87,9 @@ export async function generateThumbnail(buffer: Buffer, mediaType: MessageType,
|
||||
* Decode a media message (video, image, document, audio) & save it to the given file
|
||||
* @param message the media message you want to decode
|
||||
* @param filename the name of the file where the media will be saved
|
||||
* @param attachExtension should the correct extension be applied automatically to the file
|
||||
*/
|
||||
export async function decodeMediaMessage(message: WAMessageContent, filename: string) {
|
||||
export async function decodeMediaMessage(message: WAMessageContent, filename: string, attachExtension: boolean=true) {
|
||||
const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1]
|
||||
/*
|
||||
One can infer media type from the key in the message
|
||||
@@ -132,7 +133,7 @@ export async function decodeMediaMessage(message: WAMessageContent, filename: st
|
||||
if (sign.equals(mac)) {
|
||||
const decrypted = aesDecryptWithIV(file, cipherKey, iv) // decrypt media
|
||||
|
||||
const trueFileName = filename + '.' + getExtension(messageContent.mimetype)
|
||||
const trueFileName = attachExtension ? (filename + '.' + getExtension(messageContent.mimetype)) : filename
|
||||
fs.writeFileSync(trueFileName, decrypted)
|
||||
|
||||
return trueFileName
|
||||
|
||||
Reference in New Issue
Block a user