diff --git a/.eslintrc.js b/.eslintrc.js index 8735bc0e..147ad2d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,6 +25,7 @@ module.exports = { "@typescript-eslint/interface-name-prefix": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/ban-ts-comment": "off", diff --git a/docs/site/content/en/docs/how-to/chats/index.md b/docs/site/content/en/docs/how-to/chats/index.md new file mode 100644 index 00000000..fa353f2e --- /dev/null +++ b/docs/site/content/en/docs/how-to/chats/index.md @@ -0,0 +1,41 @@ +--- +title : "Chats" +description: "Chats" +lead: "" +date: 2020-10-06T08:48:45+00:00 +lastmod: 2020-10-06T08:48:45+00:00 +draft: false +images: [] +weight: 126 +--- + +Chats methods. + +Parameters in path that you can find in below endpoints: +- `{session}` - use the session name for Whatsapp instance that you created with `POST /api/session/start` endpoint +- `{chatId}` - chat id in format `123123123123@[c.us|g.us]`, `c.us` for direct chats and `g.us` for groups. + +## Methods +### Get all chats +Get all chats +`GET /api/{session}/chats` + +### Get messages from chat +Get 100 messages from the chat + +`GET /api/{session}/chats/{chatId/messages?limit=100` + + +Get 100 messages from the chat, skip downloading media (images, files) + +`GET /api/{session}/chats/{chatId/messages?limit=100&downloadMedia=false` + +### Delete chat +Use the method to delete chat + +`DELETE /api/{session}/chats/{chatId}` + +### Clear messages +Use the method to clear all messages from the chat + +`DELETE /api/{session}/chats/{chatId}/messages` diff --git a/docs/site/content/en/docs/how-to/receive-messages/index.md b/docs/site/content/en/docs/how-to/receive-messages/index.md index 74fd9e68..39503e67 100644 --- a/docs/site/content/en/docs/how-to/receive-messages/index.md +++ b/docs/site/content/en/docs/how-to/receive-messages/index.md @@ -155,7 +155,10 @@ If you want to limit files lifetime, specify file types for download or change d ## Endpoints ### Get messages -You can read messages from the history by using `GET /api/messages` endpoint. +Read messages from the history - use `GET /api/messages` endpoint. + +You also can get messages for chats by using +[**Get messages from chat ->**]({{< relref "/docs/how-to/chats" >}}) endpoint. {{< alert icon="👉" text="We do not recommend using this method in production, consider using webhooks instead!" />}} diff --git a/docs/site/content/en/docs/overview/changelog.md b/docs/site/content/en/docs/overview/changelog.md index 17de3e82..bd99ef40 100644 --- a/docs/site/content/en/docs/overview/changelog.md +++ b/docs/site/content/en/docs/overview/changelog.md @@ -14,6 +14,12 @@ toc: true --- ## 2023.8 August 2023 +- Added [chats endpoints](https://waha.devlike.pro/docs/how-to/chats) + - Get all chats - `GET /api/{session/chats` + - Delete chat - `DELETE /api/{session/chats/{chatId}` + - Get messages from chat - `GET /api/{session/chats/{chatId}/messages?limit=1000?downloadMedia=true` + - Clear messages in the chat `DELETE /api/{session/chats/{chatId}/messages` +- Added `downloadMedia` flag to speed up `GET /api/messages` and `GET /api/{session/chats/{chatId}/messages` performance. - Added [set presence endpoint](https://waha.devlike.pro/docs/how-to/presence)! - With `online` presence you can show your status as online [#121](https://github.com/devlikeapro/whatsapp-http-api/issues/121) - With `offline` presence you can get notifications for your device [#28](https://github.com/devlikeapro/whatsapp-http-api/issues/28) diff --git a/src/api/chats.controller.ts b/src/api/chats.controller.ts new file mode 100644 index 00000000..3ba3fb40 --- /dev/null +++ b/src/api/chats.controller.ts @@ -0,0 +1,56 @@ +import { Controller, Delete, Get, Param, Post, Query } 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 { GetChatMessagesQuery } from '../structures/chats.dto'; +import { parseBool } from '../helpers'; + +@ApiSecurity('api_key') +@Controller('api/:session/chats') +@ApiTags('chats') +class ChatsController { + constructor(private manager: SessionManager) {} + + @Get('') + @SessionApiParam + @ApiOperation({ summary: 'Get all chats' }) + getAllChats(@SessionParam session: WhatsappSession) { + return session.getChats(); + } + + @Delete(':chatId') + @SessionApiParam + @ApiOperation({ summary: 'Deletes the chat' }) + deleteChat( + @SessionParam session: WhatsappSession, + @Param('chatId') chatId: string, + ) { + return session.deleteChat(chatId); + } + + @Get(':chatId/messages') + @SessionApiParam + @ApiOperation({ summary: 'Gets messages in the chat' }) + getChatMessages( + @Query() query: GetChatMessagesQuery, + @SessionParam session: WhatsappSession, + @Param('chatId') chatId: string, + ) { + const downloadMedia = parseBool(query.downloadMedia); + return session.getChatMessages(chatId, query.limit, downloadMedia); + } + + @Delete(':chatId/messages') + @SessionApiParam + @ApiOperation({ summary: 'Clears all messages from the chat' }) + clearMessages( + @SessionParam session: WhatsappSession, + @Param('chatId') chatId: string, + ) { + return session.clearMessages(chatId); + } +} + +export { ChatsController }; diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index 9aed5bc7..301ef028 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -43,6 +43,7 @@ export function getBrowserExecutablePath() { } return CHROMIUM_PATH; } + export function ensureSuffix(phone) { const suffix = '@c.us'; if (phone.includes('@')) { @@ -168,10 +169,35 @@ export abstract class WhatsappSession { abstract stopTyping(chat: ChatRequest); - abstract getMessages(query: GetMessageQuery); + getMessages(query: GetMessageQuery) { + throw new NotImplementedByEngineError(); + } abstract setReaction(request: MessageReactionRequest); + /** + * Chats methods + */ + public getChats() { + throw new NotImplementedByEngineError(); + } + + public deleteChat(chatId) { + throw new NotImplementedByEngineError(); + } + + public getChatMessages( + chatId: string, + limit: number, + downloadMedia: boolean, + ) { + throw new NotImplementedByEngineError(); + } + + public clearMessages(chatId) { + throw new NotImplementedByEngineError(); + } + /** * Contacts methods */ diff --git a/src/core/app.module.core.ts b/src/core/app.module.core.ts index 94c6c00e..8f31c981 100644 --- a/src/core/app.module.core.ts +++ b/src/core/app.module.core.ts @@ -13,6 +13,7 @@ import { VersionController } from '../api/version.controller'; import { WhatsappConfigService } from '../config.service'; import { SessionManager } from './abc/manager.abc'; import { SessionManagerCore } from './manager.core'; +import { ChatsController } from '../api/chats.controller'; export const IMPORTS = [ ConfigModule.forRoot({ @@ -36,6 +37,7 @@ export const IMPORTS = [ export const CONTROLLERS = [ SessionsController, ChattingController, + ChatsController, ContactsController, GroupsController, PresenceController, diff --git a/src/core/session.noweb.core.ts b/src/core/session.noweb.core.ts index a1528875..e36be980 100644 --- a/src/core/session.noweb.core.ts +++ b/src/core/session.noweb.core.ts @@ -307,10 +307,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { return this.sock.sendPresenceUpdate('paused', request.chatId); } - async getMessages(query: GetMessageQuery) { - throw new NotImplementedByEngineError(); - } - async setReaction(request: MessageReactionRequest) { const key = parseMessageId(request.messageId); const reactionMessage = { diff --git a/src/core/session.venom.core.ts b/src/core/session.venom.core.ts index 8d43ab89..5e34fc60 100644 --- a/src/core/session.venom.core.ts +++ b/src/core/session.venom.core.ts @@ -228,16 +228,16 @@ export class WhatsappSessionVenomCore extends WhatsappSession { // return this.whatsapp.stopTyping(chat.chatId); } - async getMessages(query: GetMessageQuery) { + async getChatMessages(chatId: string, limit: number) { const messages = await this.whatsapp.getAllMessagesInChat( - query.chatId, + chatId, true, false, ); // Go over messages, download media, and convert to right format. const result = []; for (const [count, message] of messages.entries()) { - if (count > query.limit) { + if (count > limit) { // Have enough in the list, stop processing break; } @@ -246,6 +246,10 @@ export class WhatsappSessionVenomCore extends WhatsappSession { return result; } + async getMessages(query: GetMessageQuery) { + return this.getChatMessages(query.chatId, query.limit); + } + setReaction(request: MessageReactionRequest) { throw new NotImplementedByEngineError(); } diff --git a/src/core/session.webjs.core.ts b/src/core/session.webjs.core.ts index e1804bae..1833c742 100644 --- a/src/core/session.webjs.core.ts +++ b/src/core/session.webjs.core.ts @@ -212,18 +212,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { } async getMessages(query: GetMessageQuery) { - const chat: Chat = await this.whatsapp.getChatById( - this.ensureSuffix(query.chatId), - ); - const messages = await chat.fetchMessages({ - limit: query.limit, - }); - // Go over messages, download media, and convert to right format. - const result = []; - for (const message of messages) { - result.push(await this.processIncomingMessage(message)); - } - return result; + return this.getChatMessages(query.chatId, query.limit, query.downloadMedia); } async setReaction(request: MessageReactionRequest) { @@ -236,6 +225,39 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { return message.react(request.reaction); } + /** + * Chats methods + */ + getChats() { + return this.whatsapp.getChats(); + } + + async getChatMessages(chatId: string, limit: number, downloadMedia: boolean) { + const chat: Chat = await this.whatsapp.getChatById( + this.ensureSuffix(chatId), + ); + const messages = await chat.fetchMessages({ + limit: limit, + }); + // Go over messages, download media, and convert to right format. + const result = []; + for (const message of messages) { + const msg = await this.processIncomingMessage(message, downloadMedia); + result.push(msg); + } + return result; + } + + async deleteChat(chatId) { + const chat = await this.whatsapp.getChatById(chatId); + return chat.delete(); + } + + async clearMessages(chatId) { + const chat = await this.whatsapp.getChatById(chatId); + return chat.clearMessages(); + } + /** * Contacts methods */ @@ -426,8 +448,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { } } - private processIncomingMessage(message: Message) { - return this.downloadMedia(message).then(this.toWAMessage); + private async processIncomingMessage(message: Message, downloadMedia = true) { + if (downloadMedia) { + try { + message = await this.downloadMedia(message); + } catch (e) { + this.log.error('Failed when tried to download media for a message'); + this.log.error(e, e.stack); + } + } + return await this.toWAMessage(message); } protected toWAMessage(message: Message): Promise { diff --git a/src/core/swagger.module.core.ts b/src/core/swagger.module.core.ts index 61944573..c6e72703 100644 --- a/src/core/swagger.module.core.ts +++ b/src/core/swagger.module.core.ts @@ -16,7 +16,8 @@ export class SwaggerModuleCore { .setVersion(VERSION.version) .addTag('sessions', 'Control WhatsApp sessions') .addTag('screenshot', 'Get screenshot of WhatsApp and show QR code') - .addTag('chatting', 'Chat methods') + .addTag('chatting', 'Chatting methods') + .addTag('chats', `Chats methods`) .addTag( 'contacts', `Contacts methods.
diff --git a/src/structures/chats.dto.ts b/src/structures/chats.dto.ts new file mode 100644 index 00000000..294e2eba --- /dev/null +++ b/src/structures/chats.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { SessionBaseRequest, SessionQuery } from './base.dto'; + +/** + * Queries + */ + +export class GetChatMessagesQuery extends SessionQuery { + limit: number = 100; + @ApiProperty({ + example: true, + required: false, + description: 'Download media for messages', + }) + downloadMedia: boolean = true; +} + +/** + * Requests + */ diff --git a/src/structures/chatting.dto.ts b/src/structures/chatting.dto.ts index e2a351c1..b377bfe8 100644 --- a/src/structures/chatting.dto.ts +++ b/src/structures/chatting.dto.ts @@ -28,6 +28,12 @@ export class ChatQuery extends SessionQuery { export class GetMessageQuery extends ChatQuery { @IsNumber() limit: number; + @ApiProperty({ + example: true, + required: false, + description: 'Download media for messages', + }) + downloadMedia: true; } export class GetPresenceQuery extends ChatQuery {}