diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f44ebac..a1cd028b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **Opt-in deep chat history (`deep=true`).** `GET /sessions/:id/messages/:chatId/history` was capped at + 100 messages per request — OpenWA's own bound, not a WhatsApp limit, since whatsapp-web.js can load + earlier messages on demand. A new `deep=true` query raises the ceiling to 2000 so callers can reach + weeks/months back. Deep mode is metadata-only (it ignores `includeMedia`, since base64 for up to 2000 + messages would be an enormous payload). The default path is unchanged (default 50, max 100). The Baileys + engine has no history sync, so the endpoint still returns `501` there regardless of `deep`. (#347) + ### Fixed - **Baileys engine: inbound message ids are now engine-neutral (`@c.us`).** The Baileys adapter emitted diff --git a/docs/examples/chat-history-limits.md b/docs/examples/chat-history-limits.md index 635e4f56..91d5cfd6 100644 --- a/docs/examples/chat-history-limits.md +++ b/docs/examples/chat-history-limits.md @@ -23,17 +23,32 @@ This endpoint asks the active WhatsApp engine for recent messages in a chat. It The endpoint is intentionally bounded: - `limit` defaults to `50`. -- `limit` is clamped to the range `1`–`100`. +- `limit` is clamped to the range `1`–`100` (or `1`–`2000` with `deep=true`, see below). - Values such as `limit=999` do not request unbounded history; they are reduced to the maximum allowed limit. - `includeMedia=true` downloads media data and is slower than metadata-only history. +- `deep=true` raises the ceiling to `2000` for reaching further back, and forces metadata-only. ## How Deep It Can Reach -The live history endpoint returns at most the **100 most recent** messages per request (the `limit` clamp -above). The `whatsapp-web.js` engine *can* load older messages on demand — internally it drives WhatsApp -Web's "load earlier messages" mechanism — so reaching further back is bounded by **OpenWA's current cap**, -not by what WhatsApp Web is willing to expose. To go back weeks or months you would need a much larger -window than 100; a deeper-history mode is tracked in [#347](https://github.com/rmyndharis/OpenWA/issues/347). +By default the live history endpoint returns at most the **100 most recent** messages per request (the +`limit` clamp above). The `whatsapp-web.js` engine *can* load older messages on demand — internally it +drives WhatsApp Web's "load earlier messages" mechanism — so reaching further back is bounded by +**OpenWA's cap**, not by what WhatsApp Web is willing to expose. + +To go back weeks or months, set `deep=true`. This raises the ceiling to **2000** messages per request: + +```http +GET /api/sessions/{sessionId}/messages/{chatId}/history?limit=2000&deep=true +``` + +Deep mode is **metadata-only** — `includeMedia` is ignored, because downloading base64 media for up to +2000 messages would produce an enormous, slow response. Fetch media separately for the specific messages +you need. Note that a very large, rapid history pull is heavier on the linked session and can increase the +risk of WhatsApp rate-limiting; use the smallest window that meets your need. + +Deep mode applies to the `whatsapp-web.js` engine. The Baileys engine does not expose on-demand history +(it has no message-history sync), so the history endpoint returns `501 Not Implemented` there regardless +of `deep`; consume Baileys history through local storage / webhooks / WebSocket as it arrives instead. There is still an ultimate ceiling: once WhatsApp's servers stop returning older messages for the linked session, no further history is retrievable through the web engine, regardless of `limit`. So the endpoint diff --git a/src/modules/message/message.controller.ts b/src/modules/message/message.controller.ts index 4cf7fcb4..160adc4e 100644 --- a/src/modules/message/message.controller.ts +++ b/src/modules/message/message.controller.ts @@ -272,21 +272,32 @@ export class MessageController { type: Boolean, description: 'When true, downloads media (base64) for messages that have it. Slower; default false.', }) + @ApiQuery({ + name: 'deep', + required: false, + type: Boolean, + description: + 'When true, raises the limit ceiling from 100 to 2000 for reaching further back in history ' + + '(whatsapp-web.js only; loads earlier messages on demand). Forces metadata-only (includeMedia ' + + 'is ignored). Large/slow requests may increase WhatsApp rate-limiting risk; default false.', + }) @ApiResponse({ status: 200, description: 'Chat history (most recent messages)' }) async getChatHistory( @Param('sessionId') sessionId: string, @Param('chatId') chatId: string, @Query('limit') limit?: string, @Query('includeMedia') includeMedia?: string, + @Query('deep') deep?: string, ) { // Parse the limit defensively: a non-numeric query value (?limit=abc) yields NaN, - // so fall back to undefined and let the service apply its default + [1,100] clamp. + // so fall back to undefined and let the service apply its default + clamp. const parsedLimit = limit ? parseInt(limit, 10) : undefined; return this.messageService.getChatHistory( sessionId, chatId, parsedLimit !== undefined && !Number.isNaN(parsedLimit) ? parsedLimit : undefined, includeMedia === 'true' || includeMedia === '1', + deep === 'true' || deep === '1', ); } diff --git a/src/modules/message/message.service.spec.ts b/src/modules/message/message.service.spec.ts index 9fade9d2..f1e01d53 100644 --- a/src/modules/message/message.service.spec.ts +++ b/src/modules/message/message.service.spec.ts @@ -555,6 +555,28 @@ describe('MessageService', () => { const result = await service.getChatHistory('sess-1', 'test@c.us'); expect(result).toBe(fake); }); + + describe('deep mode (#347)', () => { + it('allows a limit above the standard 100 cap when deep=true', async () => { + await service.getChatHistory('sess-1', 'test@c.us', 500, false, true); + expect(mockEngine.getChatHistory).toHaveBeenLastCalledWith('test@c.us', 500, false); + }); + + it('clamps a deep limit to the 2000 ceiling', async () => { + await service.getChatHistory('sess-1', 'test@c.us', 5000, false, true); + expect(mockEngine.getChatHistory).toHaveBeenLastCalledWith('test@c.us', 2000, false); + }); + + it('forces includeMedia off in deep mode (metadata-only)', async () => { + await service.getChatHistory('sess-1', 'test@c.us', 300, true, true); + expect(mockEngine.getChatHistory).toHaveBeenLastCalledWith('test@c.us', 300, false); + }); + + it('still clamps to 100 when deep is not set (regression guard)', async () => { + await service.getChatHistory('sess-1', 'test@c.us', 500, false, false); + expect(mockEngine.getChatHistory).toHaveBeenLastCalledWith('test@c.us', 100, false); + }); + }); }); describe('deleteMessage', () => { diff --git a/src/modules/message/message.service.ts b/src/modules/message/message.service.ts index e69072e2..5a1e4705 100644 --- a/src/modules/message/message.service.ts +++ b/src/modules/message/message.service.ts @@ -530,20 +530,24 @@ export class MessageService { /** Maximum messages a single getChatHistory call may request from the engine. */ private static readonly MAX_CHAT_HISTORY_LIMIT = 100; + /** Higher ceiling for opt-in deep history (`deep=true`). Bounded so a caller still can't ask unbounded. */ + private static readonly MAX_DEEP_CHAT_HISTORY_LIMIT = 2000; + /** * Fetch chat history live from WhatsApp (bypasses local DB). * Returns the most recent `limit` messages for the given chat. * When `includeMedia` is true, downloads media (base64) for messages that have it. * - * `limit` is clamped to [1, 100] (and falls back to 50 for non-finite input) so a - * caller cannot ask the engine to fetch an unbounded number of messages. + * `limit` is clamped to [1, 100] (and falls back to 50 for non-finite input) so a caller cannot ask the + * engine to fetch an unbounded number of messages. When `deep` is true the ceiling is raised to 2000 + * (for reaching weeks/months back on whatsapp-web.js, which can load earlier messages on demand) and + * media is forced off — downloading base64 for up to 2000 messages would be an enormous, slow payload. */ - async getChatHistory(sessionId: string, chatId: string, limit = 50, includeMedia = false) { + async getChatHistory(sessionId: string, chatId: string, limit = 50, includeMedia = false, deep = false) { const engine = this.getEngine(sessionId); - const safeLimit = Number.isFinite(limit) - ? Math.min(Math.max(Math.trunc(limit), 1), MessageService.MAX_CHAT_HISTORY_LIMIT) - : 50; - return engine.getChatHistory(chatId, safeLimit, includeMedia); + const ceiling = deep ? MessageService.MAX_DEEP_CHAT_HISTORY_LIMIT : MessageService.MAX_CHAT_HISTORY_LIMIT; + const safeLimit = Number.isFinite(limit) ? Math.min(Math.max(Math.trunc(limit), 1), ceiling) : 50; + return engine.getChatHistory(chatId, safeLimit, deep ? false : includeMedia); } // ========== Delete Message ==========