Manage group metdata state in Baileys + Remove presence from Contact

This commit is contained in:
Adhiraj Singh
2020-12-03 13:16:37 +05:30
parent d4b453f0e5
commit 6d7dc4d9fe
7 changed files with 136 additions and 66 deletions

View File

@@ -89,7 +89,7 @@ export class WAConnection extends Base {
this.conn.on('message', data => this.onMessageRecieved(data as any))
this.conn.on ('open', async () => {
this.conn.once('open', async () => {
this.startKeepAliveRequest()
this.logger.info(`connected to WhatsApp Web server, authenticating via ${reconnectID ? 'reconnect' : 'takeover'}`)
@@ -109,8 +109,8 @@ export class WAConnection extends Base {
)
this.conn
.removeAllListeners ('error')
.removeAllListeners ('close')
.removeAllListeners('error')
.removeAllListeners('close')
this.stopDebouncedTimeout ()
resolve ({ ...authResult, ...chatsResult })
} catch (error) {

View File

@@ -48,7 +48,9 @@ export class WAConnection extends Base {
chat.messages = oldChat.messages
if (oldChat.t !== chat.t || oldChat.modify_tag !== chat.modify_tag) {
const changes = shallowChanges (oldChat, chat)
delete chat.metadata // remove group metadata as that may have changed; TODO, write better mechanism for this
delete changes.messages
updatedChats.push({ ...changes, jid: chat.jid })
}
}
@@ -138,22 +140,15 @@ export class WAConnection extends Base {
// new messages
this.on('CB:action,add:relay,message', json => {
const message = json[2][0][2] as WAMessage
const jid = whatsappID( message.key.remoteJid )
if (jid.endsWith('@s.whatsapp.net')) {
const contact = this.contacts[jid]
if (contact && contact?.lastKnownPresence === Presence.composing) {
contact.lastKnownPresence = Presence.available
}
}
this.chatAddMessageAppropriate (message)
})
this.on('CB:Chat,cmd:action', json => {
const data = json[1].data
if (data) {
const emitGroupParticipantsUpdate = (action: WAParticipantAction) => this.emit(
'group-participants-update',
{ participants: data[2].participants.map(whatsappID), actor: data[1], jid: json[1].id, action }
)
const emitGroupParticipantsUpdate = (action: WAParticipantAction) => this.emitParticipantsUpdate
(json[1].id, data[2].participants.map(whatsappID), action)
const emitGroupUpdate = (data: Partial<WAGroupMetadata>) => this.emitGroupUpdate(json[1].id, data)
switch (data[0]) {
case "promote":
emitGroupParticipantsUpdate('promote')
@@ -161,6 +156,12 @@ export class WAConnection extends Base {
case "demote":
emitGroupParticipantsUpdate('demote')
break
case "desc_add":
emitGroupUpdate({ ...data[2], descOwner: data[1] })
break
default:
this.logger.debug({ unhandled: true }, json)
break
}
}
})
@@ -324,26 +325,24 @@ export class WAConnection extends Base {
// emit deprecated
this.emit('user-presence-update', update)
const contact = this.contacts[jid]
if (contact && jid.endsWith('@s.whatsapp.net')) { // if its a single chat
if (update.t) contact.lastSeen = +update.t
else if (update.type === Presence.unavailable && contact.lastKnownPresence !== Presence.unavailable) {
contact.lastSeen = unixTimestampSeconds()
}
contact.lastKnownPresence = update.type
const presence: WAPresenceData = {
lastKnownPresence: contact.lastKnownPresence,
lastSeen: contact.lastSeen,
name: contact.name || contact.vname || contact.notify
const chat = this.chats.get(chatId)
if (chat && jid.endsWith('@s.whatsapp.net')) { // if its a single chat
chat.presences = chat.presences || {}
const presence = { ...(chat.presences[jid] || {}) } as WAPresenceData
if (update.t) presence.lastSeen = +update.t
else if (update.type === Presence.unavailable && presence.lastKnownPresence !== Presence.unavailable) {
presence.lastSeen = unixTimestampSeconds()
}
presence.lastKnownPresence = update.type
const chat = this.chats.get(chatId)
if (chat) {
chat.presences = chat.presences || {}
chat.presences[jid] = presence
return { jid: chatId, presences: { [jid]: presence } } as Partial<WAChat>
const contact = this.contacts[jid]
if (contact) {
presence.name = contact.name || contact.notify || contact.vname
}
chat.presences[jid] = presence
return { jid: chatId, presences: { [jid]: presence } } as Partial<WAChat>
}
}
protected forwardStatusUpdate (update: WAMessageStatusUpdate) {
@@ -366,12 +365,13 @@ export class WAConnection extends Base {
spam: 'false',
name
}
this.chats.insert (chat)
if (this.loadProfilePicturesForChatsAutomatically) {
await this.setProfilePicture (chat)
}
this.emit ('chat-new', chat)
return chat
}
/** find a chat or return an error */
@@ -395,9 +395,10 @@ export class WAConnection extends Base {
chat.count += 1
chatUpdate.count = chat.count
const contact = this.contacts[message.participant || chat.jid]
const participant = whatsappID(message.participant || chat.jid)
const contact = chat.presences && chat.presences[participant]
if (contact?.lastKnownPresence === Presence.composing) { // update presence
const update = this.applyingPresenceUpdate({ id: chat.jid, participant: message.participant || chat.jid, type: Presence.available })
const update = this.applyingPresenceUpdate({ id: chat.jid, participant, type: Presence.available })
update && Object.assign(chatUpdate, update)
}
}
@@ -446,10 +447,12 @@ export class WAConnection extends Base {
// check if the message is an action
if (message.messageStubType) {
const jid = chat.jid
let actor = whatsappID (message.participant)
//let actor = whatsappID (message.participant)
let participants: string[]
const emitParticipantsUpdate = (action: WAParticipantAction) => this.emit ('group-participants-update', { jid, actor, participants, action })
const emitGroupUpdate = (update: Partial<WAGroupMetadata>) => this.emit ('group-update', { jid, actor, ...update })
const emitParticipantsUpdate = (action: WAParticipantAction) => (
this.emitParticipantsUpdate(jid, participants, action)
)
const emitGroupUpdate = (update: Partial<WAGroupMetadata>) => this.emitGroupUpdate(jid, update)
switch (message.messageStubType) {
case WA_MESSAGE_STUB_TYPE.GROUP_PARTICIPANT_LEAVE:
@@ -480,14 +483,11 @@ export class WAConnection extends Base {
const restrict = message.messageStubParameters[0] === 'on' ? 'true' : 'false'
emitGroupUpdate({ restrict })
break
case WA_MESSAGE_STUB_TYPE.GROUP_CHANGE_DESCRIPTION:
const desc = message.messageStubParameters[0]
emitGroupUpdate({ desc })
break
case WA_MESSAGE_STUB_TYPE.GROUP_CHANGE_SUBJECT:
case WA_MESSAGE_STUB_TYPE.GROUP_CREATE:
chat.name = message.messageStubParameters[0]
chatUpdate.name = chat.name
if (chat.metadata) chat.metadata.subject = chat.name
break
}
}
@@ -495,6 +495,35 @@ export class WAConnection extends Base {
this.emit('chat-update', chatUpdate)
}
protected emitParticipantsUpdate = (jid: string, participants: string[], action: WAParticipantAction) => {
const chat = this.chats.get(jid)
const meta = chat?.metadata
if (meta) {
switch (action) {
case 'add':
participants.forEach(id => (
meta.participants.push({ id, isAdmin: false, isSuperAdmin: false })
))
break
case 'remove':
meta.participants = meta.participants.filter(p => !participants.includes(whatsappID(p.id)))
break
case 'promote':
case 'demote':
const isAdmin = action==='promote'
meta.participants.forEach(p => {
if (participants.includes(whatsappID(p.id))) p.isAdmin = isAdmin
})
break
}
}
this.emit ('group-participants-update', { jid, participants, action })
}
protected emitGroupUpdate = (jid: string, update: Partial<WAGroupMetadata>) => {
const chat = this.chats.get(jid)
if (chat.metadata) Object.assign(chat.metadata, update)
this.emit ('group-update', { jid, ...update })
}
protected chatUpdatedMessage (messageIDs: string[], status: WA_MESSAGE_STATUS_TYPE, chat: WAChat) {
for (let id of messageIDs) {
let msg = chat.messages.get (GET_MESSAGE_ID({ id, fromMe: true })) || chat.messages.get (GET_MESSAGE_ID({ id, fromMe: false }))

View File

@@ -1,7 +1,8 @@
import {WAConnection as Base} from './7.MessagesExtra'
import { WAMetric, WAFlag, WANode, WAGroupMetadata, WAGroupCreateResponse, WAGroupModification } from '../WAConnection/Constants'
import { WAMetric, WAFlag, WANode, WAGroupMetadata, WAGroupCreateResponse, WAGroupModification, BaileysError } from '../WAConnection/Constants'
import { GroupSettingChange } from './Constants'
import { generateMessageID } from '../WAConnection/Utils'
import { Mutex } from './Mutex'
export class WAConnection extends Base {
/** Generic function for group queries */
@@ -21,8 +22,26 @@ export class WAConnection extends Base {
const result = await this.setQuery ([json], [WAMetric.group, 136], tag)
return result
}
/** Get the metadata of the group */
groupMetadata = (jid: string) => this.query({json: ['query', 'GroupMetadata', jid], expect200: true}) as Promise<WAGroupMetadata>
/**
* Get the metadata of the group
* Baileys automatically caches & maintains this state
*/
@Mutex(jid => jid)
async groupMetadata (jid: string) {
const chat = this.chats.get(jid)
let metadata = chat?.metadata
if (!metadata) {
if (chat?.read_only) {
metadata = await this.groupMetadataMinimal(jid)
} else {
metadata = await this.fetchGroupMetadataFromWA(jid)
}
if (chat) chat.metadata = metadata
}
return metadata
}
/** Get the metadata of the group from WA */
fetchGroupMetadataFromWA = (jid: string) => this.query({json: ['query', 'GroupMetadata', jid], expect200: true}) as Promise<WAGroupMetadata>
/** Get the metadata (works after you've left the group also) */
groupMetadataMinimal = async (jid: string) => {
const query = ['query', {type: 'group', jid: jid, epoch: this.msgCount.toString()}, null]
@@ -49,18 +68,20 @@ export class WAConnection extends Base {
groupCreate = async (title: string, participants: string[]) => {
const response = await this.groupQuery('create', null, title, participants) as WAGroupCreateResponse
const gid = response.gid
let metadata: WAGroupMetadata
try {
await this.groupMetadata (gid)
metadata = await this.groupMetadata (gid)
} catch (error) {
this.logger.warn (`error in group creation: ${error}, switching gid & checking`)
// if metadata is not available
const comps = gid.replace ('@g.us', '').split ('-')
response.gid = `${comps[0]}-${+comps[1] + 1}@g.us`
await this.groupMetadata (gid)
metadata = await this.groupMetadata (gid)
this.logger.warn (`group ID switched from ${gid} to ${response.gid}`)
}
await this.chatAdd (response.gid, title)
this.chats.get(response.gid).metadata = metadata
return response
}
/**
@@ -82,7 +103,7 @@ export class WAConnection extends Base {
*/
groupUpdateSubject = async (jid: string, title: string) => {
const chat = this.chats.get (jid)
if (chat?.name === title) throw new Error ('redundant change')
if (chat?.name === title) throw new BaileysError ('redundant change', { status: 400 })
const response = await this.groupQuery('subject', jid, title)
if (chat) chat.name = title

View File

@@ -180,7 +180,7 @@ export interface WAGroupMetadata {
restrict?: 'true' | 'false'
/** is set when the group only allows admins to write messages */
announce?: 'true' | 'false'
participants: [{ id: string; isAdmin: boolean; isSuperAdmin: boolean }]
participants: { id: string; isAdmin: boolean; isSuperAdmin: boolean }[]
}
export interface WAGroupModification {
status: number
@@ -191,7 +191,7 @@ export interface WAPresenceData {
lastSeen?: number
name?: string
}
export interface WAContact extends WAPresenceData {
export interface WAContact {
verify?: string
/** name of the contact, the contact has set on their own on WA */
notify?: string
@@ -227,6 +227,7 @@ export interface WAChat {
messages: KeyedDB<WAMessage, string>
imgUrl?: string
presences?: { [k: string]: WAPresenceData }
metadata?: WAGroupMetadata
}
export type WAChatUpdate = Partial<WAChat> & { jid: string, hasNewMessage?: boolean }
export enum WAMetric {