@cogitator-ai/channels 0.2.5 → 0.4.0
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/README.md +142 -40
- package/dist/assistant-config.d.ts +1 -0
- package/dist/assistant-config.d.ts.map +1 -1
- package/dist/assistant-config.js +1 -0
- package/dist/assistant-config.js.map +1 -1
- package/dist/channels/discord.d.ts +4 -0
- package/dist/channels/discord.d.ts.map +1 -1
- package/dist/channels/discord.js +123 -59
- package/dist/channels/discord.js.map +1 -1
- package/dist/channels/slack.d.ts +9 -0
- package/dist/channels/slack.d.ts.map +1 -1
- package/dist/channels/slack.js +153 -34
- package/dist/channels/slack.js.map +1 -1
- package/dist/channels/telegram.d.ts +13 -0
- package/dist/channels/telegram.d.ts.map +1 -1
- package/dist/channels/telegram.js +206 -111
- package/dist/channels/telegram.js.map +1 -1
- package/dist/channels/terminal.d.ts +10 -0
- package/dist/channels/terminal.d.ts.map +1 -1
- package/dist/channels/terminal.js +46 -10
- package/dist/channels/terminal.js.map +1 -1
- package/dist/channels/webchat.d.ts +7 -2
- package/dist/channels/webchat.d.ts.map +1 -1
- package/dist/channels/webchat.js +112 -50
- package/dist/channels/webchat.js.map +1 -1
- package/dist/channels/whatsapp.d.ts +19 -3
- package/dist/channels/whatsapp.d.ts.map +1 -1
- package/dist/channels/whatsapp.js +204 -66
- package/dist/channels/whatsapp.js.map +1 -1
- package/dist/cron.d.ts +2 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +9 -0
- package/dist/cron.js.map +1 -0
- package/dist/envelope.d.ts.map +1 -1
- package/dist/envelope.js +7 -6
- package/dist/envelope.js.map +1 -1
- package/dist/formatters/code-protect.d.ts +2 -0
- package/dist/formatters/code-protect.d.ts.map +1 -0
- package/dist/formatters/code-protect.js +12 -0
- package/dist/formatters/code-protect.js.map +1 -0
- package/dist/formatters/markdown.d.ts +1 -1
- package/dist/formatters/markdown.d.ts.map +1 -1
- package/dist/formatters/markdown.js +79 -21
- package/dist/formatters/markdown.js.map +1 -1
- package/dist/formatters/whatsapp-markdown.d.ts.map +1 -1
- package/dist/formatters/whatsapp-markdown.js +7 -21
- package/dist/formatters/whatsapp-markdown.js.map +1 -1
- package/dist/gateway.d.ts +32 -3
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +275 -174
- package/dist/gateway.js.map +1 -1
- package/dist/heartbeat.d.ts +9 -2
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +121 -84
- package/dist/heartbeat.js.map +1 -1
- package/dist/inbound-debounce.js +2 -2
- package/dist/inbound-debounce.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/media/audio-format.d.ts +3 -0
- package/dist/media/audio-format.d.ts.map +1 -0
- package/dist/media/audio-format.js +20 -0
- package/dist/media/audio-format.js.map +1 -0
- package/dist/media/deepgram-stt.d.ts +1 -1
- package/dist/media/deepgram-stt.d.ts.map +1 -1
- package/dist/media/deepgram-stt.js +4 -3
- package/dist/media/deepgram-stt.js.map +1 -1
- package/dist/media/media-processor.d.ts +12 -1
- package/dist/media/media-processor.d.ts.map +1 -1
- package/dist/media/media-processor.js +75 -18
- package/dist/media/media-processor.js.map +1 -1
- package/dist/media/whisper-api.d.ts.map +1 -1
- package/dist/media/whisper-api.js +21 -33
- package/dist/media/whisper-api.js.map +1 -1
- package/dist/media/whisper-local.d.ts +1 -0
- package/dist/media/whisper-local.d.ts.map +1 -1
- package/dist/media/whisper-local.js +106 -50
- package/dist/media/whisper-local.js.map +1 -1
- package/dist/message-queue.d.ts +1 -0
- package/dist/message-queue.d.ts.map +1 -1
- package/dist/message-queue.js +17 -10
- package/dist/message-queue.js.map +1 -1
- package/dist/middleware/auto-extract.d.ts +2 -0
- package/dist/middleware/auto-extract.d.ts.map +1 -1
- package/dist/middleware/auto-extract.js +94 -30
- package/dist/middleware/auto-extract.js.map +1 -1
- package/dist/middleware/dm-policy.d.ts +2 -1
- package/dist/middleware/dm-policy.d.ts.map +1 -1
- package/dist/middleware/dm-policy.js +26 -27
- package/dist/middleware/dm-policy.js.map +1 -1
- package/dist/middleware/owner-commands.d.ts +1 -1
- package/dist/middleware/owner-commands.d.ts.map +1 -1
- package/dist/middleware/owner-commands.js +8 -7
- package/dist/middleware/owner-commands.js.map +1 -1
- package/dist/middleware/pairing-codes.d.ts +12 -0
- package/dist/middleware/pairing-codes.d.ts.map +1 -0
- package/dist/middleware/pairing-codes.js +26 -0
- package/dist/middleware/pairing-codes.js.map +1 -0
- package/dist/middleware/pairing.d.ts +3 -3
- package/dist/middleware/pairing.d.ts.map +1 -1
- package/dist/middleware/pairing.js +18 -25
- package/dist/middleware/pairing.js.map +1 -1
- package/dist/middleware/rate-limit.d.ts +3 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +21 -6
- package/dist/middleware/rate-limit.js.map +1 -1
- package/dist/runtime-builder.d.ts +11 -0
- package/dist/runtime-builder.d.ts.map +1 -1
- package/dist/runtime-builder.js +212 -62
- package/dist/runtime-builder.js.map +1 -1
- package/dist/simple-timer-store.d.ts +6 -0
- package/dist/simple-timer-store.d.ts.map +1 -1
- package/dist/simple-timer-store.js +8 -1
- package/dist/simple-timer-store.js.map +1 -1
- package/dist/stream-buffer.d.ts +14 -7
- package/dist/stream-buffer.d.ts.map +1 -1
- package/dist/stream-buffer.js +129 -84
- package/dist/stream-buffer.js.map +1 -1
- package/dist/tools/path-guard.d.ts +4 -0
- package/dist/tools/path-guard.d.ts.map +1 -0
- package/dist/tools/path-guard.js +53 -0
- package/dist/tools/path-guard.js.map +1 -0
- package/dist/tools/self-config.d.ts +12 -2
- package/dist/tools/self-config.d.ts.map +1 -1
- package/dist/tools/self-config.js +53 -16
- package/dist/tools/self-config.js.map +1 -1
- package/package.json +27 -21
- package/dist/media/index.d.ts +0 -7
- package/dist/media/index.d.ts.map +0 -1
- package/dist/media/index.js +0 -5
- package/dist/media/index.js.map +0 -1
|
@@ -3,19 +3,24 @@ export interface WebChatConfig {
|
|
|
3
3
|
port: number;
|
|
4
4
|
path?: string;
|
|
5
5
|
auth?: (token: string) => boolean;
|
|
6
|
+
/** Maximum size of an incoming frame in bytes. Default: 1 MiB. */
|
|
7
|
+
maxPayload?: number;
|
|
6
8
|
}
|
|
7
9
|
export declare class WebChatChannel implements Channel {
|
|
8
10
|
private readonly config;
|
|
9
11
|
readonly type: ChannelType;
|
|
10
12
|
private handler;
|
|
11
13
|
private server;
|
|
12
|
-
private clients;
|
|
14
|
+
private readonly clients;
|
|
13
15
|
constructor(config: WebChatConfig);
|
|
14
16
|
onMessage(handler: (msg: ChannelMessage) => Promise<void>): void;
|
|
15
17
|
start(): Promise<void>;
|
|
18
|
+
private handleConnection;
|
|
16
19
|
stop(): Promise<void>;
|
|
17
|
-
|
|
20
|
+
private send;
|
|
21
|
+
sendText(channelId: string, text: string, options?: SendOptions): Promise<string>;
|
|
18
22
|
editText(channelId: string, messageId: string, text: string): Promise<void>;
|
|
23
|
+
deleteMessage(channelId: string, messageId: string): Promise<void>;
|
|
19
24
|
sendFile(channelId: string, file: Attachment): Promise<void>;
|
|
20
25
|
sendTyping(channelId: string): Promise<void>;
|
|
21
26
|
private onRawMessage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webchat.d.ts","sourceRoot":"","sources":["../../src/channels/webchat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webchat.d.ts","sourceRoot":"","sources":["../../src/channels/webchat.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,WAAW,EACX,UAAU,EACV,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA4BD,qBAAa,cAAe,YAAW,OAAO;IAMhC,OAAO,CAAC,QAAQ,CAAC,MAAM;IALnC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAa;IACvC,OAAO,CAAC,OAAO,CAAyD;IACxE,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;gBAE3B,MAAM,EAAE,aAAa;IAElD,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC5B,OAAO,CAAC,gBAAgB;IAiClB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,OAAO,CAAC,IAAI;IAON,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAYjF,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3E,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAa5D,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIpC,YAAY;CAiB3B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAE7D"}
|
package/dist/channels/webchat.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
2
|
const WS_OPEN = 1;
|
|
3
|
+
const DEFAULT_MAX_PAYLOAD = 1024 * 1024;
|
|
4
|
+
const POLICY_VIOLATION = 1008;
|
|
5
|
+
const GOING_AWAY = 1001;
|
|
6
|
+
function parsePayload(raw) {
|
|
7
|
+
let parsed;
|
|
8
|
+
try {
|
|
9
|
+
parsed = JSON.parse(raw);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
15
|
+
return null;
|
|
16
|
+
const record = parsed;
|
|
17
|
+
if (typeof record.text !== 'string' || record.text.trim().length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
return {
|
|
20
|
+
text: record.text,
|
|
21
|
+
...(typeof record.id === 'string' && record.id ? { id: record.id } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
3
24
|
export class WebChatChannel {
|
|
4
25
|
config;
|
|
5
26
|
type = 'webchat';
|
|
@@ -13,89 +34,130 @@ export class WebChatChannel {
|
|
|
13
34
|
this.handler = handler;
|
|
14
35
|
}
|
|
15
36
|
async start() {
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
if (this.server)
|
|
38
|
+
return;
|
|
39
|
+
let ws;
|
|
40
|
+
try {
|
|
41
|
+
ws = await import('ws');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error('ws is required for WebChat support. Install it: pnpm add ws');
|
|
45
|
+
}
|
|
46
|
+
const wss = new ws.WebSocketServer({
|
|
18
47
|
port: this.config.port,
|
|
19
48
|
path: this.config.path ?? '/ws',
|
|
49
|
+
maxPayload: this.config.maxPayload ?? DEFAULT_MAX_PAYLOAD,
|
|
50
|
+
});
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
const onError = (err) => {
|
|
53
|
+
wss.off('listening', onListening);
|
|
54
|
+
reject(err);
|
|
55
|
+
};
|
|
56
|
+
const onListening = () => {
|
|
57
|
+
wss.off('error', onError);
|
|
58
|
+
resolve();
|
|
59
|
+
};
|
|
60
|
+
wss.once('error', onError);
|
|
61
|
+
wss.once('listening', onListening);
|
|
62
|
+
});
|
|
63
|
+
wss.on('error', (err) => {
|
|
64
|
+
console.error('[webchat] Server error:', err.message);
|
|
20
65
|
});
|
|
21
|
-
wss.on('connection', (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
66
|
+
wss.on('connection', (socket, req) => this.handleConnection(socket, req));
|
|
67
|
+
this.server = wss;
|
|
68
|
+
}
|
|
69
|
+
handleConnection(socket, req) {
|
|
70
|
+
if (this.config.auth) {
|
|
71
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
72
|
+
const token = url.searchParams.get('token') ?? '';
|
|
73
|
+
if (!this.config.auth(token)) {
|
|
74
|
+
socket.send(JSON.stringify({ type: 'error', message: 'unauthorized' }));
|
|
75
|
+
socket.close(POLICY_VIOLATION, 'unauthorized');
|
|
76
|
+
return;
|
|
30
77
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
78
|
+
}
|
|
79
|
+
const clientId = `webchat_${nanoid(8)}`;
|
|
80
|
+
this.clients.set(clientId, socket);
|
|
81
|
+
socket.send(JSON.stringify({ type: 'connected', clientId }));
|
|
82
|
+
socket.on('message', (data) => {
|
|
83
|
+
const raw = Array.isArray(data)
|
|
84
|
+
? Buffer.concat(data).toString('utf-8')
|
|
85
|
+
: Buffer.from(data instanceof ArrayBuffer ? new Uint8Array(data) : data).toString('utf-8');
|
|
86
|
+
void this.onRawMessage(clientId, raw).catch((err) => {
|
|
87
|
+
console.error('[webchat] Message handler error:', err);
|
|
38
88
|
});
|
|
39
89
|
});
|
|
40
|
-
|
|
90
|
+
socket.on('close', () => {
|
|
91
|
+
this.clients.delete(clientId);
|
|
92
|
+
});
|
|
93
|
+
socket.on('error', () => {
|
|
94
|
+
this.clients.delete(clientId);
|
|
95
|
+
});
|
|
41
96
|
}
|
|
42
97
|
async stop() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
98
|
+
const server = this.server;
|
|
99
|
+
if (!server)
|
|
100
|
+
return;
|
|
101
|
+
this.server = null;
|
|
102
|
+
for (const socket of this.clients.values()) {
|
|
103
|
+
socket.close(GOING_AWAY, 'server shutting down');
|
|
46
104
|
}
|
|
47
105
|
this.clients.clear();
|
|
106
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
48
107
|
}
|
|
49
|
-
|
|
108
|
+
send(channelId, payload) {
|
|
50
109
|
const client = this.clients.get(channelId);
|
|
51
110
|
if (client?.readyState !== WS_OPEN)
|
|
52
|
-
return
|
|
111
|
+
return false;
|
|
112
|
+
client.send(JSON.stringify(payload));
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
async sendText(channelId, text, options) {
|
|
53
116
|
const messageId = `msg_${nanoid(8)}`;
|
|
54
|
-
|
|
117
|
+
const delivered = this.send(channelId, {
|
|
118
|
+
type: 'message',
|
|
119
|
+
id: messageId,
|
|
120
|
+
text,
|
|
121
|
+
...(options?.replyTo ? { replyTo: options.replyTo } : {}),
|
|
122
|
+
});
|
|
123
|
+
if (!delivered)
|
|
124
|
+
throw new Error(`WebChat client ${channelId} is not connected`);
|
|
55
125
|
return messageId;
|
|
56
126
|
}
|
|
57
127
|
async editText(channelId, messageId, text) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
128
|
+
this.send(channelId, { type: 'edit', id: messageId, text });
|
|
129
|
+
}
|
|
130
|
+
async deleteMessage(channelId, messageId) {
|
|
131
|
+
this.send(channelId, { type: 'delete', id: messageId });
|
|
62
132
|
}
|
|
63
133
|
async sendFile(channelId, file) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
134
|
+
if (!file.url && !file.buffer) {
|
|
135
|
+
throw new Error('Attachment must have either a buffer or a url');
|
|
136
|
+
}
|
|
137
|
+
this.send(channelId, {
|
|
68
138
|
type: 'file',
|
|
69
139
|
filename: file.filename,
|
|
70
140
|
mimeType: file.mimeType,
|
|
71
|
-
url: file.url,
|
|
72
|
-
|
|
141
|
+
...(file.url ? { url: file.url } : {}),
|
|
142
|
+
...(file.buffer ? { data: Buffer.from(file.buffer).toString('base64') } : {}),
|
|
143
|
+
});
|
|
73
144
|
}
|
|
74
145
|
async sendTyping(channelId) {
|
|
75
|
-
|
|
76
|
-
if (client?.readyState !== WS_OPEN)
|
|
77
|
-
return;
|
|
78
|
-
client.send(JSON.stringify({ type: 'typing' }));
|
|
146
|
+
this.send(channelId, { type: 'typing' });
|
|
79
147
|
}
|
|
80
148
|
async onRawMessage(clientId, raw) {
|
|
81
149
|
if (!this.handler)
|
|
82
150
|
return;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
parsed = JSON.parse(raw);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (!parsed.text)
|
|
151
|
+
const payload = parsePayload(raw);
|
|
152
|
+
if (!payload)
|
|
91
153
|
return;
|
|
92
154
|
const msg = {
|
|
93
|
-
id:
|
|
155
|
+
id: payload.id ?? `in_${nanoid(8)}`,
|
|
94
156
|
channelType: 'webchat',
|
|
95
157
|
channelId: clientId,
|
|
96
158
|
userId: clientId,
|
|
97
|
-
text:
|
|
98
|
-
raw:
|
|
159
|
+
text: payload.text,
|
|
160
|
+
raw: payload,
|
|
99
161
|
};
|
|
100
162
|
await this.handler(msg);
|
|
101
163
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webchat.js","sourceRoot":"","sources":["../../src/channels/webchat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webchat.js","sourceRoot":"","sources":["../../src/channels/webchat.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type { Attachment, Channel, ChannelMessage } from '@cogitator-ai/types';
|
|
1
|
+
import type { Attachment, Channel, ChannelMessage, SendOptions } from '@cogitator-ai/types';
|
|
2
2
|
export interface WhatsAppChannelConfig {
|
|
3
3
|
sessionPath?: string;
|
|
4
4
|
qrCallback?: (qr: string) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Render the pairing QR code in the terminal when no `qrCallback` is set.
|
|
7
|
+
* Requires the optional `qrcode-terminal` package. Default: true.
|
|
8
|
+
*/
|
|
5
9
|
printQr?: boolean;
|
|
6
10
|
}
|
|
7
11
|
type MessageHandler = (msg: ChannelMessage) => Promise<void>;
|
|
@@ -9,12 +13,24 @@ export declare class WhatsAppChannel implements Channel {
|
|
|
9
13
|
readonly type = "whatsapp";
|
|
10
14
|
private handler;
|
|
11
15
|
private sock;
|
|
12
|
-
private
|
|
16
|
+
private baileys;
|
|
17
|
+
private readonly config;
|
|
18
|
+
private readonly recent;
|
|
19
|
+
private stopped;
|
|
20
|
+
private reconnectAttempts;
|
|
21
|
+
private reconnectTimer;
|
|
13
22
|
constructor(config?: WhatsAppChannelConfig);
|
|
14
23
|
start(): Promise<void>;
|
|
24
|
+
private connect;
|
|
25
|
+
private handleConnectionUpdate;
|
|
26
|
+
private presentQr;
|
|
27
|
+
private extractText;
|
|
28
|
+
private extractAttachments;
|
|
29
|
+
private handleIncoming;
|
|
15
30
|
stop(): Promise<void>;
|
|
16
31
|
onMessage(handler: MessageHandler): void;
|
|
17
|
-
|
|
32
|
+
private requireSocket;
|
|
33
|
+
sendText(channelId: string, text: string, options?: SendOptions): Promise<string>;
|
|
18
34
|
editText(channelId: string, messageId: string, text: string): Promise<void>;
|
|
19
35
|
sendFile(channelId: string, file: Attachment): Promise<void>;
|
|
20
36
|
sendTyping(channelId: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whatsapp.d.ts","sourceRoot":"","sources":["../../src/channels/whatsapp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"whatsapp.d.ts","sourceRoot":"","sources":["../../src/channels/whatsapp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EAEV,OAAO,EACP,cAAc,EACd,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAuED,KAAK,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAyB7D,qBAAa,eAAgB,YAAW,OAAO;IAC7C,QAAQ,CAAC,IAAI,cAAc;IAC3B,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,IAAI,CAA8B;IAC1C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8C;gBAExD,MAAM,GAAE,qBAA0B;IAIxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBd,OAAO;IA4BrB,OAAO,CAAC,sBAAsB;YA8BhB,SAAS;IAsBvB,OAAO,CAAC,WAAW;YAaL,kBAAkB;YAuBlB,cAAc;IAkCtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3B,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAIxC,OAAO,CAAC,aAAa;IAKf,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAajF,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ3E,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC5D,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAInD;AAED,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAEvE"}
|
|
@@ -1,109 +1,247 @@
|
|
|
1
1
|
import { markdownToWhatsApp } from '../formatters/whatsapp-markdown.js';
|
|
2
|
+
const MAX_RECENT_MESSAGES = 200;
|
|
3
|
+
const RECONNECT_BASE_MS = 1000;
|
|
4
|
+
const RECONNECT_MAX_MS = 60_000;
|
|
5
|
+
const MEDIA_KINDS = [
|
|
6
|
+
{ key: 'imageMessage', type: 'image', fallbackMime: 'image/jpeg' },
|
|
7
|
+
{ key: 'audioMessage', type: 'audio', fallbackMime: 'audio/ogg' },
|
|
8
|
+
{ key: 'videoMessage', type: 'video', fallbackMime: 'video/mp4' },
|
|
9
|
+
{ key: 'documentMessage', type: 'file', fallbackMime: 'application/octet-stream' },
|
|
10
|
+
];
|
|
11
|
+
async function loadOptional(specifier) {
|
|
12
|
+
try {
|
|
13
|
+
return (await import(specifier));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
2
19
|
export class WhatsAppChannel {
|
|
3
20
|
type = 'whatsapp';
|
|
4
21
|
handler = null;
|
|
5
22
|
sock = null;
|
|
23
|
+
baileys = null;
|
|
6
24
|
config;
|
|
25
|
+
recent = new Map();
|
|
26
|
+
stopped = true;
|
|
27
|
+
reconnectAttempts = 0;
|
|
28
|
+
reconnectTimer = null;
|
|
7
29
|
constructor(config = {}) {
|
|
8
30
|
this.config = config;
|
|
9
31
|
}
|
|
10
32
|
async start() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
if (this.sock)
|
|
34
|
+
return;
|
|
35
|
+
this.stopped = false;
|
|
36
|
+
const baileys = await loadOptional('@whiskeysockets/baileys');
|
|
37
|
+
if (!baileys) {
|
|
38
|
+
this.stopped = true;
|
|
17
39
|
throw new Error('@whiskeysockets/baileys is required for WhatsApp. Install it: pnpm add @whiskeysockets/baileys');
|
|
18
40
|
}
|
|
41
|
+
this.baileys = baileys;
|
|
42
|
+
await this.connect(baileys);
|
|
43
|
+
}
|
|
44
|
+
async connect(baileys) {
|
|
19
45
|
const sessionPath = this.config.sessionPath ?? '.cogitator/whatsapp-session';
|
|
20
46
|
const { state, saveCreds } = await baileys.useMultiFileAuthState(sessionPath);
|
|
21
|
-
const sock = baileys.default({
|
|
22
|
-
auth: state,
|
|
23
|
-
printQRInTerminal: this.config.printQr ?? true,
|
|
24
|
-
});
|
|
47
|
+
const sock = baileys.default({ auth: state });
|
|
25
48
|
this.sock = sock;
|
|
26
|
-
sock.ev.on('creds.update',
|
|
49
|
+
sock.ev.on('creds.update', () => {
|
|
50
|
+
void saveCreds().catch((err) => {
|
|
51
|
+
console.error('[whatsapp] Failed to persist credentials:', err);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
27
54
|
sock.ev.on('connection.update', (update) => {
|
|
28
|
-
|
|
29
|
-
if (qr && this.config.qrCallback) {
|
|
30
|
-
this.config.qrCallback(qr);
|
|
31
|
-
}
|
|
32
|
-
if (connection === 'close') {
|
|
33
|
-
const { lastDisconnect } = update;
|
|
34
|
-
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
35
|
-
const loggedOut = statusCode === baileys.DisconnectReason.loggedOut;
|
|
36
|
-
if (!loggedOut) {
|
|
37
|
-
void this.start();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
55
|
+
this.handleConnectionUpdate(sock, baileys, update);
|
|
40
56
|
});
|
|
41
57
|
sock.ev.on('messages.upsert', (upsert) => {
|
|
42
58
|
const { messages, type } = upsert;
|
|
43
59
|
if (type !== 'notify')
|
|
44
60
|
return;
|
|
45
61
|
for (const msg of messages) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
void this.handleIncoming(msg).catch((err) => {
|
|
63
|
+
console.error('[whatsapp] Message handler error:', err);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
handleConnectionUpdate(sock, baileys, update) {
|
|
69
|
+
if (update.qr)
|
|
70
|
+
void this.presentQr(update.qr);
|
|
71
|
+
if (update.connection === 'open') {
|
|
72
|
+
this.reconnectAttempts = 0;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (update.connection !== 'close' || this.sock !== sock)
|
|
76
|
+
return;
|
|
77
|
+
this.sock = null;
|
|
78
|
+
const statusCode = update.lastDisconnect?.error?.output?.statusCode;
|
|
79
|
+
const loggedOut = statusCode === baileys.DisconnectReason.loggedOut;
|
|
80
|
+
if (this.stopped || loggedOut)
|
|
81
|
+
return;
|
|
82
|
+
const delay = Math.min(RECONNECT_BASE_MS * 2 ** this.reconnectAttempts, RECONNECT_MAX_MS);
|
|
83
|
+
this.reconnectAttempts++;
|
|
84
|
+
this.reconnectTimer = setTimeout(() => {
|
|
85
|
+
this.reconnectTimer = null;
|
|
86
|
+
if (this.stopped)
|
|
87
|
+
return;
|
|
88
|
+
void this.connect(baileys).catch((err) => {
|
|
89
|
+
console.error('[whatsapp] Reconnect failed:', err);
|
|
90
|
+
});
|
|
91
|
+
}, delay);
|
|
92
|
+
}
|
|
93
|
+
async presentQr(qr) {
|
|
94
|
+
if (this.config.qrCallback) {
|
|
95
|
+
this.config.qrCallback(qr);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (this.config.printQr === false)
|
|
99
|
+
return;
|
|
100
|
+
const qrTerminal = await loadOptional('qrcode-terminal');
|
|
101
|
+
const generator = qrTerminal?.default ?? qrTerminal;
|
|
102
|
+
if (generator?.generate) {
|
|
103
|
+
generator.generate(qr, { small: true });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.warn('[whatsapp] Scan required but qrcode-terminal is not installed (pnpm add qrcode-terminal). ' +
|
|
107
|
+
'Pass qrCallback to render the QR code yourself. Raw QR payload:', qr);
|
|
108
|
+
}
|
|
109
|
+
extractText(msg) {
|
|
110
|
+
const m = msg.message;
|
|
111
|
+
if (!m)
|
|
112
|
+
return '';
|
|
113
|
+
return (m.conversation ??
|
|
114
|
+
m.extendedTextMessage?.text ??
|
|
115
|
+
m.imageMessage?.caption ??
|
|
116
|
+
m.videoMessage?.caption ??
|
|
117
|
+
m.documentMessage?.caption ??
|
|
118
|
+
'');
|
|
119
|
+
}
|
|
120
|
+
async extractAttachments(msg) {
|
|
121
|
+
const m = msg.message;
|
|
122
|
+
if (!m || !this.baileys)
|
|
123
|
+
return [];
|
|
124
|
+
const attachments = [];
|
|
125
|
+
for (const kind of MEDIA_KINDS) {
|
|
126
|
+
const media = m[kind.key];
|
|
127
|
+
if (!media)
|
|
128
|
+
continue;
|
|
129
|
+
try {
|
|
130
|
+
const buffer = await this.baileys.downloadMediaMessage(msg, 'buffer', {});
|
|
131
|
+
attachments.push({
|
|
132
|
+
type: kind.type,
|
|
133
|
+
mimeType: media.mimetype?.split(';')[0]?.trim() || kind.fallbackMime,
|
|
134
|
+
buffer,
|
|
135
|
+
...(media.fileName ? { filename: media.fileName } : {}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error('[whatsapp] Failed to download media:', err);
|
|
69
140
|
}
|
|
141
|
+
}
|
|
142
|
+
return attachments;
|
|
143
|
+
}
|
|
144
|
+
async handleIncoming(msg) {
|
|
145
|
+
if (msg.key.fromMe || !msg.message || !this.handler)
|
|
146
|
+
return;
|
|
147
|
+
const jid = msg.key.remoteJid ?? '';
|
|
148
|
+
const id = msg.key.id ?? '';
|
|
149
|
+
if (!jid || !id)
|
|
150
|
+
return;
|
|
151
|
+
const text = this.extractText(msg);
|
|
152
|
+
const attachments = await this.extractAttachments(msg);
|
|
153
|
+
if (!text && attachments.length === 0)
|
|
154
|
+
return;
|
|
155
|
+
this.recent.set(id, msg);
|
|
156
|
+
if (this.recent.size > MAX_RECENT_MESSAGES) {
|
|
157
|
+
const oldest = this.recent.keys().next().value;
|
|
158
|
+
if (oldest !== undefined)
|
|
159
|
+
this.recent.delete(oldest);
|
|
160
|
+
}
|
|
161
|
+
const sender = msg.key.participant ?? jid;
|
|
162
|
+
const userId = sender.split('@')[0];
|
|
163
|
+
const isGroup = jid.endsWith('@g.us');
|
|
164
|
+
await this.handler({
|
|
165
|
+
id,
|
|
166
|
+
channelType: 'whatsapp',
|
|
167
|
+
channelId: jid,
|
|
168
|
+
userId,
|
|
169
|
+
userName: msg.pushName ?? userId,
|
|
170
|
+
text,
|
|
171
|
+
raw: msg,
|
|
172
|
+
...(isGroup ? { groupId: jid } : {}),
|
|
173
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
70
174
|
});
|
|
71
175
|
}
|
|
72
176
|
async stop() {
|
|
177
|
+
this.stopped = true;
|
|
178
|
+
if (this.reconnectTimer) {
|
|
179
|
+
clearTimeout(this.reconnectTimer);
|
|
180
|
+
this.reconnectTimer = null;
|
|
181
|
+
}
|
|
73
182
|
if (this.sock) {
|
|
74
|
-
this.sock
|
|
183
|
+
const sock = this.sock;
|
|
75
184
|
this.sock = null;
|
|
185
|
+
sock.end();
|
|
76
186
|
}
|
|
187
|
+
this.recent.clear();
|
|
77
188
|
}
|
|
78
189
|
onMessage(handler) {
|
|
79
190
|
this.handler = handler;
|
|
80
191
|
}
|
|
81
|
-
|
|
192
|
+
requireSocket() {
|
|
82
193
|
if (!this.sock)
|
|
83
194
|
throw new Error('WhatsApp not connected');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
195
|
+
return this.sock;
|
|
196
|
+
}
|
|
197
|
+
async sendText(channelId, text, options) {
|
|
198
|
+
const sock = this.requireSocket();
|
|
199
|
+
const quoted = options?.replyTo ? this.recent.get(options.replyTo) : undefined;
|
|
200
|
+
const sent = await sock.sendMessage(channelId, { text: markdownToWhatsApp(text) }, quoted ? { quoted } : undefined);
|
|
201
|
+
const id = sent?.key.id;
|
|
202
|
+
if (!id)
|
|
203
|
+
throw new Error('WhatsApp did not return a message id');
|
|
204
|
+
return id;
|
|
87
205
|
}
|
|
88
206
|
async editText(channelId, messageId, text) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
text: waText,
|
|
94
|
-
edit: { key: { remoteJid: channelId, id: messageId } },
|
|
207
|
+
const sock = this.requireSocket();
|
|
208
|
+
await sock.sendMessage(channelId, {
|
|
209
|
+
text: markdownToWhatsApp(text),
|
|
210
|
+
edit: { remoteJid: channelId, id: messageId, fromMe: true },
|
|
95
211
|
});
|
|
96
212
|
}
|
|
97
213
|
async sendFile(channelId, file) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
214
|
+
const sock = this.requireSocket();
|
|
215
|
+
let buffer;
|
|
216
|
+
if (file.buffer) {
|
|
217
|
+
buffer = Buffer.from(file.buffer);
|
|
218
|
+
}
|
|
219
|
+
else if (file.url) {
|
|
220
|
+
const res = await fetch(file.url);
|
|
221
|
+
if (!res.ok)
|
|
222
|
+
throw new Error(`Failed to download ${file.url}: HTTP ${res.status}`);
|
|
223
|
+
buffer = Buffer.from(await res.arrayBuffer());
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
throw new Error('Attachment must have either a buffer or a url');
|
|
227
|
+
}
|
|
228
|
+
switch (file.type) {
|
|
229
|
+
case 'image':
|
|
230
|
+
await sock.sendMessage(channelId, { image: buffer, mimetype: file.mimeType });
|
|
231
|
+
return;
|
|
232
|
+
case 'audio':
|
|
233
|
+
await sock.sendMessage(channelId, { audio: buffer, mimetype: file.mimeType });
|
|
234
|
+
return;
|
|
235
|
+
case 'video':
|
|
236
|
+
await sock.sendMessage(channelId, { video: buffer, mimetype: file.mimeType });
|
|
237
|
+
return;
|
|
238
|
+
default:
|
|
239
|
+
await sock.sendMessage(channelId, {
|
|
240
|
+
document: buffer,
|
|
241
|
+
mimetype: file.mimeType,
|
|
242
|
+
fileName: file.filename ?? 'file',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
107
245
|
}
|
|
108
246
|
async sendTyping(channelId) {
|
|
109
247
|
if (!this.sock)
|