lyxe-baileys 1.0.2 → 2.0.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 +167 -1
- package/package.json +1 -1
- package/src/Lyxe/ai-stream.js +72 -0
- package/src/Lyxe/dashboard.js +301 -0
- package/src/Lyxe/group-bypass.js +87 -0
- package/src/Lyxe/humanizer.js +45 -0
- package/src/Lyxe/index.d.ts +85 -0
- package/src/Lyxe/index.js +118 -0
- package/src/Lyxe/media-tools.js +67 -0
- package/src/Lyxe/radar.js +95 -0
- package/src/Lyxe/session-manager.js +111 -0
- package/src/Lyxe/status-tools.js +75 -0
- package/src/Lyxe/telegram-bridge.js +188 -0
- package/src/Lyxe/vault.js +138 -0
- package/src/Lyxe/video-note.js +21 -0
- package/src/Lyxe/view-once.js +153 -0
- package/src/Socket/Client/proxy-agent.js +188 -0
- package/src/Socket/Client/websocket.js +60 -53
- package/src/Socket/index.js +4 -2
- package/src/index.d.ts +23 -2
- package/src/index.js +2 -1
|
@@ -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,85 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { proto } from '../WAProto/index.js';
|
|
3
|
+
import type https from 'https';
|
|
4
|
+
|
|
5
|
+
export interface ViewOnceDownloadResult {
|
|
6
|
+
buffer: Buffer;
|
|
7
|
+
mediaType: 'image' | 'video' | 'audio';
|
|
8
|
+
caption: string;
|
|
9
|
+
mimetype: string;
|
|
10
|
+
fileName: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface GroupBypassResult {
|
|
14
|
+
jid: string;
|
|
15
|
+
status: string;
|
|
16
|
+
bypassed: boolean;
|
|
17
|
+
inviteCode?: string;
|
|
18
|
+
inviteExpiration?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export declare const smartDelay: (minMs?: number, maxMs?: number) => Promise<void>;
|
|
22
|
+
export declare const simulateTyping: (sock: any, jid: string, durationMs?: number) => Promise<void>;
|
|
23
|
+
export declare const simulateRecording: (sock: any, jid: string, durationMs?: number) => Promise<void>;
|
|
24
|
+
|
|
25
|
+
export declare const downloadViewOnce: (message: any, store?: any) => Promise<ViewOnceDownloadResult>;
|
|
26
|
+
export declare const extractRevokedMessage: (store: any, revocationMsg: any) => any | null;
|
|
27
|
+
|
|
28
|
+
export declare const readStatus: (sock: any, statusMessage: any, delayMs?: number) => Promise<any>;
|
|
29
|
+
export declare const reactStatus: (sock: any, statusMessageOrKey: any, emoji?: string) => Promise<any>;
|
|
30
|
+
export declare const sendStatusMention: (sock: any, content: string | object, targetJids?: string[] | string, options?: any) => Promise<any>;
|
|
31
|
+
export declare const sendVoiceStatus: (sock: any, audio: Buffer | { url: string } | string, options?: any) => Promise<any>;
|
|
32
|
+
|
|
33
|
+
export declare const streamMessage: (sock: any, jid: string, stream: AsyncIterable<string> | Iterable<string> | string, options?: { throttleMs?: number; initialPlaceholder?: string; quoted?: any }) => Promise<any>;
|
|
34
|
+
|
|
35
|
+
export declare const sendVoiceNote: (sock: any, jid: string, audio: Buffer | { url: string } | string, quoted?: any, options?: any) => Promise<any>;
|
|
36
|
+
export declare const sendImageAsSticker: (sock: any, jid: string, sticker: Buffer | { url: string } | string, quoted?: any, options?: any) => Promise<any>;
|
|
37
|
+
export declare const sendHDMedia: (sock: any, jid: string, mediaContent: { image?: any; video?: any; caption?: string }, options?: any) => Promise<any>;
|
|
38
|
+
|
|
39
|
+
export declare const sendVideoNote: (sock: any, jid: string, video: Buffer | { url: string } | string, options?: any) => Promise<any>;
|
|
40
|
+
|
|
41
|
+
export declare const groupAddWithBypass: (sock: any, groupJid: string, participants: string[] | string, options?: { autoInvite?: boolean; caption?: string }) => Promise<GroupBypassResult[]>;
|
|
42
|
+
|
|
43
|
+
export declare const CHROME_TLS_CONFIG: any;
|
|
44
|
+
export declare const CHROME_BROWSER_HEADERS: Record<string, string>;
|
|
45
|
+
export declare const createProxyAgent: (proxy?: string | object, customTlsOptions?: any) => https.Agent;
|
|
46
|
+
|
|
47
|
+
export declare class ImmortalVault {
|
|
48
|
+
constructor(options?: { dir?: string; enabled?: boolean; maxItems?: number });
|
|
49
|
+
recordMessage(msg: any, sock: any): Promise<void>;
|
|
50
|
+
markRevoked(revokedKey: any): any | null;
|
|
51
|
+
search(query: string): any[];
|
|
52
|
+
get(msgId: string): any | null;
|
|
53
|
+
list(limit?: number): any[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export declare class LyxeDashboard {
|
|
57
|
+
constructor(sock: any, options?: { port?: number });
|
|
58
|
+
start(): void;
|
|
59
|
+
stop(): void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export declare class TelegramBridge {
|
|
63
|
+
constructor(sock: any, options: { token: string; teleChatId?: string | number; waTargetJid: string; prefixHeader?: string; autoStart?: boolean });
|
|
64
|
+
start(): void;
|
|
65
|
+
stop(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export declare class PresenceRadar {
|
|
69
|
+
constructor(sock: any);
|
|
70
|
+
track(jid: string, callback?: (ev: { jid: string; status: 'online' | 'offline'; time: number; durationMs?: number }) => void): Promise<string>;
|
|
71
|
+
getHistory(jid: string): any[];
|
|
72
|
+
getStatus(jid: string): { online: boolean; lastSeen: number } | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export declare class LyxeSessionManager {
|
|
76
|
+
constructor(options?: { baseConfig?: any; createSocketFn?: any });
|
|
77
|
+
setSocketFactory(fn: (config: any) => any): void;
|
|
78
|
+
createSession(sessionId: string, customConfig?: any): Promise<any>;
|
|
79
|
+
getSession(sessionId: string): any | null;
|
|
80
|
+
hasSession(sessionId: string): boolean;
|
|
81
|
+
deleteSession(sessionId: string): Promise<boolean>;
|
|
82
|
+
listSessions(): Array<{ id: string; createdAt: Date; hasSocket: boolean }>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export declare const bindLyxeHelpers: (sock: any, config?: any) => any;
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
import { groupAddWithBypass } from './group-bypass.js';
|
|
8
|
+
import { createProxyAgent, CHROME_TLS_CONFIG, CHROME_BROWSER_HEADERS } from '../Socket/Client/proxy-agent.js';
|
|
9
|
+
import { ImmortalVault } from './vault.js';
|
|
10
|
+
import { LyxeDashboard } from './dashboard.js';
|
|
11
|
+
import { TelegramBridge } from './telegram-bridge.js';
|
|
12
|
+
import { PresenceRadar } from './radar.js';
|
|
13
|
+
import { sendVideoNote } from './video-note.js';
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
smartDelay,
|
|
17
|
+
simulateTyping,
|
|
18
|
+
simulateRecording,
|
|
19
|
+
downloadViewOnce,
|
|
20
|
+
extractRevokedMessage,
|
|
21
|
+
readStatus,
|
|
22
|
+
reactStatus,
|
|
23
|
+
sendStatusMention,
|
|
24
|
+
sendVoiceStatus,
|
|
25
|
+
streamMessage,
|
|
26
|
+
sendVoiceNote,
|
|
27
|
+
sendImageAsSticker,
|
|
28
|
+
sendHDMedia,
|
|
29
|
+
LyxeSessionManager,
|
|
30
|
+
groupAddWithBypass,
|
|
31
|
+
createProxyAgent,
|
|
32
|
+
CHROME_TLS_CONFIG,
|
|
33
|
+
CHROME_BROWSER_HEADERS,
|
|
34
|
+
ImmortalVault,
|
|
35
|
+
LyxeDashboard,
|
|
36
|
+
TelegramBridge,
|
|
37
|
+
PresenceRadar,
|
|
38
|
+
sendVideoNote
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Menyuntikkan seluruh helper Lyxe ke dalam instance socket Baileys.
|
|
43
|
+
* @param {any} sock Socket Baileys
|
|
44
|
+
* @param {any} config Konfigurasi koneksi
|
|
45
|
+
*/
|
|
46
|
+
export const bindLyxeHelpers = (sock, config = {}) => {
|
|
47
|
+
// 1. Smart Anti-Ban & Humanizer
|
|
48
|
+
sock.smartDelay = (minMs, maxMs) => smartDelay(minMs, maxMs);
|
|
49
|
+
sock.simulateTyping = (jid, durationMs) => simulateTyping(sock, jid, durationMs);
|
|
50
|
+
sock.simulateRecording = (jid, durationMs) => simulateRecording(sock, jid, durationMs);
|
|
51
|
+
|
|
52
|
+
// 2. Anti-Delete & View Once
|
|
53
|
+
sock.downloadViewOnce = (message, store) => downloadViewOnce(message, store);
|
|
54
|
+
sock.extractRevokedMessage = (store, revocationMsg) => extractRevokedMessage(store, revocationMsg);
|
|
55
|
+
|
|
56
|
+
// 3. Status / Story Tools
|
|
57
|
+
sock.readStatus = (statusMessage, delayMs) => readStatus(sock, statusMessage, delayMs);
|
|
58
|
+
sock.reactStatus = (statusMessageOrKey, emoji) => reactStatus(sock, statusMessageOrKey, emoji);
|
|
59
|
+
sock.sendStatusMention = (content, targetJids, options) => sendStatusMention(sock, content, targetJids, options);
|
|
60
|
+
sock.sendVoiceStatus = (audio, options) => sendVoiceStatus(sock, audio, options);
|
|
61
|
+
|
|
62
|
+
// 4. Media & Audio Tools
|
|
63
|
+
sock.sendVoiceNote = (jid, audio, quoted, options) => sendVoiceNote(sock, jid, audio, quoted, options);
|
|
64
|
+
sock.sendImageAsSticker = (jid, sticker, quoted, options) => sendImageAsSticker(sock, jid, sticker, quoted, options);
|
|
65
|
+
sock.sendHDMedia = (jid, mediaContent, options) => sendHDMedia(sock, jid, mediaContent, options);
|
|
66
|
+
|
|
67
|
+
// 5. AI Streaming
|
|
68
|
+
sock.streamMessage = (jid, stream, options) => streamMessage(sock, jid, stream, options);
|
|
69
|
+
|
|
70
|
+
// 6. Group Privacy 403 Bypass Engine
|
|
71
|
+
sock.groupAddWithBypass = (jid, participants, options) => groupAddWithBypass(sock, jid, participants, options);
|
|
72
|
+
|
|
73
|
+
// 7. Video Note Bulat (Telegram-Style PTV)
|
|
74
|
+
sock.sendVideoNote = (jid, video, options) => sendVideoNote(sock, jid, video, options);
|
|
75
|
+
|
|
76
|
+
// 8. Immortal Media & Message Vault
|
|
77
|
+
sock.vault = new ImmortalVault(config.vaultOptions || {});
|
|
78
|
+
sock.ev.on('messages.upsert', async ({ messages }) => {
|
|
79
|
+
for (const msg of messages || []) {
|
|
80
|
+
sock.vault.recordMessage(msg, sock);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 9. Intelligence Presence Radar (Stalker Engine)
|
|
85
|
+
sock.radar = new PresenceRadar(sock);
|
|
86
|
+
sock.trackPresence = (jid, callback) => sock.radar.track(jid, callback);
|
|
87
|
+
|
|
88
|
+
// 10. Telegram to WhatsApp Bridge
|
|
89
|
+
sock.bridgeTelegram = (options) => new TelegramBridge(sock, options);
|
|
90
|
+
|
|
91
|
+
// 11. Built-in Web Cockpit Dashboard
|
|
92
|
+
sock.startDashboard = (port = 3000) => {
|
|
93
|
+
if (!sock.dashboard) {
|
|
94
|
+
sock.dashboard = new LyxeDashboard(sock, { port });
|
|
95
|
+
sock.dashboard.start();
|
|
96
|
+
}
|
|
97
|
+
return sock.dashboard;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (config.dashboard === true) {
|
|
101
|
+
sock.startDashboard(config.dashboardPort || 3000);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Otomatis inject opsi simulateTyping di sendMessage jika diaktifkan
|
|
105
|
+
const originalSendMessage = sock.sendMessage.bind(sock);
|
|
106
|
+
sock.sendMessage = async (jid, content, options = {}) => {
|
|
107
|
+
if (options.simulateTyping) {
|
|
108
|
+
const textLen = (typeof content === 'string' ? content : content?.text || '').length;
|
|
109
|
+
const duration = typeof options.simulateTyping === 'number'
|
|
110
|
+
? options.simulateTyping
|
|
111
|
+
: Math.min(Math.max(textLen * 35, 1200), 4000);
|
|
112
|
+
await sock.simulateTyping(jid, duration);
|
|
113
|
+
}
|
|
114
|
+
return originalSendMessage(jid, content, options);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return sock;
|
|
118
|
+
};
|
|
@@ -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,95 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WhatsApp Intelligence & Presence Radar.
|
|
5
|
+
* Memantau aktivitas Online/Offline nomor target detik demi detik tanpa perlu chat.
|
|
6
|
+
*/
|
|
7
|
+
export class PresenceRadar extends EventEmitter {
|
|
8
|
+
constructor(sock) {
|
|
9
|
+
super();
|
|
10
|
+
this.sock = sock;
|
|
11
|
+
this.targets = new Map(); // jid -> { online: boolean, lastSeen: number, history: [] }
|
|
12
|
+
this.bindEvents();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
bindEvents() {
|
|
16
|
+
if (!this.sock) return;
|
|
17
|
+
|
|
18
|
+
this.sock.ev.on('presence.update', ({ id, presences }) => {
|
|
19
|
+
const jid = id;
|
|
20
|
+
if (!this.targets.has(jid) && !Object.keys(presences || {}).some(k => this.targets.has(k))) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const [userJid, presence] of Object.entries(presences || {})) {
|
|
25
|
+
const targetKey = this.targets.has(userJid) ? userJid : jid;
|
|
26
|
+
const info = this.targets.get(targetKey);
|
|
27
|
+
if (!info) continue;
|
|
28
|
+
|
|
29
|
+
const isAvailable = presence.lastKnownPresence === 'available' || presence.lastKnownPresence === 'composing';
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
|
|
32
|
+
if (isAvailable && !info.online) {
|
|
33
|
+
info.online = true;
|
|
34
|
+
info.onlineSince = now;
|
|
35
|
+
const eventData = { jid: targetKey, status: 'online', time: now };
|
|
36
|
+
info.history.push(eventData);
|
|
37
|
+
this.emit('presence', eventData);
|
|
38
|
+
} else if (!isAvailable && info.online) {
|
|
39
|
+
const durationMs = now - (info.onlineSince || now);
|
|
40
|
+
info.online = false;
|
|
41
|
+
info.lastSeen = now;
|
|
42
|
+
const eventData = {
|
|
43
|
+
jid: targetKey,
|
|
44
|
+
status: 'offline',
|
|
45
|
+
time: now,
|
|
46
|
+
durationMs,
|
|
47
|
+
durationSec: Math.round(durationMs / 1000)
|
|
48
|
+
};
|
|
49
|
+
info.history.push(eventData);
|
|
50
|
+
this.emit('presence', eventData);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Daftarkan nomor target untuk mulai dipantau status onlinenya secara diam-diam.
|
|
58
|
+
* @param {string} jid JID target (misal: 628123456789@s.whatsapp.net)
|
|
59
|
+
* @param {Function} [callback] Callback saat target online/offline
|
|
60
|
+
*/
|
|
61
|
+
async track(jid, callback) {
|
|
62
|
+
const formatted = jid.includes('@') ? jid : `${jid.replace(/[^0-9]/g, '')}@s.whatsapp.net`;
|
|
63
|
+
if (!this.targets.has(formatted)) {
|
|
64
|
+
this.targets.set(formatted, {
|
|
65
|
+
online: false,
|
|
66
|
+
onlineSince: 0,
|
|
67
|
+
lastSeen: 0,
|
|
68
|
+
history: []
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof callback === 'function') {
|
|
73
|
+
this.on('presence', (ev) => {
|
|
74
|
+
if (ev.jid === formatted) callback(ev);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await this.sock.presenceSubscribe(formatted);
|
|
80
|
+
} catch {}
|
|
81
|
+
|
|
82
|
+
return formatted;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getHistory(jid) {
|
|
86
|
+
const formatted = jid.includes('@') ? jid : `${jid.replace(/[^0-9]/g, '')}@s.whatsapp.net`;
|
|
87
|
+
return this.targets.get(formatted)?.history || [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getStatus(jid) {
|
|
91
|
+
const formatted = jid.includes('@') ? jid : `${jid.replace(/[^0-9]/g, '')}@s.whatsapp.net`;
|
|
92
|
+
const item = this.targets.get(formatted);
|
|
93
|
+
return item ? { online: item.online, lastSeen: item.lastSeen } : null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -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
|
+
};
|