[core][noweb] Add presence endpoints and webhook
This commit is contained in:
1 parent
642079ab1f
commit
374199905d
14 files changed
+290
-1
No files matched your search
@@ -75,6 +75,7 @@
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Get presence (online\offline\typing) status
|
||||
|
||||
# Quick start
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 801
|
||||
weight: 899
|
||||
---
|
||||
|
||||
This page provides useful information to assist you in deploying the project.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: "Presence"
|
||||
description: "Presence"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 801
|
||||
---
|
||||
|
||||
You can get presence information (online, offline with last seen, typing status) for a contact if they share their
|
||||
presence information.
|
||||
|
||||
{{< alert icon="👉" text="Presence endpoints and webhook is available only in **NOWEB** engine." />}}
|
||||
[Read more about engines to choose right for you ->]({{< relref "/docs/how-to/engines" >}})
|
||||
|
||||
Here's few notes about fields:
|
||||
|
||||
- `chatId` - either contact id (`213213213@c.us`) or group chat id (`1111111111111@g.us`).
|
||||
- `lastSeen` - contains Unix timestamps indicating when a participant was last online
|
||||
- `lastKnownPresence` - contains the last known presence status, which can be
|
||||
`offline`, `online`, `typing`, `recording`, or `paused`
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Get all presence
|
||||
|
||||
You can get all presence information available for a session by calling `GET /api/{session}/presence/`.
|
||||
It returns both groups' and personal chats' presence information.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "2132132130@c.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "2132132130@c.us",
|
||||
"lastKnownPresence": "offline",
|
||||
"lastSeen": 1686719326
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "11111111111111111111111@g.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "11111111111111111111111@g.us",
|
||||
"lastKnownPresence": "online",
|
||||
"lastSeen": null
|
||||
},
|
||||
{
|
||||
"participant": "2132132130@c.us",
|
||||
"lastKnownPresence": "offline",
|
||||
"lastSeen": 1686719326
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get chat presence
|
||||
|
||||
To get presence information for a single chat - call `GET /api/{session}/presence/{chatId}`.
|
||||
For a group, you'll get participants' statuses.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "2132132130@c.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "2132132130@c.us",
|
||||
"lastKnownPresence": "online",
|
||||
"lastSeen": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Subscribe to presence
|
||||
|
||||
You can subscribe to presence information by calling `POST /api/{session}/presence/{chatId}/subscribe` request
|
||||
(no body required).
|
||||
You can get later presence information for the chat with above `GET` endpoints or by listening to `presence.update`
|
||||
webhook.
|
||||
|
||||
## Webhook
|
||||
|
||||
You can subscribe to `presence.update` webhook event to get the most recent presence information.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "presence.update",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"id": "111111111111111111@g.us",
|
||||
"presences": [
|
||||
{
|
||||
"participant": "11111111111@c.us",
|
||||
"lastKnownPresence": "typing",
|
||||
"lastSeen": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"engine": "NOWEB"
|
||||
}
|
||||
```
|
||||
@@ -16,6 +16,8 @@ toc: true
|
||||
July 2023
|
||||
- Added [proxy configuration](https://waha.devlike.pro/docs/how-to/config/#proxy) with supporting proxy authentication.
|
||||
Thanks **puntolino** for the contribution!
|
||||
- Added [presence information](https://waha.devlike.pro/docs/how-to/presence) - now you can get online status for
|
||||
a contact by calling endpoints or receiving a webhook event!
|
||||
- Now you can mention contact in groups by settings `mentions` field in `POST /api/sendText`
|
||||
[read more about it in Send Messages ->]({{< relref "/docs/how-to/send-messages" >}})
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ Here's the available features:
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Get presence (online\offline\typing) status
|
||||
|
||||
### Sessions
|
||||
- Multi Device
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
GetPresenceQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Controller, Get, Param, Post } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import { WAHAChatPresences } from '../structures/presence.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/presence')
|
||||
@ApiTags('presence')
|
||||
export class PresenceController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all subscribed presence information.' })
|
||||
getPresenceAll(
|
||||
@SessionParam session: WhatsappSession,
|
||||
): Promise<WAHAChatPresences[]> {
|
||||
return session.getPresences();
|
||||
}
|
||||
|
||||
@Get(':chatId')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Get the presence for the chat id. If it hasn't been subscribed - it also subscribes to it.",
|
||||
})
|
||||
getPresence(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
): Promise<WAHAChatPresences> {
|
||||
return session.getPresence(chatId);
|
||||
}
|
||||
|
||||
@Post(':chatId/subscribe')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Subscribe to presence events for the chat.',
|
||||
})
|
||||
subscribe(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('chatId') chatId: string,
|
||||
): Promise<void> {
|
||||
return session.subscribePresence(chatId);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import * as fs from 'fs';
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
import { EventEmitter } from 'events';
|
||||
import { WAHAChatPresences } from '../../structures/presence.dto';
|
||||
|
||||
export function getBrowserExecutablePath() {
|
||||
if (fs.existsSync(CHROME_PATH)) {
|
||||
@@ -256,6 +257,18 @@ export abstract class WhatsappSession {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getPresences(): Promise<WAHAChatPresences[]> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getPresence(id: string): Promise<WAHAChatPresences> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public subscribePresence(id: string): Promise<void> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PassportModule } from '@nestjs/passport';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import { SessionManagerCore } from './manager.core';
|
||||
import { GroupsController } from '../api/groups.controller';
|
||||
import { PresenceController } from '../api/presence.controller';
|
||||
|
||||
export const IMPORTS = [
|
||||
ConfigModule.forRoot({
|
||||
@@ -36,6 +37,7 @@ export const CONTROLLERS = [
|
||||
ChattingController,
|
||||
ContactsController,
|
||||
GroupsController,
|
||||
PresenceController,
|
||||
ScreenshotController,
|
||||
VersionController,
|
||||
];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
SECOND,
|
||||
WAEvents,
|
||||
WAHAPresenceStatus,
|
||||
WhatsappEngine,
|
||||
WhatsappStatus,
|
||||
} from '../structures/enums.dto';
|
||||
@@ -8,6 +9,7 @@ import makeWASocket, {
|
||||
DisconnectReason,
|
||||
isJidGroup,
|
||||
makeInMemoryStore,
|
||||
PresenceData,
|
||||
useMultiFileAuthState,
|
||||
} from '@adiwajshing/baileys';
|
||||
|
||||
@@ -49,6 +51,11 @@ import { Message } from 'whatsapp-web.js';
|
||||
import * as fs from 'fs';
|
||||
import { Agent } from 'https';
|
||||
import { createAgentProxy } from './helpers.proxy';
|
||||
import {
|
||||
WAHAPresenceData,
|
||||
WAHAChatPresences,
|
||||
} from '../structures/presence.dto';
|
||||
import * as lodash from 'lodash';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -60,6 +67,15 @@ export const BaileysEvents = {
|
||||
CREDS_UPDATE: 'creds.update',
|
||||
MESSAGES_UPSERT: 'messages.upsert',
|
||||
GROUPS_UPSERT: 'groups.upsert',
|
||||
PRESENCE_UPDATE: 'presence.update',
|
||||
};
|
||||
|
||||
const PresenceStatuses = {
|
||||
unavailable: WAHAPresenceStatus.OFFLINE,
|
||||
available: WAHAPresenceStatus.ONLINE,
|
||||
composing: WAHAPresenceStatus.TYPING,
|
||||
recording: WAHAPresenceStatus.RECORDING,
|
||||
paused: WAHAPresenceStatus.PAUSED,
|
||||
};
|
||||
|
||||
export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
@@ -114,6 +130,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
this.store.bind(this.sock.ev);
|
||||
}
|
||||
|
||||
resubscribeToKnownPresences() {
|
||||
for (const jid in this.store.presences) {
|
||||
this.sock.presenceSubscribe(jid);
|
||||
}
|
||||
}
|
||||
|
||||
async buildClient() {
|
||||
this.sock = await this.makeSocket();
|
||||
this.connectStore();
|
||||
@@ -122,6 +144,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
if (connection === 'connecting') {
|
||||
this.qr.save('');
|
||||
this.status = WhatsappStatus.WORKING;
|
||||
this.resubscribeToKnownPresences();
|
||||
return;
|
||||
} else if (connection === 'close') {
|
||||
const shouldReconnect =
|
||||
@@ -388,6 +411,32 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'demote');
|
||||
}
|
||||
|
||||
public async getPresences(): Promise<WAHAChatPresences[]> {
|
||||
const result: WAHAChatPresences[] = [];
|
||||
for (const remoteJid in this.store.presences) {
|
||||
const storedPresences = this.store.presences[remoteJid];
|
||||
result.push(this.toWahaPresences(remoteJid, storedPresences));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getPresence(chatId: string): Promise<WAHAChatPresences> {
|
||||
await this.subscribePresence(chatId);
|
||||
const remoteJid = toJID(chatId);
|
||||
const storedPresences = this.store.presences[remoteJid];
|
||||
return this.toWahaPresences(remoteJid, storedPresences || []);
|
||||
}
|
||||
|
||||
public async subscribePresence(chatId: string): Promise<void> {
|
||||
const remoteJid = toJID(chatId);
|
||||
if (this.store.presences[remoteJid]) {
|
||||
return;
|
||||
}
|
||||
// Have no info - subscribe to listen for events
|
||||
await this.sock.presenceSubscribe(remoteJid);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
@@ -405,6 +454,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler);
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
return this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler);
|
||||
} else if (event === WAEvents.PRESENCE_UPDATE) {
|
||||
return this.sock.ev.on(BaileysEvents.PRESENCE_UPDATE, (data) =>
|
||||
handler(this.toWahaPresences(data.id, data.presences)),
|
||||
);
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(
|
||||
`Engine does not support webhook event: ${event}`,
|
||||
@@ -465,6 +518,30 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
});
|
||||
}
|
||||
|
||||
private toWahaPresences(
|
||||
remoteJid,
|
||||
storedPresences: PresenceData[],
|
||||
): WAHAChatPresences {
|
||||
const presences: WAHAPresenceData[] = [];
|
||||
for (const participant in storedPresences) {
|
||||
const data: PresenceData = storedPresences[participant];
|
||||
const lastKnownPresence = lodash.get(
|
||||
PresenceStatuses,
|
||||
data.lastKnownPresence,
|
||||
data.lastKnownPresence,
|
||||
);
|
||||
const presence: WAHAPresenceData = {
|
||||
participant: toCusFormat(participant),
|
||||
// @ts-ignore
|
||||
lastKnownPresence: lastKnownPresence,
|
||||
lastSeen: data.lastSeen || null,
|
||||
};
|
||||
presences.push(presence);
|
||||
}
|
||||
const chatId = toCusFormat(remoteJid);
|
||||
return { id: chatId, presences: presences };
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
|
||||
@@ -23,6 +23,10 @@ export class SwaggerModuleCore {
|
||||
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
|
||||
)
|
||||
.addTag('groups', `Groups methods.<br>`)
|
||||
.addTag(
|
||||
'presence',
|
||||
`Presence information. Available in <b>NOWEB</b> engine only.<br>`,
|
||||
)
|
||||
.addTag('other', 'Other methods')
|
||||
.addApiKey({
|
||||
type: 'apiKey',
|
||||
|
||||
@@ -29,6 +29,8 @@ export class GetMessageQuery extends ChatQuery {
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export class GetPresenceQuery extends ChatQuery {}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ export enum WAEvents {
|
||||
STATE_CHANGE = 'state.change',
|
||||
GROUP_JOIN = 'group.join',
|
||||
GROUP_LEAVE = 'group.leave',
|
||||
PRESENCE_UPDATE = 'presence.update',
|
||||
}
|
||||
|
||||
export enum WhatsappStatus {
|
||||
@@ -22,3 +23,11 @@ export enum WhatsappEngine {
|
||||
WEBJS = 'WEBJS',
|
||||
NOWEB = 'NOWEB',
|
||||
}
|
||||
|
||||
export enum WAHAPresenceStatus {
|
||||
OFFLINE = 'offline',
|
||||
ONLINE = 'online',
|
||||
TYPING = 'typing',
|
||||
RECORDING = 'recording',
|
||||
PAUSED = 'paused',
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAHAPresenceStatus } from './enums.dto';
|
||||
|
||||
export class WAHAPresenceData {
|
||||
@ApiProperty({
|
||||
description: 'Chat ID - participant or contact id',
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
participant: string;
|
||||
lastKnownPresence: WAHAPresenceStatus;
|
||||
@ApiProperty({
|
||||
example: 1686568773,
|
||||
})
|
||||
lastSeen?: number;
|
||||
}
|
||||
export class WAHAChatPresences {
|
||||
@ApiProperty({
|
||||
description: 'Chat ID - either group id or contact id',
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
id: string;
|
||||
presences: WAHAPresenceData[];
|
||||
}
|
||||
Reference in new issue
Block a user