feat(ChatWoot): send messages from device as messages in ChatWoot - fix #2249

This commit is contained in:
devlikepro committed 2026-09-11 13:39:59 +07:00
1 parent 8e36c7a14c
commit ee6a952490
12 files changed
+86 -5

No files matched your search

@@ -13,6 +13,7 @@ import { ChatWootQueueService } from '@waha/apps/chatwoot/services/ChatWootQueue
import { SessionManager } from '@waha/core/abc/manager.abc';
import { AppRepository } from '@waha/apps/app_sdk/storage/AppRepository';
import { CommandPrefix } from '@waha/apps/chatwoot/cli';
import { IsExternalEcho } from '@waha/apps/chatwoot/api/webhook.guards';
@Controller('webhooks/chatwoot/')
export class ChatwootWebhookController {
@@ -41,8 +42,13 @@ export class ChatwootWebhookController {
}
const isCommandsChat = IsCommandsChat(body);
// Ignore private notes (most of them)
const deleted = body?.content_attributes?.deleted;
// Ignore messages that came from WhatsApp, do not send them back
if (IsExternalEcho(body) && !deleted) {
return { success: true };
}
// Ignore private notes (most of them)
if (body.private) {
// Ignore any private note in commands chats
if (isCommandsChat) {
+15
View File
@@ -0,0 +1,15 @@
import { conversation_message_create } from '@figuro/chatwoot-sdk';
/**
* Mark a message WAHA creates from a WhatsApp message, so the webhook does not send it back
*/
export function SetExternalEcho(body: conversation_message_create) {
body.content_attributes = { ...body.content_attributes, external_echo: true };
}
/**
* Message was created by WAHA from a WhatsApp message, not by an agent
*/
export function IsExternalEcho(body: any): boolean {
return Boolean(body?.content_attributes?.external_echo);
}
@@ -294,6 +294,7 @@ class MessagesPullHandler {
this.session,
container.Locale(),
container.WAHASelf(),
container.OutgoingMode(),
);
handler.force = options.force;
+35 -4
View File
@@ -36,10 +36,11 @@ import { EngineHelper } from '@waha/apps/chatwoot/waha';
import { EnsureSeconds } from '@waha/utils/timehelper';
import { CHATWOOT_MESSAGE_CALENDAR_THRESHOLD_SECONDS } from '@waha/apps/chatwoot/env';
import {
ChatWootAppConfig,
ChatWootConfig,
ChatWootOutgoingMode,
} from '@waha/apps/chatwoot/dto/config.dto';
import { clearContent } from '@waha/apps/chatwoot/consumers/utils';
import { SetExternalEcho } from '@waha/apps/chatwoot/api/webhook.guards';
export function ListenEventsForChatWoot(config: ChatWootConfig) {
const events = [
@@ -235,6 +236,7 @@ export abstract class MessageBaseHandler<
protected session: WAHASessionAPI,
protected l: Locale,
protected waha: WAHASelf,
protected outgoingMode: ChatWootOutgoingMode,
) {}
protected abstract getMessage(
@@ -402,10 +404,35 @@ export abstract class MessageBaseHandler<
},
);
const private_ = message.private ?? payload.fromMe;
const type = private_ ? MessageType.OUTGOING : MessageType.INCOMING;
// Send the message as a private note or as a regular message?
let private_ = message.private;
if (private_ === undefined) {
switch (this.outgoingMode) {
case ChatWootOutgoingMode.MESSAGE:
// Regular outgoing message, not a note
private_ = false;
break;
case ChatWootOutgoingMode.PRIVATE_NOTE:
default: // no value - old config, treat it as PRIVATE_NOTE
// Mark my messages as private notes
private_ = Boolean(payload.fromMe);
break;
}
}
// Send the message as incoming or outgoing?
let type: MessageType = MessageType.INCOMING;
if (payload.fromMe) {
// Any message from me is outgoing, private or not
type = MessageType.OUTGOING;
}
if (private_) {
// Private notes can only be sent as outgoing messages
type = MessageType.OUTGOING;
}
content = this.finalizeContent(content, payload);
return {
const body: conversation_message_create = {
content: content,
message_type: type,
private: private_,
@@ -414,6 +441,10 @@ export abstract class MessageBaseHandler<
in_reply_to: replyTo,
},
};
if (payload.fromMe) {
SetExternalEcho(body);
}
return body;
}
async getReplyToChatWootMessageID(
@@ -59,6 +59,7 @@ export class WAHACallAcceptedConsumer extends ChatWootWAHABaseConsumer {
new WAHASessionAPI(event.session, container.WAHASelf()),
locale,
container.WAHASelf(),
container.OutgoingMode(),
);
const msg = BuildCallMessagePayload(
event.payload,
@@ -60,6 +60,7 @@ export class WAHACallReceivedConsumer extends ChatWootWAHABaseConsumer {
new WAHASessionAPI(event.session, container.WAHASelf()),
locale,
container.WAHASelf(),
container.OutgoingMode(),
);
const msg = BuildCallMessagePayload(
event.payload,
@@ -59,6 +59,7 @@ export class WAHACallRejectedConsumer extends ChatWootWAHABaseConsumer {
new WAHASessionAPI(event.session, container.WAHASelf()),
locale,
container.WAHASelf(),
container.OutgoingMode(),
);
const msg = BuildCallMessagePayload(
event.payload,
@@ -65,6 +65,7 @@ export class WAHAMessageAnyConsumer extends ChatWootWAHABaseConsumer {
session,
container.Locale(),
container.WAHASelf(),
container.OutgoingMode(),
);
return await handler.handle(event.payload);
}
@@ -56,6 +56,7 @@ export class WAHAMessageEditedConsumer extends ChatWootWAHABaseConsumer {
session,
container.Locale(),
container.WAHASelf(),
container.OutgoingMode(),
);
return await handler.handle(event.payload);
}
@@ -60,6 +60,7 @@ export class WAHAMessageReactionConsumer extends ChatWootWAHABaseConsumer {
session,
container.Locale(),
container.WAHASelf(),
container.OutgoingMode(),
);
return await handler.handle(event.payload);
}
+6
View File
@@ -10,6 +10,7 @@ import { ChatWootInboxAPI } from '@waha/apps/chatwoot/client/interfaces';
import {
ChatWootAppConfig,
ChatWootConfig,
ChatWootOutgoingMode,
DEFAULT_LOCALE,
LinkPreview,
} from '@waha/apps/chatwoot/dto/config.dto';
@@ -119,6 +120,10 @@ export class DIContainer {
);
}
public OutgoingMode(): ChatWootOutgoingMode {
return this.ChatWootConfig().conversations.outgoing;
}
@CacheSync()
public ConversationSelector() {
const config = this.ChatWootConfig();
@@ -229,6 +234,7 @@ export function ChatWootConfigDefaults(
sort: ConversationSort.created_newest,
status: null,
markAsRead: true,
outgoing: ChatWootOutgoingMode.PRIVATE_NOTE,
},
};
return lodash.defaultsDeep({}, config, defaults);
+16
View File
@@ -30,6 +30,11 @@ export enum LinkPreview {
HQ = 'HG',
}
export enum ChatWootOutgoingMode {
PRIVATE_NOTE = 'private-note',
MESSAGE = 'message',
}
export class ChatWootConversationsConfig {
@IsEnum(ConversationSort)
sort: ConversationSort;
@@ -46,6 +51,17 @@ export class ChatWootConversationsConfig {
@IsOptional()
@IsBoolean()
markAsRead?: boolean = true;
@ApiPropertyOptional({
description:
'How to show messages sent from WhatsApp (not from ChatWoot): ' +
"'private-note' (default) or 'message' - a regular outgoing message, as if an agent sent it.",
enum: ChatWootOutgoingMode,
default: ChatWootOutgoingMode.PRIVATE_NOTE,
})
@IsOptional()
@IsEnum(ChatWootOutgoingMode)
outgoing?: ChatWootOutgoingMode = ChatWootOutgoingMode.PRIVATE_NOTE;
}
export interface ChatWootConfig {