@wabot-dev/framework 0.5.6 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/addon/chat-bot/anthropic/AnthropicChatAdapter.js +2 -1
- package/dist/src/addon/chat-bot/deepseek/DeepSeekChatAdapter.js +7 -1
- package/dist/src/addon/chat-bot/google/GoogleChatAdapter.js +2 -1
- package/dist/src/addon/chat-bot/openia/OpenaiChatAdapter.js +7 -2
- package/dist/src/addon/chat-controller/wasender/@whatsAppByWasender.js +20 -0
- package/dist/src/addon/chat-controller/wasender/WasenderWebhookController.js +90 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppByWasenderChannel.js +58 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppByWasenderChannelConfig.js +16 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppReceiverByWasender.js +39 -0
- package/dist/src/addon/chat-controller/wasender/WhatsAppSenderByWasender.js +33 -0
- package/dist/src/addon/chat-controller/wasender/extractNumberFromWasenderKey.js +5 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/@whatsAppByWasender.js +20 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppByWasenderChannel.js +52 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppByWasenderChannelConfig.js +16 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppReceiverByWasender.js +106 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppSenderByWasender.js +40 -0
- package/dist/src/addon/chat-controller/whatsapp-by-wasender/extractNumberFromWasenderKey.js +5 -0
- package/dist/src/feature/chat-bot/extractChatMessageText.js +13 -0
- package/dist/src/feature/rest-controller/metadata/RestControllerMetadataStore.js +35 -8
- package/dist/src/index.d.ts +136 -3
- package/dist/src/index.js +8 -0
- package/dist/src/node_modules/wasenderapi/dist/index.js +553 -0
- package/package.json +3 -2
|
@@ -6,6 +6,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
6
6
|
import 'uuid';
|
|
7
7
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
8
8
|
import { safeJsonParse } from '../../../feature/chat-bot/safeJsonParse.js';
|
|
9
|
+
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
9
10
|
import { Anthropic } from '@anthropic-ai/sdk';
|
|
10
11
|
|
|
11
12
|
let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
@@ -58,7 +59,7 @@ let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
|
58
59
|
if (!item.text) {
|
|
59
60
|
throw new Error('Assistant message content is empty');
|
|
60
61
|
}
|
|
61
|
-
return { role: 'assistant', content: item
|
|
62
|
+
return { role: 'assistant', content: extractChatMessageText(item) };
|
|
62
63
|
}
|
|
63
64
|
mapFunctionCall(item) {
|
|
64
65
|
return [
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Logger } from '../../../core/logger/Logger.js';
|
|
2
|
+
import '../../../feature/chat-bot/ChatBot.js';
|
|
3
|
+
import '../../../core/injection/index.js';
|
|
4
|
+
import 'uuid';
|
|
5
|
+
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
6
|
+
import '../../../core/error/setupErrorHandlers.js';
|
|
7
|
+
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
2
8
|
import { OpenAI } from 'openai';
|
|
3
9
|
|
|
4
10
|
class DeepSeekChatAdapter {
|
|
@@ -52,7 +58,7 @@ class DeepSeekChatAdapter {
|
|
|
52
58
|
if (!item.text) {
|
|
53
59
|
throw new Error('User message content is empty');
|
|
54
60
|
}
|
|
55
|
-
return { role: 'user', content: item
|
|
61
|
+
return { role: 'user', content: extractChatMessageText(item) };
|
|
56
62
|
}
|
|
57
63
|
mapBotMessage(item) {
|
|
58
64
|
if (!item.text) {
|
|
@@ -7,6 +7,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
7
7
|
import 'uuid';
|
|
8
8
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
9
9
|
import { safeJsonParse } from '../../../feature/chat-bot/safeJsonParse.js';
|
|
10
|
+
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
10
11
|
import { GoogleGenAI } from '@google/genai';
|
|
11
12
|
|
|
12
13
|
let GoogleChatAdapter = class GoogleChatAdapter {
|
|
@@ -54,7 +55,7 @@ let GoogleChatAdapter = class GoogleChatAdapter {
|
|
|
54
55
|
if (!item.text) {
|
|
55
56
|
throw new Error('Bot message content is empty');
|
|
56
57
|
}
|
|
57
|
-
return { role: 'model', parts: [{ text: item
|
|
58
|
+
return { role: 'model', parts: [{ text: extractChatMessageText(item) }] };
|
|
58
59
|
}
|
|
59
60
|
mapFunctionCall(item) {
|
|
60
61
|
return [
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { __decorate } from 'tslib';
|
|
2
|
+
import '../../../feature/chat-bot/ChatBot.js';
|
|
3
|
+
import { singleton } from '../../../core/injection/index.js';
|
|
4
|
+
import 'uuid';
|
|
5
|
+
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
6
|
+
import '../../../core/error/setupErrorHandlers.js';
|
|
7
|
+
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
2
8
|
import { Logger } from '../../../core/logger/Logger.js';
|
|
3
9
|
import { OpenAI } from 'openai';
|
|
4
|
-
import { singleton } from '../../../core/injection/index.js';
|
|
5
10
|
|
|
6
11
|
let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
7
12
|
openai = new OpenAI();
|
|
@@ -38,7 +43,7 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
38
43
|
mapConectionMessage(item) {
|
|
39
44
|
const content = [];
|
|
40
45
|
if (item.text)
|
|
41
|
-
content.push({ type: 'input_text', text: item
|
|
46
|
+
content.push({ type: 'input_text', text: extractChatMessageText(item) });
|
|
42
47
|
if (item.images) {
|
|
43
48
|
for (const image of item.images) {
|
|
44
49
|
content.push({
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { container } from '../../../core/injection/index.js';
|
|
2
|
+
import { WhatsAppByWasenderChannelConfig } from './WhatsAppByWasenderChannelConfig.js';
|
|
3
|
+
import { ControllerMetadataStore } from '../../../feature/chat-controller/metadata/ControllerMetadataStore.js';
|
|
4
|
+
import '../../../feature/chat-controller/ChatResolver.js';
|
|
5
|
+
import '../../../feature/chat-controller/runChatControllers.js';
|
|
6
|
+
import { WhatsAppByWasenderChannel } from './WhatsAppByWasenderChannel.js';
|
|
7
|
+
|
|
8
|
+
function whatsAppByWasender(config) {
|
|
9
|
+
return function (target, propertyKey) {
|
|
10
|
+
const store = container.resolve(ControllerMetadataStore);
|
|
11
|
+
store.saveChannelMetadata({
|
|
12
|
+
channelConstructor: WhatsAppByWasenderChannel,
|
|
13
|
+
functionName: propertyKey.toString(),
|
|
14
|
+
controllerConstructor: target.constructor,
|
|
15
|
+
channelConfig: new WhatsAppByWasenderChannelConfig(config ?? {}),
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { whatsAppByWasender };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
3
|
+
import '../../../core/injection/index.js';
|
|
4
|
+
import '../../../feature/rest-controller/metadata/RestControllerMetadataStore.js';
|
|
5
|
+
import { onPost } from '../../../feature/rest-controller/metadata/@onPost.js';
|
|
6
|
+
import { extractNumberFromWasenderMessageKey } from './extractNumberFromWasenderKey.js';
|
|
7
|
+
import { IncomingMessage } from 'http';
|
|
8
|
+
|
|
9
|
+
class WasenderWebhookController {
|
|
10
|
+
wasender;
|
|
11
|
+
listener;
|
|
12
|
+
logger = new Logger('wabot:wasender-webhook');
|
|
13
|
+
constructor(wasender, listener) {
|
|
14
|
+
this.wasender = wasender;
|
|
15
|
+
this.listener = listener;
|
|
16
|
+
}
|
|
17
|
+
async handleWebhook(req) {
|
|
18
|
+
const rawBody = await this.getRawBody(req);
|
|
19
|
+
const event = await this.parseEvent(req, rawBody);
|
|
20
|
+
this.logger.trace(`received event ${event.event}`);
|
|
21
|
+
switch (event.event) {
|
|
22
|
+
case 'messages.received':
|
|
23
|
+
const messages = Array.isArray(event.data.messages)
|
|
24
|
+
? event.data.messages
|
|
25
|
+
: [event.data.messages];
|
|
26
|
+
await this.handleMessages(messages);
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
this.logger.warn(`unhandled event type ${event.event}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async handleMessages(messages) {
|
|
33
|
+
if (!this.listener) {
|
|
34
|
+
this.logger.warn('No listener registered, ignoring messages');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const message of messages) {
|
|
38
|
+
const from = extractNumberFromWasenderMessageKey(message.key);
|
|
39
|
+
this.logger.trace(`new message from '${from}'`);
|
|
40
|
+
if (message.message.conversation) {
|
|
41
|
+
const chatConnection = {
|
|
42
|
+
chatType: 'PRIVATE',
|
|
43
|
+
channelName: 'WhatsAppByWasenderChannel',
|
|
44
|
+
id: from,
|
|
45
|
+
};
|
|
46
|
+
await this.listener({
|
|
47
|
+
chatConnection,
|
|
48
|
+
message: {
|
|
49
|
+
text: message.message.conversation,
|
|
50
|
+
senderName: message.pushName,
|
|
51
|
+
senderId: from,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async parseEvent(req, rawBody) {
|
|
58
|
+
const adapter = {
|
|
59
|
+
getHeader: (name) => {
|
|
60
|
+
const value = req.headers[name.toLowerCase()];
|
|
61
|
+
return Array.isArray(value) ? value[0] : value;
|
|
62
|
+
},
|
|
63
|
+
getRawBody: () => rawBody,
|
|
64
|
+
};
|
|
65
|
+
const event = await this.wasender.handleWebhookEvent(adapter);
|
|
66
|
+
return event;
|
|
67
|
+
}
|
|
68
|
+
getRawBody(req) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
let data = '';
|
|
71
|
+
req.on('data', (chunk) => {
|
|
72
|
+
data += chunk;
|
|
73
|
+
});
|
|
74
|
+
req.on('end', () => {
|
|
75
|
+
resolve(data);
|
|
76
|
+
});
|
|
77
|
+
req.on('error', (err) => {
|
|
78
|
+
reject(err);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
__decorate([
|
|
84
|
+
onPost({ disableJsonParser: true, disableUrlEncodedParser: true }),
|
|
85
|
+
__metadata("design:type", Function),
|
|
86
|
+
__metadata("design:paramtypes", [IncomingMessage]),
|
|
87
|
+
__metadata("design:returntype", Promise)
|
|
88
|
+
], WasenderWebhookController.prototype, "handleWebhook", null);
|
|
89
|
+
|
|
90
|
+
export { WasenderWebhookController };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import { injectable } from '../../../core/injection/index.js';
|
|
3
|
+
import { Env } from '../../../core/env/Env.js';
|
|
4
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
5
|
+
import { WhatsAppByWasenderChannelConfig } from './WhatsAppByWasenderChannelConfig.js';
|
|
6
|
+
import { WhatsAppReceiverByWasender } from './WhatsAppReceiverByWasender.js';
|
|
7
|
+
import { WhatsAppSenderByWasender } from './WhatsAppSenderByWasender.js';
|
|
8
|
+
|
|
9
|
+
let WhatsAppByWasenderChannel = class WhatsAppByWasenderChannel {
|
|
10
|
+
logger = new Logger('wabot:whatsapp-by-wasender-channel');
|
|
11
|
+
sender;
|
|
12
|
+
receiver;
|
|
13
|
+
phoneNumber;
|
|
14
|
+
constructor(config, env) {
|
|
15
|
+
const apiKey = config.apiKey ?? env.requireString('WASENDER_API_KEY');
|
|
16
|
+
const webhookSecret = config.webhookSecret ?? env.requireString('WASENDER_WEBHOOK_SECRET');
|
|
17
|
+
this.phoneNumber = config.phoneNumber ?? env.requireString('WASENDER_PHONE_NUMBER');
|
|
18
|
+
this.sender = new WhatsAppSenderByWasender(apiKey, config.retryOptions);
|
|
19
|
+
this.receiver = new WhatsAppReceiverByWasender({
|
|
20
|
+
apiKey,
|
|
21
|
+
webhookSecret,
|
|
22
|
+
webhookPath: config.webhookPath,
|
|
23
|
+
retryOptions: config.retryOptions,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
listen(callback) {
|
|
27
|
+
this.receiver.listenMessage(async (message) => {
|
|
28
|
+
try {
|
|
29
|
+
await callback({
|
|
30
|
+
chatConnection: message.chatConnection,
|
|
31
|
+
message: message.message,
|
|
32
|
+
reply: async (replyMessage) => {
|
|
33
|
+
await this.sender.send({
|
|
34
|
+
from: this.phoneNumber,
|
|
35
|
+
to: message.chatConnection.id,
|
|
36
|
+
message: replyMessage,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
this.logger.error('Failed to handle WhatsApp message', err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
connect() {
|
|
47
|
+
this.receiver.connect();
|
|
48
|
+
}
|
|
49
|
+
disconnect() {
|
|
50
|
+
this.receiver.disconnect();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
WhatsAppByWasenderChannel = __decorate([
|
|
54
|
+
injectable(),
|
|
55
|
+
__metadata("design:paramtypes", [WhatsAppByWasenderChannelConfig, Env])
|
|
56
|
+
], WhatsAppByWasenderChannel);
|
|
57
|
+
|
|
58
|
+
export { WhatsAppByWasenderChannel };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class WhatsAppByWasenderChannelConfig {
|
|
2
|
+
apiKey;
|
|
3
|
+
webhookSecret;
|
|
4
|
+
phoneNumber;
|
|
5
|
+
webhookPath;
|
|
6
|
+
retryOptions;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.apiKey = config.apiKey;
|
|
9
|
+
this.webhookSecret = config.webhookSecret;
|
|
10
|
+
this.phoneNumber = config.phoneNumber;
|
|
11
|
+
this.webhookPath = config.webhookPath ?? '/wasender/hook';
|
|
12
|
+
this.retryOptions = config.retryOptions ?? { enabled: true, maxRetries: 3 };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { WhatsAppByWasenderChannelConfig };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import '../../../core/injection/index.js';
|
|
3
|
+
import '../../../feature/rest-controller/metadata/RestControllerMetadataStore.js';
|
|
4
|
+
import { restController } from '../../../feature/rest-controller/metadata/@restController.js';
|
|
5
|
+
import { runRestControllers } from '../../../feature/rest-controller/runRestControllers.js';
|
|
6
|
+
import { createWasender } from '../../../node_modules/wasenderapi/dist/index.js';
|
|
7
|
+
import { WasenderWebhookController } from './WasenderWebhookController.js';
|
|
8
|
+
|
|
9
|
+
class WhatsAppReceiverByWasender {
|
|
10
|
+
config;
|
|
11
|
+
wasender;
|
|
12
|
+
listener = null;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.wasender = createWasender(config.apiKey, undefined, undefined, undefined, config.retryOptions, config.webhookSecret);
|
|
16
|
+
}
|
|
17
|
+
listenMessage(listener) {
|
|
18
|
+
this.listener = listener;
|
|
19
|
+
}
|
|
20
|
+
connect() {
|
|
21
|
+
const wasender = this.wasender;
|
|
22
|
+
const listener = this.listener;
|
|
23
|
+
let UniqueController = class UniqueController extends WasenderWebhookController {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(wasender, listener);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
UniqueController = __decorate([
|
|
29
|
+
restController(this.config.webhookPath),
|
|
30
|
+
__metadata("design:paramtypes", [])
|
|
31
|
+
], UniqueController);
|
|
32
|
+
runRestControllers([UniqueController]);
|
|
33
|
+
}
|
|
34
|
+
disconnect() {
|
|
35
|
+
// Nothing to disconnect
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { WhatsAppReceiverByWasender };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
2
|
+
import { createWasender } from '../../../node_modules/wasenderapi/dist/index.js';
|
|
3
|
+
|
|
4
|
+
class WhatsAppSenderByWasender {
|
|
5
|
+
wasender;
|
|
6
|
+
logger = new Logger('wabot:whatsapp-sender-by-wasender');
|
|
7
|
+
constructor(apiKey, retryOptions) {
|
|
8
|
+
this.wasender = createWasender(apiKey, undefined, undefined, undefined, retryOptions, undefined);
|
|
9
|
+
}
|
|
10
|
+
async send(request) {
|
|
11
|
+
try {
|
|
12
|
+
const textPayload = {
|
|
13
|
+
messageType: 'text',
|
|
14
|
+
to: `+${request.to.replace(/\D+/g, '')}`,
|
|
15
|
+
text: request.message.text ?? 'No Text',
|
|
16
|
+
};
|
|
17
|
+
const result = await this.wasender.send(textPayload);
|
|
18
|
+
this.logger.trace(`message sent from '${request.from}' to '${request.to}'`);
|
|
19
|
+
this.logger.trace(`rate limit remaining: ${result.rateLimit?.remaining}`);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
this.logger.error(`Failed to send message from '${request.from}' to '${request.to}'`, error);
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
throw new Error(error.message, { cause: error });
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new Error('error sending message');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { WhatsAppSenderByWasender };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { container } from '../../../core/injection/index.js';
|
|
2
|
+
import { WhatsAppByWasenderChannelConfig } from './WhatsAppByWasenderChannelConfig.js';
|
|
3
|
+
import { ControllerMetadataStore } from '../../../feature/chat-controller/metadata/ControllerMetadataStore.js';
|
|
4
|
+
import '../../../feature/chat-controller/ChatResolver.js';
|
|
5
|
+
import '../../../feature/chat-controller/runChatControllers.js';
|
|
6
|
+
import { WhatsAppByWasenderChannel } from './WhatsAppByWasenderChannel.js';
|
|
7
|
+
|
|
8
|
+
function whatsAppByWasender(config) {
|
|
9
|
+
return function (target, propertyKey) {
|
|
10
|
+
const store = container.resolve(ControllerMetadataStore);
|
|
11
|
+
store.saveChannelMetadata({
|
|
12
|
+
channelConstructor: WhatsAppByWasenderChannel,
|
|
13
|
+
functionName: propertyKey.toString(),
|
|
14
|
+
controllerConstructor: target.constructor,
|
|
15
|
+
channelConfig: new WhatsAppByWasenderChannelConfig(config),
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { whatsAppByWasender };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import { injectable } from '../../../core/injection/index.js';
|
|
3
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
4
|
+
import { WhatsAppByWasenderChannelConfig } from './WhatsAppByWasenderChannelConfig.js';
|
|
5
|
+
import { WhatsAppReceiverByWasender } from './WhatsAppReceiverByWasender.js';
|
|
6
|
+
import { WhatsAppSenderByWasender } from './WhatsAppSenderByWasender.js';
|
|
7
|
+
|
|
8
|
+
let WhatsAppByWasenderChannel = class WhatsAppByWasenderChannel {
|
|
9
|
+
config;
|
|
10
|
+
sender;
|
|
11
|
+
receiver;
|
|
12
|
+
logger = new Logger('wabot:whatsapp-by-wasender-channel');
|
|
13
|
+
constructor(config, sender, receiver) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.sender = sender;
|
|
16
|
+
this.receiver = receiver;
|
|
17
|
+
}
|
|
18
|
+
listen(callback) {
|
|
19
|
+
this.receiver.listenMessage(async (message) => {
|
|
20
|
+
try {
|
|
21
|
+
await callback({
|
|
22
|
+
chatConnection: message.chatConnection,
|
|
23
|
+
message: message.message,
|
|
24
|
+
reply: async (replyMessage) => {
|
|
25
|
+
await this.sender.send({
|
|
26
|
+
from: this.config.phoneNumber,
|
|
27
|
+
to: message.chatConnection.id,
|
|
28
|
+
message: replyMessage,
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
this.logger.error('Failed to handle WhatsApp message', err);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
connect() {
|
|
39
|
+
this.receiver.connect();
|
|
40
|
+
}
|
|
41
|
+
disconnect() {
|
|
42
|
+
this.receiver.disconnect();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
WhatsAppByWasenderChannel = __decorate([
|
|
46
|
+
injectable(),
|
|
47
|
+
__metadata("design:paramtypes", [WhatsAppByWasenderChannelConfig,
|
|
48
|
+
WhatsAppSenderByWasender,
|
|
49
|
+
WhatsAppReceiverByWasender])
|
|
50
|
+
], WhatsAppByWasenderChannel);
|
|
51
|
+
|
|
52
|
+
export { WhatsAppByWasenderChannel };
|
package/dist/src/addon/chat-controller/whatsapp-by-wasender/WhatsAppByWasenderChannelConfig.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class WhatsAppByWasenderChannelConfig {
|
|
2
|
+
apiKey;
|
|
3
|
+
webhookSecret;
|
|
4
|
+
phoneNumber;
|
|
5
|
+
webhookPath;
|
|
6
|
+
retryOptions;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.apiKey = config.apiKey;
|
|
9
|
+
this.webhookSecret = config.webhookSecret;
|
|
10
|
+
this.phoneNumber = config.phoneNumber;
|
|
11
|
+
this.webhookPath = config.webhookPath ?? '/wasender/hook';
|
|
12
|
+
this.retryOptions = config.retryOptions ?? { enabled: true, maxRetries: 3 };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { WhatsAppByWasenderChannelConfig };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
3
|
+
import { injectable } from '../../../core/injection/index.js';
|
|
4
|
+
import { ExpressProvider } from '../../../feature/express/ExpressProvider.js';
|
|
5
|
+
import { createWasender } from '../../../node_modules/wasenderapi/dist/index.js';
|
|
6
|
+
import { WhatsAppByWasenderChannelConfig } from './WhatsAppByWasenderChannelConfig.js';
|
|
7
|
+
import { extractNumberFromWasenderMessageKey } from './extractNumberFromWasenderKey.js';
|
|
8
|
+
|
|
9
|
+
let WhatsAppReceiverByWasender = class WhatsAppReceiverByWasender {
|
|
10
|
+
expressProvider;
|
|
11
|
+
config;
|
|
12
|
+
wasender;
|
|
13
|
+
listener = null;
|
|
14
|
+
logger = new Logger('wabot:whatsapp-receiver-by-wasender');
|
|
15
|
+
constructor(expressProvider, config) {
|
|
16
|
+
this.expressProvider = expressProvider;
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.wasender = createWasender(config.apiKey, undefined, undefined, undefined, config.retryOptions, config.webhookSecret);
|
|
19
|
+
}
|
|
20
|
+
listenMessage(listener) {
|
|
21
|
+
this.listener = listener;
|
|
22
|
+
}
|
|
23
|
+
connect() {
|
|
24
|
+
const expressApp = this.expressProvider.getExpress();
|
|
25
|
+
expressApp.post(this.config.webhookPath, async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const rawBody = await this.getRawBody(req);
|
|
28
|
+
const event = await this.parseEvent(req, rawBody);
|
|
29
|
+
this.logger.trace(`received event ${event.event}`);
|
|
30
|
+
switch (event.event) {
|
|
31
|
+
case 'messages.received':
|
|
32
|
+
const messages = Array.isArray(event.data.messages)
|
|
33
|
+
? event.data.messages
|
|
34
|
+
: [event.data.messages];
|
|
35
|
+
await this.handleMessages(messages);
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
this.logger.warn(`unhandled event type ${event.event}`);
|
|
39
|
+
}
|
|
40
|
+
res.sendStatus(200);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
this.logger.error('Failed to handle Wasender webhook', err);
|
|
44
|
+
res.sendStatus(500);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.expressProvider.listen();
|
|
48
|
+
}
|
|
49
|
+
disconnect() {
|
|
50
|
+
// Nothing to disconnect
|
|
51
|
+
}
|
|
52
|
+
async handleMessages(messages) {
|
|
53
|
+
if (!this.listener) {
|
|
54
|
+
this.logger.warn('No listener registered, ignoring messages');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const message of messages) {
|
|
58
|
+
const from = extractNumberFromWasenderMessageKey(message.key);
|
|
59
|
+
this.logger.trace(`new message from '${from}' to '${this.config.phoneNumber}'`);
|
|
60
|
+
if (message.message.conversation) {
|
|
61
|
+
const chatConnection = {
|
|
62
|
+
chatType: 'PRIVATE',
|
|
63
|
+
channelName: 'WhatsAppByWasenderChannel',
|
|
64
|
+
id: from,
|
|
65
|
+
};
|
|
66
|
+
await this.listener({
|
|
67
|
+
chatConnection,
|
|
68
|
+
message: {
|
|
69
|
+
text: message.message.conversation,
|
|
70
|
+
senderName: message.pushName,
|
|
71
|
+
senderId: from,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async parseEvent(req, rawBody) {
|
|
78
|
+
const adapter = {
|
|
79
|
+
getHeader: (name) => req.header(name),
|
|
80
|
+
getRawBody: () => rawBody,
|
|
81
|
+
};
|
|
82
|
+
const event = await this.wasender.handleWebhookEvent(adapter);
|
|
83
|
+
return event;
|
|
84
|
+
}
|
|
85
|
+
getRawBody(req) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
let data = '';
|
|
88
|
+
req.on('data', (chunk) => {
|
|
89
|
+
data += chunk;
|
|
90
|
+
});
|
|
91
|
+
req.on('end', () => {
|
|
92
|
+
resolve(data);
|
|
93
|
+
});
|
|
94
|
+
req.on('error', (err) => {
|
|
95
|
+
reject(err);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
WhatsAppReceiverByWasender = __decorate([
|
|
101
|
+
injectable(),
|
|
102
|
+
__metadata("design:paramtypes", [ExpressProvider,
|
|
103
|
+
WhatsAppByWasenderChannelConfig])
|
|
104
|
+
], WhatsAppReceiverByWasender);
|
|
105
|
+
|
|
106
|
+
export { WhatsAppReceiverByWasender };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
3
|
+
import { injectable } from '../../../core/injection/index.js';
|
|
4
|
+
import { createWasender } from '../../../node_modules/wasenderapi/dist/index.js';
|
|
5
|
+
import { WhatsAppByWasenderChannelConfig } from './WhatsAppByWasenderChannelConfig.js';
|
|
6
|
+
|
|
7
|
+
let WhatsAppSenderByWasender = class WhatsAppSenderByWasender {
|
|
8
|
+
wasender;
|
|
9
|
+
logger = new Logger('wabot:whatsapp-sender-by-wasender');
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.wasender = createWasender(config.apiKey, undefined, undefined, undefined, config.retryOptions, undefined);
|
|
12
|
+
}
|
|
13
|
+
async send(request) {
|
|
14
|
+
try {
|
|
15
|
+
const textPayload = {
|
|
16
|
+
messageType: 'text',
|
|
17
|
+
to: `+${request.to.replace(/\D+/g, '')}`,
|
|
18
|
+
text: request.message.text ?? 'No Text',
|
|
19
|
+
};
|
|
20
|
+
const result = await this.wasender.send(textPayload);
|
|
21
|
+
this.logger.trace(`message sent from '${request.from}' to '${request.to}'`);
|
|
22
|
+
this.logger.trace(`rate limit remaining: ${result.rateLimit?.remaining}`);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
this.logger.error(`Failed to send message from '${request.from}' to '${request.to}'`, error);
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
throw new Error(error.message, { cause: error });
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
throw new Error('error sending message');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
WhatsAppSenderByWasender = __decorate([
|
|
36
|
+
injectable(),
|
|
37
|
+
__metadata("design:paramtypes", [WhatsAppByWasenderChannelConfig])
|
|
38
|
+
], WhatsAppSenderByWasender);
|
|
39
|
+
|
|
40
|
+
export { WhatsAppSenderByWasender };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function extractChatMessageText(message) {
|
|
2
|
+
const messageData = {
|
|
3
|
+
senderId: message.senderId,
|
|
4
|
+
senderName: message.senderName,
|
|
5
|
+
text: message.text,
|
|
6
|
+
object: message.object,
|
|
7
|
+
metadata: message.metadata,
|
|
8
|
+
images: message.images?.map((x) => ({ id: x.id, name: x.name })),
|
|
9
|
+
};
|
|
10
|
+
return JSON.stringify(messageData);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { extractChatMessageText };
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { __decorate } from 'tslib';
|
|
2
2
|
import { singleton } from '../../../core/injection/index.js';
|
|
3
3
|
|
|
4
|
+
function getClassHierarchy(cls) {
|
|
5
|
+
const classes = [];
|
|
6
|
+
let proto = Object.getPrototypeOf(cls.prototype);
|
|
7
|
+
while (proto && proto.constructor !== Object) {
|
|
8
|
+
classes.push(proto.constructor);
|
|
9
|
+
proto = Object.getPrototypeOf(proto);
|
|
10
|
+
}
|
|
11
|
+
return classes;
|
|
12
|
+
}
|
|
4
13
|
let RestControllerMetadataStore = class RestControllerMetadataStore {
|
|
5
14
|
endPoints = new Map();
|
|
6
15
|
middlewares = new Map();
|
|
@@ -31,16 +40,34 @@ let RestControllerMetadataStore = class RestControllerMetadataStore {
|
|
|
31
40
|
if (!controller) {
|
|
32
41
|
throw new Error(`${controllerConstructor.name} should be decorated with @restController`);
|
|
33
42
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
const hierarchy = [controllerConstructor, ...getClassHierarchy(controllerConstructor)];
|
|
44
|
+
const endPointsMap = new Map();
|
|
45
|
+
for (const cls of [...hierarchy].reverse()) {
|
|
46
|
+
const classEndPoints = this.endPoints.get(cls);
|
|
47
|
+
if (classEndPoints) {
|
|
48
|
+
for (const [name, endPoint] of classEndPoints) {
|
|
49
|
+
endPointsMap.set(name, endPoint);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!endPointsMap.size) {
|
|
37
54
|
return [];
|
|
38
55
|
}
|
|
39
|
-
return [...
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
return [...endPointsMap.values()].map((endPoint) => {
|
|
57
|
+
const middlewares = [];
|
|
58
|
+
for (const cls of [...hierarchy].reverse()) {
|
|
59
|
+
const classMiddlewares = this.middlewares.get(cls)?.get(endPoint.functionName);
|
|
60
|
+
if (classMiddlewares) {
|
|
61
|
+
middlewares.push(...classMiddlewares);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
...endPoint,
|
|
66
|
+
controllerConstructor,
|
|
67
|
+
middlewares,
|
|
68
|
+
controller,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
44
71
|
}
|
|
45
72
|
};
|
|
46
73
|
RestControllerMetadataStore = __decorate([
|