lyxe-baileys 1.0.2 → 1.1.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 +93 -1
- package/package.json +1 -1
- package/src/Lyxe/ai-stream.js +72 -0
- package/src/Lyxe/humanizer.js +45 -0
- package/src/Lyxe/index.d.ts +40 -0
- package/src/Lyxe/index.js +68 -0
- package/src/Lyxe/media-tools.js +67 -0
- package/src/Lyxe/session-manager.js +111 -0
- package/src/Lyxe/status-tools.js +75 -0
- package/src/Lyxe/view-once.js +82 -0
- package/src/Socket/index.js +4 -2
- package/src/index.d.ts +16 -2
- package/src/index.js +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="https://
|
|
3
|
+
<img src="https://i.ibb.co.com/fz44Z9Nz/images.jpg" width="400" alt="Lyxe Baileys Banner" />
|
|
4
4
|
<br><br>
|
|
5
5
|
|
|
6
6
|
<h1>lyxe-baileys</h1>
|
|
@@ -113,6 +113,98 @@ Baru di sini? Ini seluruh library dalam satu pandangan. Tiap baris menaut ke bag
|
|
|
113
113
|
|
|
114
114
|
---
|
|
115
115
|
|
|
116
|
+
## 🚀 Fitur Eksklusif Lyxe Baileys
|
|
117
|
+
|
|
118
|
+
### 1. 🛡️ Smart Anti-Ban & Humanizer
|
|
119
|
+
Hindari pemblokiran akun dengan jeda acak manusiawi dan simulasi presence:
|
|
120
|
+
```javascript
|
|
121
|
+
import { smartDelay } from 'lyxe-baileys'
|
|
122
|
+
|
|
123
|
+
// Jeda acak (misal 1000 - 2500ms)
|
|
124
|
+
await sock.smartDelay(1000, 2500)
|
|
125
|
+
|
|
126
|
+
// Simulasi mengetik otomatis sebelum pesan terkirim
|
|
127
|
+
await sock.simulateTyping(jid, 2000)
|
|
128
|
+
|
|
129
|
+
// Atau langsung kirim dengan opsi simulateTyping
|
|
130
|
+
await sock.sendMessage(jid, { text: 'Halo! Ada yang bisa dibantu?' }, { simulateTyping: true })
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2. 👁️ View-Once & Anti-Delete Retriever
|
|
134
|
+
Bongkar media pesan 1x lihat (foto/video/audio) atau pulihkan pesan yang ditarik:
|
|
135
|
+
```javascript
|
|
136
|
+
// Download pesan View Once secara instan
|
|
137
|
+
const { buffer, mediaType, caption } = await sock.downloadViewOnce(m)
|
|
138
|
+
console.log(`Media ${mediaType} berhasil diunduh! Ukuran: ${buffer.length} bytes`)
|
|
139
|
+
|
|
140
|
+
// Tangkap pesan yang dihapus pengirim dari store
|
|
141
|
+
const original = sock.extractRevokedMessage(store, revocationMsg)
|
|
142
|
+
if (original) {
|
|
143
|
+
console.log('Pesan yang ditarik:', original.message)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 3. 📢 WhatsApp Status / Story Supercharger
|
|
148
|
+
Otomatis baca story, beri reaksi emoji, buat status mention, atau unggah voice note ke story:
|
|
149
|
+
```javascript
|
|
150
|
+
// Otomatis tandai status sudah dibaca dengan jeda alami
|
|
151
|
+
await sock.readStatus(statusMessage, 1500)
|
|
152
|
+
|
|
153
|
+
// Beri reaksi emoji ke story kontak
|
|
154
|
+
await sock.reactStatus(statusMessage, '❤️')
|
|
155
|
+
|
|
156
|
+
// Buat status teks/media dengan mention kontak tertentu
|
|
157
|
+
await sock.sendStatusMention('Halo semua!', ['628123456789@s.whatsapp.net'])
|
|
158
|
+
|
|
159
|
+
// Unggah Voice Note (VN) ke Status WhatsApp
|
|
160
|
+
await sock.sendVoiceStatus(audioBuffer, { backgroundColor: '#7F5AF0' })
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 4. 🎙️ Media & Audio Helpers
|
|
164
|
+
Kirim Voice Note (PTT) asli dengan animasi waveform, stiker, dan media HD:
|
|
165
|
+
```javascript
|
|
166
|
+
// Kirim rekaman suara (Voice Note PTT) lengkap dengan waveform
|
|
167
|
+
await sock.sendVoiceNote(jid, './suara.mp3', quoted)
|
|
168
|
+
|
|
169
|
+
// Kirim stiker instan
|
|
170
|
+
await sock.sendImageAsSticker(jid, stickerBuffer, quoted)
|
|
171
|
+
|
|
172
|
+
// Kirim foto/video kualitas HD resmi
|
|
173
|
+
await sock.sendHDMedia(jid, { image: { url: './foto.jpg' }, caption: 'Kualitas HD' })
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 5. 🤖 AI Streaming Message
|
|
177
|
+
Tampilkan pesan bertahap dengan animasi ketik langsung seperti ChatGPT/Gemini di WhatsApp:
|
|
178
|
+
```javascript
|
|
179
|
+
// Mengalirkan teks bertahap (AsyncIterable, generator, array, atau string)
|
|
180
|
+
await sock.streamMessage(jid, aiTextStream, {
|
|
181
|
+
throttleMs: 350,
|
|
182
|
+
initialPlaceholder: '⏳ Sedang merangkai jawaban...',
|
|
183
|
+
quoted: m
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 6. 📱 Multi-Session Manager
|
|
188
|
+
Kelola banyak nomor bot WhatsApp sekaligus dalam satu script:
|
|
189
|
+
```javascript
|
|
190
|
+
import makeWASocket, { LyxeSessionManager, useMultiFileAuthState } from 'lyxe-baileys'
|
|
191
|
+
|
|
192
|
+
const manager = new LyxeSessionManager({
|
|
193
|
+
createSocketFn: makeWASocket
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// Buat dan jalankan sesi bot 1
|
|
197
|
+
const { state, saveCreds } = await useMultiFileAuthState('./session-bot1')
|
|
198
|
+
const sock1 = await manager.createSession('bot-1', { auth: state })
|
|
199
|
+
|
|
200
|
+
// Tangkap pesan dari semua sesi sekaligus
|
|
201
|
+
manager.on('messages.upsert', ({ sessionId, upsert, sock }) => {
|
|
202
|
+
console.log(`Pesan masuk di ${sessionId}`)
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
116
208
|
## 📚 Daftar Isi
|
|
117
209
|
|
|
118
210
|
- [Ringkasan](#-ringkasan)
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { delay } from '../Utils/generics.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mengirim pesan dengan efek streaming (seperti animasi mengetik AI ChatGPT/Gemini).
|
|
5
|
+
* Pesan dikirim sekali lalu di-edit bertahap seiring potongan teks baru tiba.
|
|
6
|
+
*
|
|
7
|
+
* @param {any} sock Socket Baileys
|
|
8
|
+
* @param {string} jid Target chat
|
|
9
|
+
* @param {AsyncIterable<string>|string[]|Iterable<string>} stream Iterable teks/chunk
|
|
10
|
+
* @param {object} [options={}] Opsi
|
|
11
|
+
* @param {number} [options.throttleMs=350] Interval minimum antar edit agar tidak terkena rate-limit WA
|
|
12
|
+
* @param {string} [options.initialPlaceholder='⏳ Sedang berpikir...'] Teks awal saat proses mulai
|
|
13
|
+
* @param {any} [options.quoted] Pesan rujukan (quoted message)
|
|
14
|
+
* @returns {Promise<any>} Objek pesan terakhir yang dikirim
|
|
15
|
+
*/
|
|
16
|
+
export const streamMessage = async (sock, jid, stream, options = {}) => {
|
|
17
|
+
const throttleMs = options.throttleMs || 350;
|
|
18
|
+
const placeholder = options.initialPlaceholder || '⏳ Sedang berpikir...';
|
|
19
|
+
|
|
20
|
+
// Kirim pesan pertama
|
|
21
|
+
const sent = await sock.sendMessage(jid, { text: placeholder }, { quoted: options.quoted });
|
|
22
|
+
if (!sent?.key) return sent;
|
|
23
|
+
|
|
24
|
+
let accumulatedText = '';
|
|
25
|
+
let lastEditTime = Date.now();
|
|
26
|
+
|
|
27
|
+
const doEdit = async (text) => {
|
|
28
|
+
try {
|
|
29
|
+
await sock.sendMessage(jid, {
|
|
30
|
+
text: text,
|
|
31
|
+
edit: sent.key
|
|
32
|
+
});
|
|
33
|
+
lastEditTime = Date.now();
|
|
34
|
+
} catch {
|
|
35
|
+
// Abaikan error transien saat streaming edit
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (stream && typeof stream[Symbol.asyncIterator] === 'function') {
|
|
40
|
+
for await (const chunk of stream) {
|
|
41
|
+
accumulatedText += chunk;
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
if (now - lastEditTime >= throttleMs) {
|
|
44
|
+
await doEdit(accumulatedText);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else if (stream && typeof stream[Symbol.iterator] === 'function') {
|
|
48
|
+
for (const chunk of stream) {
|
|
49
|
+
accumulatedText += chunk;
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
if (now - lastEditTime >= throttleMs) {
|
|
52
|
+
await doEdit(accumulatedText);
|
|
53
|
+
await delay(throttleMs);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else if (typeof stream === 'string') {
|
|
57
|
+
// Jika diberikan full string, simulasikan streaming dengan memecah per kata
|
|
58
|
+
const words = stream.split(' ');
|
|
59
|
+
for (let i = 0; i < words.length; i += 3) {
|
|
60
|
+
accumulatedText = words.slice(0, i + 3).join(' ');
|
|
61
|
+
await doEdit(accumulatedText);
|
|
62
|
+
await delay(throttleMs);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Pastikan teks akhir terkirim utuh
|
|
67
|
+
if (accumulatedText && accumulatedText !== placeholder) {
|
|
68
|
+
await doEdit(accumulatedText);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return sent;
|
|
72
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { delay } from '../Utils/generics.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Jeda acak manusiawi (Smart Delay) untuk menghindari deteksi spam WhatsApp.
|
|
5
|
+
* @param {number} minMs Waktu minimum (ms), default 800
|
|
6
|
+
* @param {number} maxMs Waktu maksimum (ms), default 2000
|
|
7
|
+
*/
|
|
8
|
+
export const smartDelay = (minMs = 800, maxMs = 2000) => {
|
|
9
|
+
const duration = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
|
|
10
|
+
return delay(duration);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Simulasi mengetik manusiawi (Composing presence).
|
|
15
|
+
* @param {any} sock Socket Baileys
|
|
16
|
+
* @param {string} jid Target jid
|
|
17
|
+
* @param {number} [durationMs] Durasi dalam ms. Jika tidak diisi, otomatis 1500 - 3500ms.
|
|
18
|
+
*/
|
|
19
|
+
export const simulateTyping = async (sock, jid, durationMs) => {
|
|
20
|
+
const time = durationMs || (Math.floor(Math.random() * 2000) + 1500);
|
|
21
|
+
try {
|
|
22
|
+
await sock.sendPresenceUpdate('composing', jid);
|
|
23
|
+
await delay(time);
|
|
24
|
+
await sock.sendPresenceUpdate('paused', jid);
|
|
25
|
+
} catch {
|
|
26
|
+
// Abaikan kegagalan presence jika koneksi sedang transisi
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Simulasi merekam audio manusiawi (Recording presence).
|
|
32
|
+
* @param {any} sock Socket Baileys
|
|
33
|
+
* @param {string} jid Target jid
|
|
34
|
+
* @param {number} [durationMs] Durasi dalam ms, default 2000 - 4000ms.
|
|
35
|
+
*/
|
|
36
|
+
export const simulateRecording = async (sock, jid, durationMs) => {
|
|
37
|
+
const time = durationMs || (Math.floor(Math.random() * 2000) + 2000);
|
|
38
|
+
try {
|
|
39
|
+
await sock.sendPresenceUpdate('recording', jid);
|
|
40
|
+
await delay(time);
|
|
41
|
+
await sock.sendPresenceUpdate('paused', jid);
|
|
42
|
+
} catch {
|
|
43
|
+
// Abaikan jika presence gagal
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { proto } from '../WAProto/index.js';
|
|
3
|
+
|
|
4
|
+
export interface ViewOnceDownloadResult {
|
|
5
|
+
buffer: Buffer;
|
|
6
|
+
mediaType: 'image' | 'video' | 'audio';
|
|
7
|
+
caption: string;
|
|
8
|
+
mimetype: string;
|
|
9
|
+
fileName: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare const smartDelay: (minMs?: number, maxMs?: number) => Promise<void>;
|
|
13
|
+
export declare const simulateTyping: (sock: any, jid: string, durationMs?: number) => Promise<void>;
|
|
14
|
+
export declare const simulateRecording: (sock: any, jid: string, durationMs?: number) => Promise<void>;
|
|
15
|
+
|
|
16
|
+
export declare const downloadViewOnce: (message: any) => Promise<ViewOnceDownloadResult>;
|
|
17
|
+
export declare const extractRevokedMessage: (store: any, revocationMsg: any) => any | null;
|
|
18
|
+
|
|
19
|
+
export declare const readStatus: (sock: any, statusMessage: any, delayMs?: number) => Promise<any>;
|
|
20
|
+
export declare const reactStatus: (sock: any, statusMessageOrKey: any, emoji?: string) => Promise<any>;
|
|
21
|
+
export declare const sendStatusMention: (sock: any, content: string | object, targetJids?: string[] | string, options?: any) => Promise<any>;
|
|
22
|
+
export declare const sendVoiceStatus: (sock: any, audio: Buffer | { url: string } | string, options?: any) => Promise<any>;
|
|
23
|
+
|
|
24
|
+
export declare const streamMessage: (sock: any, jid: string, stream: AsyncIterable<string> | Iterable<string> | string, options?: { throttleMs?: number; initialPlaceholder?: string; quoted?: any }) => Promise<any>;
|
|
25
|
+
|
|
26
|
+
export declare const sendVoiceNote: (sock: any, jid: string, audio: Buffer | { url: string } | string, quoted?: any, options?: any) => Promise<any>;
|
|
27
|
+
export declare const sendImageAsSticker: (sock: any, jid: string, sticker: Buffer | { url: string } | string, quoted?: any, options?: any) => Promise<any>;
|
|
28
|
+
export declare const sendHDMedia: (sock: any, jid: string, mediaContent: { image?: any; video?: any; caption?: string }, options?: any) => Promise<any>;
|
|
29
|
+
|
|
30
|
+
export declare class LyxeSessionManager {
|
|
31
|
+
constructor(options?: { baseConfig?: any; createSocketFn?: any });
|
|
32
|
+
setSocketFactory(fn: (config: any) => any): void;
|
|
33
|
+
createSession(sessionId: string, customConfig?: any): Promise<any>;
|
|
34
|
+
getSession(sessionId: string): any | null;
|
|
35
|
+
hasSession(sessionId: string): boolean;
|
|
36
|
+
deleteSession(sessionId: string): Promise<boolean>;
|
|
37
|
+
listSessions(): Array<{ id: string; createdAt: Date; hasSocket: boolean }>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export declare const bindLyxeHelpers: (sock: any, config?: any) => any;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { smartDelay, simulateTyping, simulateRecording } from './humanizer.js';
|
|
2
|
+
import { downloadViewOnce, extractRevokedMessage } from './view-once.js';
|
|
3
|
+
import { readStatus, reactStatus, sendStatusMention, sendVoiceStatus } from './status-tools.js';
|
|
4
|
+
import { streamMessage } from './ai-stream.js';
|
|
5
|
+
import { sendVoiceNote, sendImageAsSticker, sendHDMedia } from './media-tools.js';
|
|
6
|
+
import { LyxeSessionManager } from './session-manager.js';
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
smartDelay,
|
|
10
|
+
simulateTyping,
|
|
11
|
+
simulateRecording,
|
|
12
|
+
downloadViewOnce,
|
|
13
|
+
extractRevokedMessage,
|
|
14
|
+
readStatus,
|
|
15
|
+
reactStatus,
|
|
16
|
+
sendStatusMention,
|
|
17
|
+
sendVoiceStatus,
|
|
18
|
+
streamMessage,
|
|
19
|
+
sendVoiceNote,
|
|
20
|
+
sendImageAsSticker,
|
|
21
|
+
sendHDMedia,
|
|
22
|
+
LyxeSessionManager
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Menyuntikkan seluruh helper Lyxe ke dalam instance socket Baileys.
|
|
27
|
+
* @param {any} sock Socket Baileys
|
|
28
|
+
* @param {any} config Konfigurasi koneksi
|
|
29
|
+
*/
|
|
30
|
+
export const bindLyxeHelpers = (sock, config = {}) => {
|
|
31
|
+
// 1. Smart Anti-Ban & Humanizer
|
|
32
|
+
sock.smartDelay = (minMs, maxMs) => smartDelay(minMs, maxMs);
|
|
33
|
+
sock.simulateTyping = (jid, durationMs) => simulateTyping(sock, jid, durationMs);
|
|
34
|
+
sock.simulateRecording = (jid, durationMs) => simulateRecording(sock, jid, durationMs);
|
|
35
|
+
|
|
36
|
+
// 2. Anti-Delete & View Once
|
|
37
|
+
sock.downloadViewOnce = (message) => downloadViewOnce(message);
|
|
38
|
+
sock.extractRevokedMessage = (store, revocationMsg) => extractRevokedMessage(store, revocationMsg);
|
|
39
|
+
|
|
40
|
+
// 3. Status / Story Tools
|
|
41
|
+
sock.readStatus = (statusMessage, delayMs) => readStatus(sock, statusMessage, delayMs);
|
|
42
|
+
sock.reactStatus = (statusMessageOrKey, emoji) => reactStatus(sock, statusMessageOrKey, emoji);
|
|
43
|
+
sock.sendStatusMention = (content, targetJids, options) => sendStatusMention(sock, content, targetJids, options);
|
|
44
|
+
sock.sendVoiceStatus = (audio, options) => sendVoiceStatus(sock, audio, options);
|
|
45
|
+
|
|
46
|
+
// 4. Media & Audio Tools
|
|
47
|
+
sock.sendVoiceNote = (jid, audio, quoted, options) => sendVoiceNote(sock, jid, audio, quoted, options);
|
|
48
|
+
sock.sendImageAsSticker = (jid, sticker, quoted, options) => sendImageAsSticker(sock, jid, sticker, quoted, options);
|
|
49
|
+
sock.sendHDMedia = (jid, mediaContent, options) => sendHDMedia(sock, jid, mediaContent, options);
|
|
50
|
+
|
|
51
|
+
// 5. AI Streaming
|
|
52
|
+
sock.streamMessage = (jid, stream, options) => streamMessage(sock, jid, stream, options);
|
|
53
|
+
|
|
54
|
+
// Otomatis inject opsi simulateTyping di sendMessage jika diaktifkan
|
|
55
|
+
const originalSendMessage = sock.sendMessage.bind(sock);
|
|
56
|
+
sock.sendMessage = async (jid, content, options = {}) => {
|
|
57
|
+
if (options.simulateTyping) {
|
|
58
|
+
const textLen = (typeof content === 'string' ? content : content?.text || '').length;
|
|
59
|
+
const duration = typeof options.simulateTyping === 'number'
|
|
60
|
+
? options.simulateTyping
|
|
61
|
+
: Math.min(Math.max(textLen * 35, 1200), 4000);
|
|
62
|
+
await sock.simulateTyping(jid, duration);
|
|
63
|
+
}
|
|
64
|
+
return originalSendMessage(jid, content, options);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return sock;
|
|
68
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getAudioWaveform } from '../Utils/messages-media.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mengirim pesan audio sebagai Voice Note (PTT) asli dengan animasi gelombang suara.
|
|
5
|
+
* @param {any} sock Socket Baileys
|
|
6
|
+
* @param {string} jid Target jid
|
|
7
|
+
* @param {Buffer|{ url: string }|string} audio Buffer audio atau URL file audio
|
|
8
|
+
* @param {any} [quoted] Pesan rujukan (opsional)
|
|
9
|
+
* @param {object} [options={}] Opsi tambahan
|
|
10
|
+
*/
|
|
11
|
+
export const sendVoiceNote = async (sock, jid, audio, quoted, options = {}) => {
|
|
12
|
+
const audioPayload = Buffer.isBuffer(audio) ? audio : (typeof audio === 'string' ? { url: audio } : audio);
|
|
13
|
+
|
|
14
|
+
let waveform = options.waveform;
|
|
15
|
+
if (!waveform && Buffer.isBuffer(audioPayload)) {
|
|
16
|
+
try {
|
|
17
|
+
waveform = await getAudioWaveform(audioPayload, sock.logger);
|
|
18
|
+
} catch {
|
|
19
|
+
// Fallback default waveform jika decoder tidak tersedia
|
|
20
|
+
waveform = new Uint8Array([0, 15, 30, 65, 80, 95, 100, 75, 45, 20, 10, 0]);
|
|
21
|
+
}
|
|
22
|
+
} else if (!waveform) {
|
|
23
|
+
waveform = new Uint8Array([0, 15, 30, 65, 80, 95, 100, 75, 45, 20, 10, 0]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return await sock.sendMessage(jid, {
|
|
27
|
+
audio: audioPayload,
|
|
28
|
+
mimetype: options.mimetype || 'audio/ogg; codecs=opus',
|
|
29
|
+
ptt: true,
|
|
30
|
+
waveform: waveform,
|
|
31
|
+
...options
|
|
32
|
+
}, { quoted });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Mengirim stiker WhatsApp dari media WebP atau buffer gambar/video.
|
|
37
|
+
* @param {any} sock Socket Baileys
|
|
38
|
+
* @param {string} jid Target jid
|
|
39
|
+
* @param {Buffer|{ url: string }|string} sticker Buffer sticker atau URL
|
|
40
|
+
* @param {any} [quoted] Pesan rujukan
|
|
41
|
+
* @param {object} [options={}] Opsi tambahan
|
|
42
|
+
*/
|
|
43
|
+
export const sendImageAsSticker = async (sock, jid, sticker, quoted, options = {}) => {
|
|
44
|
+
const stickerPayload = Buffer.isBuffer(sticker) ? sticker : (typeof sticker === 'string' ? { url: sticker } : sticker);
|
|
45
|
+
|
|
46
|
+
return await sock.sendMessage(jid, {
|
|
47
|
+
sticker: stickerPayload,
|
|
48
|
+
mimetype: 'image/webp',
|
|
49
|
+
...options
|
|
50
|
+
}, { quoted });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Mengirim gambar atau video dengan opsi kualitas HD resmi.
|
|
55
|
+
* @param {any} sock Socket Baileys
|
|
56
|
+
* @param {string} jid Target jid
|
|
57
|
+
* @param {{ image?: any, video?: any, caption?: string }} mediaContent Konten media
|
|
58
|
+
* @param {object} [options={}] Opsi pengiriman
|
|
59
|
+
*/
|
|
60
|
+
export const sendHDMedia = async (sock, jid, mediaContent, options = {}) => {
|
|
61
|
+
return await sock.sendMessage(jid, {
|
|
62
|
+
...mediaContent,
|
|
63
|
+
viewOnce: false
|
|
64
|
+
}, {
|
|
65
|
+
...options
|
|
66
|
+
});
|
|
67
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manajer sesi multi-nomor terpadu untuk Lyxe Baileys.
|
|
5
|
+
* Memudahkan menjalankan beberapa bot WhatsApp dalam satu proses.
|
|
6
|
+
*/
|
|
7
|
+
export class LyxeSessionManager extends EventEmitter {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
super();
|
|
10
|
+
this.sessions = new Map();
|
|
11
|
+
this.baseConfig = options.baseConfig || {};
|
|
12
|
+
this.createSocketFn = options.createSocketFn || null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Daftarkan fungsi pembuat socket jika belum disetel di constructor.
|
|
17
|
+
* @param {Function} fn Fungsi makeWASocket
|
|
18
|
+
*/
|
|
19
|
+
setSocketFactory(fn) {
|
|
20
|
+
this.createSocketFn = fn;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Menambahkan atau menginisialisasi sesi baru.
|
|
25
|
+
* @param {string} sessionId ID unik untuk sesi (misal: 'bot-1', 'cs-admin')
|
|
26
|
+
* @param {object} [customConfig={}] Konfigurasi khusus untuk sesi ini
|
|
27
|
+
*/
|
|
28
|
+
async createSession(sessionId, customConfig = {}) {
|
|
29
|
+
if (this.sessions.has(sessionId)) {
|
|
30
|
+
return this.sessions.get(sessionId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof this.createSocketFn !== 'function') {
|
|
34
|
+
throw new Error('LyxeSessionManager: Socket factory belum disetel. Jalankan manager.setSocketFactory(makeWASocket)');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const mergedConfig = {
|
|
38
|
+
...this.baseConfig,
|
|
39
|
+
...customConfig
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const sock = this.createSocketFn(mergedConfig);
|
|
43
|
+
sock.sessionId = sessionId;
|
|
44
|
+
|
|
45
|
+
// Forward event ke session manager
|
|
46
|
+
sock.ev.on('connection.update', (update) => {
|
|
47
|
+
this.emit('connection.update', { sessionId, update });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
sock.ev.on('messages.upsert', (upsert) => {
|
|
51
|
+
this.emit('messages.upsert', { sessionId, upsert, sock });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.sessions.set(sessionId, {
|
|
55
|
+
id: sessionId,
|
|
56
|
+
sock: sock,
|
|
57
|
+
config: mergedConfig,
|
|
58
|
+
createdAt: new Date()
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.emit('session.created', { sessionId, sock });
|
|
62
|
+
return sock;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Mengambil instance socket dari sesi yang sedang aktif.
|
|
67
|
+
* @param {string} sessionId
|
|
68
|
+
*/
|
|
69
|
+
getSession(sessionId) {
|
|
70
|
+
const item = this.sessions.get(sessionId);
|
|
71
|
+
return item ? item.sock : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Memeriksa apakah sesi sedang terdaftar.
|
|
76
|
+
* @param {string} sessionId
|
|
77
|
+
*/
|
|
78
|
+
hasSession(sessionId) {
|
|
79
|
+
return this.sessions.has(sessionId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Menghapus sesi dan menutup koneksinya.
|
|
84
|
+
* @param {string} sessionId
|
|
85
|
+
*/
|
|
86
|
+
async deleteSession(sessionId) {
|
|
87
|
+
const item = this.sessions.get(sessionId);
|
|
88
|
+
if (!item) return false;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
item.sock?.end?.();
|
|
92
|
+
} catch {
|
|
93
|
+
// abaikan
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.sessions.delete(sessionId);
|
|
97
|
+
this.emit('session.deleted', { sessionId });
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Mengambil daftar seluruh sesi yang aktif beserta statusnya.
|
|
103
|
+
*/
|
|
104
|
+
listSessions() {
|
|
105
|
+
return Array.from(this.sessions.values()).map(item => ({
|
|
106
|
+
id: item.id,
|
|
107
|
+
createdAt: item.createdAt,
|
|
108
|
+
hasSocket: !!item.sock
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { delay } from '../Utils/generics.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Membaca status/story WhatsApp dengan jeda alami (Auto Read Story).
|
|
5
|
+
* @param {any} sock Socket Baileys
|
|
6
|
+
* @param {any} statusMessage Objek pesan status atau key status
|
|
7
|
+
* @param {number} [delayMs=1500] Jeda sebelum menandai dibaca (ms)
|
|
8
|
+
*/
|
|
9
|
+
export const readStatus = async (sock, statusMessage, delayMs = 1500) => {
|
|
10
|
+
if (delayMs > 0) {
|
|
11
|
+
await delay(delayMs);
|
|
12
|
+
}
|
|
13
|
+
const key = statusMessage?.key || statusMessage;
|
|
14
|
+
if (!key) throw new Error('readStatus: key status tidak valid');
|
|
15
|
+
return await sock.readMessages([key]);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Memberi reaksi emoji ke status WhatsApp pengguna.
|
|
20
|
+
* @param {any} sock Socket Baileys
|
|
21
|
+
* @param {any} statusMessageOrKey Pesan status atau key status
|
|
22
|
+
* @param {string} [emoji='❤️'] Emoji reaksi
|
|
23
|
+
*/
|
|
24
|
+
export const reactStatus = async (sock, statusMessageOrKey, emoji = '❤️') => {
|
|
25
|
+
const key = statusMessageOrKey?.key || statusMessageOrKey;
|
|
26
|
+
if (!key) throw new Error('reactStatus: key status tidak valid');
|
|
27
|
+
|
|
28
|
+
const participant = key.participant || key.remoteJid;
|
|
29
|
+
return await sock.sendMessage('status@broadcast', {
|
|
30
|
+
react: {
|
|
31
|
+
text: emoji,
|
|
32
|
+
key: key
|
|
33
|
+
}
|
|
34
|
+
}, {
|
|
35
|
+
statusJidList: [participant]
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Membuat status WhatsApp baru dengan men-tag/mention nomor tertentu.
|
|
41
|
+
* @param {any} sock Socket Baileys
|
|
42
|
+
* @param {string|object} content Teks status atau objek pesan media
|
|
43
|
+
* @param {string[]|string} targetJids Daftar JID kontak yang di-mention
|
|
44
|
+
* @param {object} [options={}] Opsi tambahan sendMessage
|
|
45
|
+
*/
|
|
46
|
+
export const sendStatusMention = async (sock, content, targetJids = [], options = {}) => {
|
|
47
|
+
const msg = typeof content === 'string' ? { text: content } : content;
|
|
48
|
+
const jidList = Array.isArray(targetJids) ? targetJids : [targetJids];
|
|
49
|
+
|
|
50
|
+
return await sock.sendMessage('status@broadcast', msg, {
|
|
51
|
+
statusJidList: jidList,
|
|
52
|
+
...options
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Mengirim status rekaman suara (Voice Note Status) ke status WhatsApp.
|
|
58
|
+
* @param {any} sock Socket Baileys
|
|
59
|
+
* @param {Buffer|{ url: string }} audio Buffer audio atau URL
|
|
60
|
+
* @param {object} [options={}] Opsi (backgroundColor, statusJidList, waveform)
|
|
61
|
+
*/
|
|
62
|
+
export const sendVoiceStatus = async (sock, audio, options = {}) => {
|
|
63
|
+
const audioPayload = Buffer.isBuffer(audio) ? audio : (typeof audio === 'string' ? { url: audio } : audio);
|
|
64
|
+
|
|
65
|
+
return await sock.sendMessage('status@broadcast', {
|
|
66
|
+
audio: audioPayload,
|
|
67
|
+
mimetype: 'audio/mp4',
|
|
68
|
+
ptt: true,
|
|
69
|
+
backgroundColor: options.backgroundColor || '#7F5AF0',
|
|
70
|
+
waveform: options.waveform || new Uint8Array([0, 10, 25, 45, 70, 90, 100, 80, 50, 30, 10, 0]),
|
|
71
|
+
...options
|
|
72
|
+
}, {
|
|
73
|
+
statusJidList: options.statusJidList || []
|
|
74
|
+
});
|
|
75
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { downloadContentFromMessage, toBuffer } from '../Utils/messages-media.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mengunduh media dari pesan View Once (1x lihat) otomatis.
|
|
5
|
+
* Mendukung ViewOnce foto, video, dan audio.
|
|
6
|
+
* @param {any} message Objek pesan (baik WAMessage atau WAMessage.message)
|
|
7
|
+
* @returns {Promise<{ buffer: Buffer, mediaType: 'image'|'video'|'audio', caption: string, mimetype: string, fileName: string }>}
|
|
8
|
+
*/
|
|
9
|
+
export const downloadViewOnce = async (message) => {
|
|
10
|
+
const raw = message?.message || message;
|
|
11
|
+
if (!raw) {
|
|
12
|
+
throw new Error('downloadViewOnce: format pesan tidak valid');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const viewOnce = raw.viewOnceMessage?.message ||
|
|
16
|
+
raw.viewOnceMessageV2?.message ||
|
|
17
|
+
raw.viewOnceMessageV2Extension?.message ||
|
|
18
|
+
raw;
|
|
19
|
+
|
|
20
|
+
let mediaType = null;
|
|
21
|
+
let mediaMsg = null;
|
|
22
|
+
|
|
23
|
+
if (viewOnce.imageMessage) {
|
|
24
|
+
mediaType = 'image';
|
|
25
|
+
mediaMsg = viewOnce.imageMessage;
|
|
26
|
+
} else if (viewOnce.videoMessage) {
|
|
27
|
+
mediaType = 'video';
|
|
28
|
+
mediaMsg = viewOnce.videoMessage;
|
|
29
|
+
} else if (viewOnce.audioMessage) {
|
|
30
|
+
mediaType = 'audio';
|
|
31
|
+
mediaMsg = viewOnce.audioMessage;
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error('downloadViewOnce: media View Once tidak ditemukan di dalam pesan ini');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stream = await downloadContentFromMessage(mediaMsg, mediaType);
|
|
37
|
+
const buffer = await toBuffer(stream);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
buffer,
|
|
41
|
+
mediaType,
|
|
42
|
+
caption: mediaMsg.caption || '',
|
|
43
|
+
mimetype: mediaMsg.mimetype || '',
|
|
44
|
+
fileName: mediaMsg.fileName || (mediaType === 'image' ? 'viewonce.jpg' : mediaType === 'video' ? 'viewonce.mp4' : 'viewonce.ogg')
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mengambil kembali pesan yang ditarik/dihapus (Revoked Message) dari memory store.
|
|
50
|
+
* @param {any} store In-Memory Store Baileys
|
|
51
|
+
* @param {any} revocationMsg Pesan pembatalan (ProtocolMessage REVOKE)
|
|
52
|
+
* @returns {any|null} Pesan asli sebelum ditarik, atau null jika tidak ditemukan
|
|
53
|
+
*/
|
|
54
|
+
export const extractRevokedMessage = (store, revocationMsg) => {
|
|
55
|
+
const raw = revocationMsg?.message || revocationMsg;
|
|
56
|
+
const protocolMsg = raw?.protocolMessage;
|
|
57
|
+
// Type 0 adalah REVOKE di WhatsApp protocol
|
|
58
|
+
if (!protocolMsg || protocolMsg.type !== 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const key = protocolMsg.key;
|
|
63
|
+
if (!key) return null;
|
|
64
|
+
|
|
65
|
+
const chatId = key.remoteJid;
|
|
66
|
+
const msgId = key.id;
|
|
67
|
+
|
|
68
|
+
if (store && typeof store.loadMessage === 'function') {
|
|
69
|
+
const found = store.loadMessage(chatId, msgId);
|
|
70
|
+
if (found) return found;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (store && store.messages && store.messages[chatId]) {
|
|
74
|
+
const list = store.messages[chatId].array || store.messages[chatId];
|
|
75
|
+
if (Array.isArray(list)) {
|
|
76
|
+
const found = list.find(x => x.key?.id === msgId);
|
|
77
|
+
if (found) return found;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
};
|
package/src/Socket/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
|
|
2
2
|
import { prepareModernMessageContent } from '../Utils/modern-messages.js';
|
|
3
3
|
import { makeNewsletterStatusFetcher, makeNewsletterStatusUpdatesFetcher, makeNewsletterStatusReactionSender, makeNewsletterStatusRevokeSender, makeNewsletterStatusSender } from '../Utils/newsletter-status.js';
|
|
4
|
-
import { bindVoiceRecognition } from '../Utils/voice-recognition.js';
|
|
4
|
+
import { bindVoiceRecognition } from '../Utils/voice-recognition.js';
|
|
5
|
+
import { bindLyxeHelpers } from '../Lyxe/index.js';
|
|
5
6
|
import { makeCommunitiesSocket } from './communities.js';
|
|
6
7
|
import { makeEmailCoordinator } from './email.js';
|
|
7
8
|
import { makeMobileCoordinator } from './mobile.js';
|
|
@@ -22,7 +23,8 @@ const makeWASocket = (config) => {
|
|
|
22
23
|
sock.sendNewsletterStatusReaction = makeNewsletterStatusReactionSender(sock);
|
|
23
24
|
sock.revokeNewsletterStatus = makeNewsletterStatusRevokeSender(sock);
|
|
24
25
|
sock.getNewsletterStatuses = makeNewsletterStatusFetcher(sock);
|
|
25
|
-
sock.getNewsletterStatusUpdates = makeNewsletterStatusUpdatesFetcher(sock);
|
|
26
|
+
sock.getNewsletterStatusUpdates = makeNewsletterStatusUpdatesFetcher(sock);
|
|
27
|
+
bindLyxeHelpers(sock, newConfig);
|
|
26
28
|
if (newConfig.AnchorGuard) {
|
|
27
29
|
import('@rexxhayanasi/elaina-anchorguard')
|
|
28
30
|
.then(({ createAntiBugGuard }) => {
|
package/src/index.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ export * from "./MessageBuilder/index.js";
|
|
|
10
10
|
export * from "./MessageBuilder/extras.js";
|
|
11
11
|
export * from "./MessageBuilder/metaai.js";
|
|
12
12
|
export * from "./MessageBuilder/bot-signature.js";
|
|
13
|
-
export * from "./Voip/index.js";
|
|
13
|
+
export * from "./Voip/index.js";
|
|
14
|
+
export * from "./Lyxe/index.js";
|
|
14
15
|
import baseMakeWASocket from './Socket/index.js';
|
|
15
16
|
import type { NewsletterStatusFetchOptions, NewsletterStatusList, NewsletterStatusSendOptions, NewsletterStatusSendResult, NewsletterStatusStanzaResult, NewsletterStatusUpdatesFetchOptions } from './Utils/newsletter-status.js';
|
|
16
17
|
type BaseWASocket = ReturnType<typeof baseMakeWASocket>;
|
|
@@ -19,7 +20,20 @@ export type WASocket = BaseWASocket & {
|
|
|
19
20
|
sendNewsletterStatusReaction: (jid: string, parentServerId: string | number, reaction: string, options?: Pick<NewsletterStatusSendOptions, 'messageId' | 'ackTimeoutMs'>) => Promise<NewsletterStatusStanzaResult>;
|
|
20
21
|
revokeNewsletterStatus: (jid: string, statusId: string, options?: Pick<NewsletterStatusSendOptions, 'ackTimeoutMs'>) => Promise<NewsletterStatusStanzaResult>;
|
|
21
22
|
getNewsletterStatuses: (jid: string, options?: NewsletterStatusFetchOptions) => Promise<NewsletterStatusList>;
|
|
22
|
-
getNewsletterStatusUpdates: (jid: string, options?: NewsletterStatusUpdatesFetchOptions) => Promise<NewsletterStatusList>;
|
|
23
|
+
getNewsletterStatusUpdates: (jid: string, options?: NewsletterStatusUpdatesFetchOptions) => Promise<NewsletterStatusList>;
|
|
24
|
+
smartDelay: (minMs?: number, maxMs?: number) => Promise<void>;
|
|
25
|
+
simulateTyping: (jid: string, durationMs?: number) => Promise<void>;
|
|
26
|
+
simulateRecording: (jid: string, durationMs?: number) => Promise<void>;
|
|
27
|
+
downloadViewOnce: (message: any) => Promise<import('./Lyxe/index.js').ViewOnceDownloadResult>;
|
|
28
|
+
extractRevokedMessage: (store: any, revocationMsg: any) => any | null;
|
|
29
|
+
readStatus: (statusMessage: any, delayMs?: number) => Promise<any>;
|
|
30
|
+
reactStatus: (statusMessageOrKey: any, emoji?: string) => Promise<any>;
|
|
31
|
+
sendStatusMention: (content: string | object, targetJids?: string[] | string, options?: any) => Promise<any>;
|
|
32
|
+
sendVoiceStatus: (audio: Buffer | { url: string } | string, options?: any) => Promise<any>;
|
|
33
|
+
sendVoiceNote: (jid: string, audio: Buffer | { url: string } | string, quoted?: any, options?: any) => Promise<any>;
|
|
34
|
+
sendImageAsSticker: (jid: string, sticker: Buffer | { url: string } | string, quoted?: any, options?: any) => Promise<any>;
|
|
35
|
+
sendHDMedia: (jid: string, mediaContent: { image?: any; video?: any; caption?: string }, options?: any) => Promise<any>;
|
|
36
|
+
streamMessage: (jid: string, stream: AsyncIterable<string> | Iterable<string> | string, options?: { throttleMs?: number; initialPlaceholder?: string; quoted?: any }) => Promise<any>;
|
|
23
37
|
};
|
|
24
38
|
export declare const makeWASocket: (config: Parameters<typeof baseMakeWASocket>[0]) => WASocket;
|
|
25
39
|
export default makeWASocket;
|
package/src/index.js
CHANGED
|
@@ -18,7 +18,8 @@ export * from './MessageBuilder/index.js';
|
|
|
18
18
|
export * from './MessageBuilder/extras.js';
|
|
19
19
|
export * from './MessageBuilder/metaai.js';
|
|
20
20
|
export * from './MessageBuilder/bot-signature.js';
|
|
21
|
-
export * from './Voip/index.js';
|
|
21
|
+
export * from './Voip/index.js';
|
|
22
|
+
export * from './Lyxe/index.js';
|
|
22
23
|
|
|
23
24
|
const builderMembers = [
|
|
24
25
|
...Object.entries(messageBuilderExtras),
|