fluxy-bot 0.13.1 → 0.13.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fluxy-bot",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"releaseNotes": [
|
|
5
5
|
"1. react router implemented",
|
|
6
6
|
"2. new workspace design",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"@clack/prompts": "^1.1.0",
|
|
56
56
|
"@tailwindcss/vite": "^4.2.0",
|
|
57
57
|
"@vitejs/plugin-react": "^6.0.1",
|
|
58
|
+
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
|
58
59
|
"better-sqlite3": "^12.6.2",
|
|
59
60
|
"class-variance-authority": "^0.7.1",
|
|
60
61
|
"clsx": "^2.1.1",
|
|
@@ -29,13 +29,21 @@ export async function initializeChannels(
|
|
|
29
29
|
router.registerChannel(chatChannel);
|
|
30
30
|
await chatChannel.initialize(router);
|
|
31
31
|
|
|
32
|
-
// WhatsApp channel —
|
|
33
|
-
// The actual implementation is done by Fluxy as a skill.
|
|
34
|
-
// See docs/whatsapp-channel-guide.md for implementation details.
|
|
32
|
+
// WhatsApp channel — dynamic import so supervisor starts even without baileys installed
|
|
35
33
|
if (config.channels?.whatsapp?.enabled) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
try {
|
|
35
|
+
const { WhatsAppChannel } = await import('./whatsapp-channel.js');
|
|
36
|
+
const waChannel = new WhatsAppChannel((qr) => {
|
|
37
|
+
opts.broadcastFluxy('whatsapp:qr', { qr });
|
|
38
|
+
});
|
|
39
|
+
router.registerChannel(waChannel);
|
|
40
|
+
await waChannel.initialize(router);
|
|
41
|
+
} catch (err: any) {
|
|
42
|
+
log.warn(`[channels] WhatsApp initialization failed: ${err.message}`);
|
|
43
|
+
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
44
|
+
log.warn('[channels] Install baileys: npm install @whiskeysockets/baileys');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
// Telegram channel — placeholder for future implementation
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp Channel — Baileys adapter for WhatsApp Web.
|
|
3
|
+
* Receives messages, routes through ChannelRouter, sends responses back.
|
|
4
|
+
* Session persisted in ~/.fluxy/whatsapp-session/.
|
|
5
|
+
*
|
|
6
|
+
* Baileys is imported dynamically so the supervisor starts even if
|
|
7
|
+
* @whiskeysockets/baileys is not installed. Install it with:
|
|
8
|
+
* npm install @whiskeysockets/baileys
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { DATA_DIR } from '../../shared/paths.js';
|
|
13
|
+
import { log } from '../../shared/logger.js';
|
|
14
|
+
import type { Channel, ChannelRouter, OutgoingMessage } from './types.js';
|
|
15
|
+
|
|
16
|
+
const SESSION_DIR = path.join(DATA_DIR, 'whatsapp-session');
|
|
17
|
+
|
|
18
|
+
export class WhatsAppChannel implements Channel {
|
|
19
|
+
readonly type = 'whatsapp' as const;
|
|
20
|
+
private sock: any = null;
|
|
21
|
+
private router: ChannelRouter | null = null;
|
|
22
|
+
private onQR?: (qr: string) => void;
|
|
23
|
+
private reconnecting = false;
|
|
24
|
+
|
|
25
|
+
constructor(onQR?: (qr: string) => void) {
|
|
26
|
+
this.onQR = onQR;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async initialize(router: ChannelRouter): Promise<void> {
|
|
30
|
+
this.router = router;
|
|
31
|
+
await this.connect();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private async connect(): Promise<void> {
|
|
35
|
+
// Dynamic import — fails gracefully if baileys is not installed
|
|
36
|
+
const baileys = await import('@whiskeysockets/baileys');
|
|
37
|
+
const makeWASocket = baileys.default;
|
|
38
|
+
const { useMultiFileAuthState, DisconnectReason } = baileys;
|
|
39
|
+
|
|
40
|
+
const { state, saveCreds } = await useMultiFileAuthState(SESSION_DIR);
|
|
41
|
+
|
|
42
|
+
this.sock = makeWASocket({
|
|
43
|
+
auth: state,
|
|
44
|
+
printQRInTerminal: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.sock.ev.on('creds.update', saveCreds);
|
|
48
|
+
|
|
49
|
+
this.sock.ev.on('connection.update', (update: any) => {
|
|
50
|
+
const { connection, lastDisconnect, qr } = update;
|
|
51
|
+
|
|
52
|
+
if (qr && this.onQR) {
|
|
53
|
+
this.onQR(qr);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (connection === 'close') {
|
|
57
|
+
const statusCode = (lastDisconnect?.error as any)?.output?.statusCode;
|
|
58
|
+
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
|
|
59
|
+
|
|
60
|
+
log.warn(`[whatsapp] Connection closed: ${statusCode}. Reconnect: ${shouldReconnect}`);
|
|
61
|
+
|
|
62
|
+
if (shouldReconnect && !this.reconnecting) {
|
|
63
|
+
this.reconnecting = true;
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
this.reconnecting = false;
|
|
66
|
+
this.connect().catch((err) => {
|
|
67
|
+
log.warn(`[whatsapp] Reconnect failed: ${err.message}`);
|
|
68
|
+
});
|
|
69
|
+
}, 5000);
|
|
70
|
+
} else if (!shouldReconnect) {
|
|
71
|
+
log.warn('[whatsapp] Logged out — scan QR again to reconnect');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (connection === 'open') {
|
|
76
|
+
log.ok('[whatsapp] Connected to WhatsApp');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.sock.ev.on('messages.upsert', ({ messages, type }: any) => {
|
|
81
|
+
if (type !== 'notify') return;
|
|
82
|
+
|
|
83
|
+
for (const msg of messages) {
|
|
84
|
+
if (msg.key.fromMe) continue;
|
|
85
|
+
if (msg.key.remoteJid === 'status@broadcast') continue;
|
|
86
|
+
|
|
87
|
+
const remoteJid = msg.key.remoteJid!;
|
|
88
|
+
const senderId = remoteJid.replace('@s.whatsapp.net', '').replace('@g.us', '');
|
|
89
|
+
|
|
90
|
+
const content =
|
|
91
|
+
msg.message?.conversation ||
|
|
92
|
+
msg.message?.extendedTextMessage?.text ||
|
|
93
|
+
msg.message?.imageMessage?.caption ||
|
|
94
|
+
'';
|
|
95
|
+
|
|
96
|
+
if (!content) continue;
|
|
97
|
+
|
|
98
|
+
const displayName = msg.pushName || senderId;
|
|
99
|
+
const conversationKey = `whatsapp:${senderId}`;
|
|
100
|
+
|
|
101
|
+
log.info(`[whatsapp] Message from ${displayName} (${senderId}): ${content.slice(0, 80)}`);
|
|
102
|
+
|
|
103
|
+
this.router!.handleIncoming({
|
|
104
|
+
sender: {
|
|
105
|
+
channelType: 'whatsapp',
|
|
106
|
+
senderId,
|
|
107
|
+
displayName,
|
|
108
|
+
role: 'customer',
|
|
109
|
+
conversationKey,
|
|
110
|
+
},
|
|
111
|
+
content,
|
|
112
|
+
timestamp: (msg.messageTimestamp as number) * 1000 || Date.now(),
|
|
113
|
+
}).catch((err) => {
|
|
114
|
+
log.warn(`[whatsapp] Router error: ${err.message}`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async sendMessage(to: string, message: OutgoingMessage): Promise<void> {
|
|
121
|
+
if (!this.sock) {
|
|
122
|
+
log.warn('[whatsapp] Cannot send — not connected');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const jid = to.includes('@') ? to : `${to}@s.whatsapp.net`;
|
|
127
|
+
await this.sock.sendMessage(jid, { text: message.content });
|
|
128
|
+
log.info(`[whatsapp] Sent to ${to}: ${message.content.slice(0, 80)}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
ownsConversation(conversationKey: string): boolean {
|
|
132
|
+
return conversationKey.startsWith('whatsapp:');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async shutdown(): Promise<void> {
|
|
136
|
+
if (this.sock) {
|
|
137
|
+
this.sock.end(undefined);
|
|
138
|
+
this.sock = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|