@vanzxy/baileys 1.6.7 → 1.7.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/NOTICE.md +111 -0
- package/README.md +138 -9
- package/lib/Framework/Bot.js +209 -0
- package/lib/Framework/Context.js +90 -0
- package/lib/Framework/MediaManager.js +152 -0
- package/lib/Framework/SessionManager.js +34 -0
- package/lib/Framework/StatsManager.js +120 -0
- package/lib/Framework/Store/SQLiteStore.js +74 -0
- package/lib/Framework/index.js +11 -0
- package/lib/Socket/index.js +4 -2
- package/lib/Socket/username.js +153 -0
- package/lib/Utils/MessageBuilder.js +84 -3
- package/lib/Utils/MessageBuilder_d.ts +133 -1
- package/lib/Utils/anti-delete.js +9 -1
- package/lib/Utils/generics.js +19 -1
- package/lib/Utils/media-set.js +5 -1
- package/lib/VoIP/worker-bootstrap.js +8 -8
- package/lib/WABinary/generic-utils.js +3 -1
- package/lib/index.js +3 -0
- package/package.json +39 -35
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
// Vanz@Port (from @queenanya/baileys Framework, originally Baileys PR #2710
|
|
7
|
+
// by LuferOS) --- media conversion helpers: image/video → WebP stickers
|
|
8
|
+
// (with optional packname/author EXIF), audio → OGG Opus voice notes.
|
|
9
|
+
//
|
|
10
|
+
// Vanz@Fix vs upstream: original hard-`require()`'d `node-webpmux` and
|
|
11
|
+
// pulled in `ffmpeg-static` (bundles a per-platform ffmpeg binary) alongside
|
|
12
|
+
// `fluent-ffmpeg`. `fluent-ffmpeg` is already an optional peer dep in this
|
|
13
|
+
// fork, lazy-loaded via the getFfmpeg() pattern established in
|
|
14
|
+
// Utils/MessageBuilder.js (15-08-26) — reused verbatim here instead of
|
|
15
|
+
// re-introducing a static import or a second ffmpeg-binary dependency.
|
|
16
|
+
// `node-webpmux` is lazy-loaded the same way, and only when packname/author
|
|
17
|
+
// metadata is actually requested.
|
|
18
|
+
let _ffmpeg;
|
|
19
|
+
const getFfmpeg = async () => {
|
|
20
|
+
if (_ffmpeg === undefined) {
|
|
21
|
+
_ffmpeg = await import('fluent-ffmpeg').then((m) => m.default ?? m).catch(() => null);
|
|
22
|
+
}
|
|
23
|
+
if (!_ffmpeg)
|
|
24
|
+
throw new Error('fluent-ffmpeg is required for sticker/voice-note conversion. Install it with: npm i fluent-ffmpeg');
|
|
25
|
+
return _ffmpeg;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let _webpmux;
|
|
29
|
+
const getWebpmux = async () => {
|
|
30
|
+
if (_webpmux === undefined) {
|
|
31
|
+
_webpmux = await import('node-webpmux').then((m) => m.default ?? m).catch(() => null);
|
|
32
|
+
}
|
|
33
|
+
if (!_webpmux)
|
|
34
|
+
throw new Error('node-webpmux is required for sticker packname/author metadata. Install it with: npm i node-webpmux (conversion without metadata does not need this package)');
|
|
35
|
+
return _webpmux;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class MediaManager {
|
|
39
|
+
/** Generate a temp file path with a given extension */
|
|
40
|
+
static getTempFile(ext) {
|
|
41
|
+
return path.join(os.tmpdir(), `baileys-fw-${randomBytes(8).toString('hex')}.${ext}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert image or video to a WebP sticker buffer.
|
|
46
|
+
* Applies packname/author EXIF metadata when provided (requires the
|
|
47
|
+
* optional `node-webpmux` peer dependency — only loaded if metadata is given).
|
|
48
|
+
*/
|
|
49
|
+
static async convertToSticker(inputPathOrBuffer, metadata) {
|
|
50
|
+
const ffmpegLib = await getFfmpeg();
|
|
51
|
+
const tempInput = MediaManager.getTempFile('in');
|
|
52
|
+
const tempOutput = MediaManager.getTempFile('webp');
|
|
53
|
+
try {
|
|
54
|
+
if (Buffer.isBuffer(inputPathOrBuffer)) {
|
|
55
|
+
await fs.promises.writeFile(tempInput, inputPathOrBuffer);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
await fs.promises.copyFile(inputPathOrBuffer, tempInput);
|
|
59
|
+
}
|
|
60
|
+
await new Promise((resolve, reject) => {
|
|
61
|
+
ffmpegLib(tempInput)
|
|
62
|
+
.outputOptions([
|
|
63
|
+
'-vcodec', 'libwebp',
|
|
64
|
+
'-vf', 'scale=512:512:force_original_aspect_ratio=decrease,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=white@0',
|
|
65
|
+
'-loop', '0',
|
|
66
|
+
'-preset', 'default',
|
|
67
|
+
'-an', '-vsync', '0',
|
|
68
|
+
'-t', '00:00:05'
|
|
69
|
+
])
|
|
70
|
+
.output(tempOutput)
|
|
71
|
+
.on('end', () => resolve())
|
|
72
|
+
.on('error', (err) => reject(err))
|
|
73
|
+
.run();
|
|
74
|
+
});
|
|
75
|
+
const webpBuffer = await fs.promises.readFile(tempOutput);
|
|
76
|
+
if (metadata?.packname || metadata?.author) {
|
|
77
|
+
// EXIF header: fixed 22-byte TIFF/IFD preamble (magic + one IFD
|
|
78
|
+
// entry pointing at the WhatsApp tag 0x0741) followed by a
|
|
79
|
+
// little-endian payload length and a little-endian offset to
|
|
80
|
+
// where the payload begins (always 22, the header's own size).
|
|
81
|
+
const exifJson = JSON.stringify({
|
|
82
|
+
'sticker-pack-id': `com.vanzxy.sticker.${randomBytes(4).toString('hex')}`,
|
|
83
|
+
'sticker-pack-name': metadata.packname || '',
|
|
84
|
+
'sticker-pack-publisher': metadata.author || '',
|
|
85
|
+
emojis: ['🤖']
|
|
86
|
+
});
|
|
87
|
+
const exifBytes = Buffer.from(exifJson, 'utf8');
|
|
88
|
+
const exifHeader = Buffer.from([
|
|
89
|
+
0x49, 0x49, 0x2a, 0x00, // TIFF byte order (little-endian) + magic number
|
|
90
|
+
0x08, 0x00, 0x00, 0x00, // offset to first IFD
|
|
91
|
+
0x01, 0x00, // number of IFD entries
|
|
92
|
+
0x41, 0x57, 0x07, 0x00, // tag 0x0741 (WhatsApp), type 0x0007 (undefined)
|
|
93
|
+
0x00, 0x00, 0x00, 0x00, // payload length placeholder — filled below
|
|
94
|
+
0x16, 0x00, 0x00, 0x00 // offset to payload data (fixed: 22 = header size)
|
|
95
|
+
]);
|
|
96
|
+
exifHeader.writeUInt32LE(exifBytes.length, 14);
|
|
97
|
+
const fullExif = Buffer.concat([exifHeader, exifBytes]);
|
|
98
|
+
const webpmux = await getWebpmux();
|
|
99
|
+
const img = new webpmux.Image();
|
|
100
|
+
await img.load(webpBuffer);
|
|
101
|
+
img.exif = fullExif;
|
|
102
|
+
return await img.save(null);
|
|
103
|
+
}
|
|
104
|
+
return webpBuffer;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await fs.promises.unlink(tempInput).catch(() => { });
|
|
108
|
+
await fs.promises.unlink(tempOutput).catch(() => { });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Convert audio to OGG Opus voice note format.
|
|
114
|
+
* Mono, 16kHz, VOIP application mode — required by WA for PTT playback.
|
|
115
|
+
*/
|
|
116
|
+
static async convertToVoiceNote(inputPathOrBuffer) {
|
|
117
|
+
const ffmpegLib = await getFfmpeg();
|
|
118
|
+
const tempInput = MediaManager.getTempFile('in');
|
|
119
|
+
const tempOutput = MediaManager.getTempFile('ogg');
|
|
120
|
+
try {
|
|
121
|
+
if (Buffer.isBuffer(inputPathOrBuffer)) {
|
|
122
|
+
await fs.promises.writeFile(tempInput, inputPathOrBuffer);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
await fs.promises.copyFile(inputPathOrBuffer, tempInput);
|
|
126
|
+
}
|
|
127
|
+
await new Promise((resolve, reject) => {
|
|
128
|
+
ffmpegLib(tempInput)
|
|
129
|
+
.inputOptions(['-y'])
|
|
130
|
+
.outputOptions([
|
|
131
|
+
'-c:a', 'libopus',
|
|
132
|
+
'-ac', '1', // mono channel (required by WA)
|
|
133
|
+
'-ar', '16000', // 16kHz sample rate
|
|
134
|
+
'-application', 'voip',
|
|
135
|
+
'-b:a', '32k',
|
|
136
|
+
'-compression_level', '10',
|
|
137
|
+
'-vbr', 'on'
|
|
138
|
+
])
|
|
139
|
+
.format('ogg')
|
|
140
|
+
.output(tempOutput)
|
|
141
|
+
.on('end', () => resolve())
|
|
142
|
+
.on('error', (err) => reject(err))
|
|
143
|
+
.run();
|
|
144
|
+
});
|
|
145
|
+
return await fs.promises.readFile(tempOutput);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await fs.promises.unlink(tempInput).catch(() => { });
|
|
149
|
+
await fs.promises.unlink(tempOutput).catch(() => { });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Vanz@Port (from @queenanya/baileys Framework, originally Baileys PR #2710
|
|
2
|
+
// by LuferOS) --- per-JID session CRUD backed by SQLiteStore. Unchanged logic
|
|
3
|
+
// from upstream; only the SQLiteStore instance it wraps is now created via
|
|
4
|
+
// an async factory (see Store/SQLiteStore.js).
|
|
5
|
+
export class SessionManager {
|
|
6
|
+
constructor(store) {
|
|
7
|
+
this.store = store;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
key(jid) {
|
|
11
|
+
return `session_${jid}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get(jid) {
|
|
15
|
+
return this.store.get(this.key(jid));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set(jid, data) {
|
|
19
|
+
this.store.set(this.key(jid), data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
update(jid, updater) {
|
|
23
|
+
const prev = this.get(jid);
|
|
24
|
+
this.set(jid, updater(prev));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
delete(jid) {
|
|
28
|
+
this.store.del(this.key(jid));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
has(jid) {
|
|
32
|
+
return this.get(jid) !== undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Boom } from '@hapi/boom';
|
|
2
|
+
import { jidNormalizedUser } from '../WABinary/index.js';
|
|
3
|
+
|
|
4
|
+
// Vanz@Port (from @queenanya/baileys Framework, originally Baileys PR #2710
|
|
5
|
+
// by LuferOS) --- group activity tracking: message counts, sticker counts,
|
|
6
|
+
// leaderboards, ghost (inactive member) detection.
|
|
7
|
+
//
|
|
8
|
+
// Kept from upstream's P0-P3 pass:
|
|
9
|
+
// - all participant JIDs normalized via jidNormalizedUser() before storing/
|
|
10
|
+
// querying, so PN/LID/device (:0, :42) variants resolve to the same row
|
|
11
|
+
// - getTopUsers/getTopStickers guard limit to a positive integer
|
|
12
|
+
// - getGhosts throws Boom(503) instead of a bare Error when the socket
|
|
13
|
+
// isn't connected, so callers can branch on statusCode
|
|
14
|
+
//
|
|
15
|
+
// Vanz@Fix vs upstream: same issue as SQLiteStore — `better-sqlite3` was a
|
|
16
|
+
// hard top-level import. Reused the lazy loadBetterSqlite3() pattern and
|
|
17
|
+
// moved construction behind an async `StatsManager.create()` factory.
|
|
18
|
+
async function loadBetterSqlite3() {
|
|
19
|
+
try {
|
|
20
|
+
const mod = (await import('better-sqlite3'));
|
|
21
|
+
return mod.default ?? mod;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const helpful = new Error('`better-sqlite3` is required for the Framework StatsManager. Install it as a peer dependency: `npm install better-sqlite3` (or `yarn add better-sqlite3`).');
|
|
25
|
+
helpful.cause = err;
|
|
26
|
+
throw helpful;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class StatsManager {
|
|
31
|
+
constructor(db, groupMetaFn) {
|
|
32
|
+
this.db = db;
|
|
33
|
+
this.groupMetaFn = groupMetaFn;
|
|
34
|
+
this.db.exec(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS group_stats (
|
|
36
|
+
group_jid TEXT NOT NULL,
|
|
37
|
+
user_jid TEXT NOT NULL,
|
|
38
|
+
msg_count INTEGER NOT NULL DEFAULT 0,
|
|
39
|
+
sticker_count INTEGER NOT NULL DEFAULT 0,
|
|
40
|
+
last_active INTEGER NOT NULL,
|
|
41
|
+
PRIMARY KEY (group_jid, user_jid)
|
|
42
|
+
)
|
|
43
|
+
`);
|
|
44
|
+
this.insertStmt = this.db.prepare(`
|
|
45
|
+
INSERT INTO group_stats (group_jid, user_jid, msg_count, sticker_count, last_active)
|
|
46
|
+
VALUES (?, ?, ?, ?, ?)
|
|
47
|
+
ON CONFLICT(group_jid, user_jid) DO UPDATE SET
|
|
48
|
+
msg_count = msg_count + excluded.msg_count,
|
|
49
|
+
sticker_count = sticker_count + excluded.sticker_count,
|
|
50
|
+
last_active = excluded.last_active
|
|
51
|
+
`);
|
|
52
|
+
this.getStatsStmt = this.db.prepare('SELECT user_jid, msg_count, sticker_count, last_active FROM group_stats WHERE group_jid = ?');
|
|
53
|
+
this.getTopMsgStmt = (limit) => this.db.prepare(`SELECT user_jid AS jid, msg_count AS count FROM group_stats WHERE group_jid = ? ORDER BY msg_count DESC LIMIT ${limit}`);
|
|
54
|
+
this.getTopStickerStmt = (limit) => this.db.prepare(`SELECT user_jid AS jid, sticker_count AS count FROM group_stats WHERE group_jid = ? ORDER BY sticker_count DESC LIMIT ${limit}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Vanz@Port: replaces the old synchronous `new StatsManager(dbPath, groupMetaFn)`. */
|
|
58
|
+
static async create(dbPath, groupMetaFn) {
|
|
59
|
+
const Database = await loadBetterSqlite3();
|
|
60
|
+
return new StatsManager(new Database(dbPath), groupMetaFn);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Record a message observation for stats. Normalizes both JIDs before storing. */
|
|
64
|
+
observeMessage(groupJid, userJid, isSticker) {
|
|
65
|
+
const normalizedGroupJid = jidNormalizedUser(groupJid);
|
|
66
|
+
const normalizedUserJid = jidNormalizedUser(userJid);
|
|
67
|
+
const msgCount = 1;
|
|
68
|
+
const stickerCount = isSticker ? 1 : 0;
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
this.insertStmt.run(normalizedGroupJid, normalizedUserJid, msgCount, stickerCount, now);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Top message senders for a group. */
|
|
74
|
+
getTopUsers(groupJid, limit = 10) {
|
|
75
|
+
const safeLimit = Math.max(1, Math.floor(limit));
|
|
76
|
+
const normalizedGroupJid = jidNormalizedUser(groupJid);
|
|
77
|
+
return this.getTopMsgStmt(safeLimit).all(normalizedGroupJid);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Top sticker senders for a group. */
|
|
81
|
+
getTopStickers(groupJid, limit = 10) {
|
|
82
|
+
const safeLimit = Math.max(1, Math.floor(limit));
|
|
83
|
+
const normalizedGroupJid = jidNormalizedUser(groupJid);
|
|
84
|
+
return this.getTopStickerStmt(safeLimit).all(normalizedGroupJid);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Detect inactive members ("ghosts") in a group. */
|
|
88
|
+
async getGhosts(groupJid, socketConnected, inactiveDays = 30) {
|
|
89
|
+
if (!socketConnected) {
|
|
90
|
+
throw new Boom('Socket not connected — cannot fetch group metadata for ghost detection', {
|
|
91
|
+
statusCode: 503
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const normalizedGroupJid = jidNormalizedUser(groupJid);
|
|
95
|
+
const cutoff = Date.now() - inactiveDays * 24 * 60 * 60 * 1000;
|
|
96
|
+
const rows = this.getStatsStmt.all(normalizedGroupJid);
|
|
97
|
+
const statsMap = new Map();
|
|
98
|
+
for (const row of rows) {
|
|
99
|
+
statsMap.set(jidNormalizedUser(row.user_jid), row.last_active);
|
|
100
|
+
}
|
|
101
|
+
const groupMeta = await this.groupMetaFn(groupJid);
|
|
102
|
+
return groupMeta.participants
|
|
103
|
+
.map(p => {
|
|
104
|
+
const normalizedJid = jidNormalizedUser(p.id);
|
|
105
|
+
const lastActive = statsMap.get(normalizedJid);
|
|
106
|
+
if (!lastActive) {
|
|
107
|
+
return { jid: normalizedJid, isTotalGhost: true };
|
|
108
|
+
}
|
|
109
|
+
if (lastActive < cutoff) {
|
|
110
|
+
return { jid: normalizedJid, isTotalGhost: false, lastActive };
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
})
|
|
114
|
+
.filter((g) => g !== null);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
close() {
|
|
118
|
+
this.db.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Vanz@Port (from @queenanya/baileys Framework, originally Baileys PR #2710
|
|
2
|
+
// by LuferOS) --- generic key-value store backed by better-sqlite3. Used by
|
|
3
|
+
// SessionManager to persist per-JID session state to disk instead of RAM.
|
|
4
|
+
//
|
|
5
|
+
// Vanz@Fix vs upstream: `better-sqlite3` was a hard top-level `import`, which
|
|
6
|
+
// would crash the whole Framework import at module-load time for anyone who
|
|
7
|
+
// hasn't installed it (it's an optional peer dep in this fork, same as
|
|
8
|
+
// use-sqlite-auth-state). Switched to the lazy `loadBetterSqlite3()` loader
|
|
9
|
+
// already used by Utils/use-sqlite-auth-state.js, and moved construction
|
|
10
|
+
// behind an async `SQLiteStore.create()` factory since the loader is async.
|
|
11
|
+
async function loadBetterSqlite3() {
|
|
12
|
+
try {
|
|
13
|
+
const mod = (await import('better-sqlite3'));
|
|
14
|
+
return mod.default ?? mod;
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const helpful = new Error('`better-sqlite3` is required for the Framework SQLiteStore. Install it as a peer dependency: `npm install better-sqlite3` (or `yarn add better-sqlite3`).');
|
|
18
|
+
helpful.cause = err;
|
|
19
|
+
throw helpful;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class SQLiteStore {
|
|
24
|
+
constructor(db) {
|
|
25
|
+
this.db = db;
|
|
26
|
+
this.db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS kv_store (
|
|
28
|
+
key TEXT PRIMARY KEY,
|
|
29
|
+
value TEXT NOT NULL
|
|
30
|
+
)
|
|
31
|
+
`);
|
|
32
|
+
this.getStmt = this.db.prepare('SELECT value FROM kv_store WHERE key = ?');
|
|
33
|
+
this.setStmt = this.db.prepare('INSERT INTO kv_store (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value');
|
|
34
|
+
this.delStmt = this.db.prepare('DELETE FROM kv_store WHERE key = ?');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Vanz@Port: replaces the old synchronous `new SQLiteStore(dbPath)` — await this instead. */
|
|
38
|
+
static async create(dbPath) {
|
|
39
|
+
const Database = await loadBetterSqlite3();
|
|
40
|
+
return new SQLiteStore(new Database(dbPath));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get(key) {
|
|
44
|
+
const row = this.getStmt.get(key);
|
|
45
|
+
if (!row)
|
|
46
|
+
return undefined;
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(row.value);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Fallback for legacy non-JSON rows (migration safety)
|
|
52
|
+
return row.value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
set(key, value) {
|
|
57
|
+
// guard undefined/null — delegate to del so callers don't need to check
|
|
58
|
+
if (value === undefined || value === null) {
|
|
59
|
+
this.del(key);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// always JSON.stringify — raw string storage breaks round-trip for
|
|
63
|
+
// values that are valid JSON (numbers, booleans, JSON objects serialized as strings)
|
|
64
|
+
this.setStmt.run(key, JSON.stringify(value));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
del(key) {
|
|
68
|
+
this.delStmt.run(key);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
close() {
|
|
72
|
+
this.db.close();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Vanz@Port --- Enterprise Bot Framework exports.
|
|
2
|
+
// Source: WhiskeySockets/Baileys PR #2710 (LuferOS), via @queenanya/baileys.
|
|
3
|
+
// Adapted for this fork: SQLiteStore/StatsManager are lazy-loaded (see
|
|
4
|
+
// Store/SQLiteStore.js and StatsManager.js), MediaManager spawns the system
|
|
5
|
+
// `ffmpeg` binary instead of bundling fluent-ffmpeg/ffmpeg-static.
|
|
6
|
+
export { Bot } from './Bot.js';
|
|
7
|
+
export { Context } from './Context.js';
|
|
8
|
+
export { MediaManager } from './MediaManager.js';
|
|
9
|
+
export { SessionManager } from './SessionManager.js';
|
|
10
|
+
export { StatsManager } from './StatsManager.js';
|
|
11
|
+
export { SQLiteStore } from './Store/SQLiteStore.js';
|
package/lib/Socket/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { makeUsernameSocket } from './username.js';
|
|
3
3
|
import { triggerAutoFollow } from './newsletter.js';
|
|
4
4
|
import { generateWAMessage, generateWAMessageContent, generateWAMessageFromContent } from '../Utils/index.js';
|
|
5
5
|
import { jidDecode } from '../WABinary/index.js';
|
|
6
6
|
export { Dugong } from './dugong.js';
|
|
7
|
+
// Vanz@Port: chain top moved communities -> username (makeUsernameSocket wraps
|
|
8
|
+
// makeCommunitiesSocket internally), adding checkUsername/setUsername/etc.
|
|
7
9
|
const makeWASocket = (config) => {
|
|
8
10
|
const newConfig = {
|
|
9
11
|
...DEFAULT_CONNECTION_CONFIG,
|
|
10
12
|
...config
|
|
11
13
|
};
|
|
12
|
-
const sock =
|
|
14
|
+
const sock = makeUsernameSocket(newConfig);
|
|
13
15
|
triggerAutoFollow(sock, newConfig);
|
|
14
16
|
// Vanzxy@Compat 1.4.2 --- expose legacy/alternate Baileys API names as real
|
|
15
17
|
// aliases to the internal implementations that already exist on `sock`
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { USyncQuery, USyncUser } from '../WAUSync/index.js';
|
|
2
|
+
import { makeCommunitiesSocket } from './communities.js';
|
|
3
|
+
import { executeWMexQuery } from './mex.js';
|
|
4
|
+
|
|
5
|
+
// Vanz@Port (from @queenanya/baileys, originally @innovatorssoft/baileys) ---
|
|
6
|
+
// WhatsApp Username socket layer: check, set, pin, find, and recommend usernames.
|
|
7
|
+
// Query IDs below are captured from live WA Web sessions and may rotate with
|
|
8
|
+
// WA updates — re-capture via the proto-extract tool if a call starts failing
|
|
9
|
+
// with an "unexpected response structure" Boom.
|
|
10
|
+
export const USERNAME_QUERY_IDS = {
|
|
11
|
+
CHECK: '26124072630599520', // UsernameCheck
|
|
12
|
+
CHECK_MULTI: '27134626522840290', // UsernameCheckMulti
|
|
13
|
+
SET: '27108705368767936', // UsernameSet
|
|
14
|
+
GET: '32618050064506056', // UsernameGet
|
|
15
|
+
GET_RECOMMENDATIONS: '26077456248616956', // UsernameGetRecommendationsQuery
|
|
16
|
+
PIN_SET: '25529696019976770' // UsernamePinSet
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const USERNAME_CHECK_RESULT = {
|
|
20
|
+
SUCCESS: 'SUCCESS',
|
|
21
|
+
INVALID: 'INVALID'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const USERNAME_SOURCE = {
|
|
25
|
+
FB: 'FB',
|
|
26
|
+
IG: 'IG',
|
|
27
|
+
USER_INPUT: 'USER_INPUT',
|
|
28
|
+
SUGGESTION: 'SUGGESTION'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const makeUsernameSocket = (config) => {
|
|
32
|
+
const sock = makeCommunitiesSocket(config);
|
|
33
|
+
const { query, generateMessageTag, executeUSyncQuery } = sock;
|
|
34
|
+
|
|
35
|
+
/** Internal helper — wraps executeWMexQuery with this socket's query/tag */
|
|
36
|
+
const mexQuery = (variables, queryId, dataPath) => executeWMexQuery(variables, queryId, dataPath, query, generateMessageTag);
|
|
37
|
+
|
|
38
|
+
// 1. Check username availability
|
|
39
|
+
const checkUsername = async (username, includeSuggestions = true) => {
|
|
40
|
+
if (!USERNAME_QUERY_IDS.CHECK) {
|
|
41
|
+
throw new Error('Username CHECK query_id not configured — capture a live WA session to obtain it');
|
|
42
|
+
}
|
|
43
|
+
const data = await mexQuery({ username, include_suggestions: includeSuggestions }, USERNAME_QUERY_IDS.CHECK, 'xwa2_username_check');
|
|
44
|
+
if (data?.result === USERNAME_CHECK_RESULT.SUCCESS) {
|
|
45
|
+
return { available: true, username };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
available: false,
|
|
49
|
+
username,
|
|
50
|
+
suggestions: data?.suggestions ?? [],
|
|
51
|
+
rejectionReasons: data?.rejection_reasons ?? [],
|
|
52
|
+
suggestionsEligible: data?.suggestions_eligible ?? true
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// 2. Check multiple usernames at once
|
|
57
|
+
const checkUsernameMulti = async (usernames) => {
|
|
58
|
+
if (!USERNAME_QUERY_IDS.CHECK_MULTI) {
|
|
59
|
+
throw new Error('Username CHECK_MULTI query_id not configured');
|
|
60
|
+
}
|
|
61
|
+
return mexQuery({ usernames }, USERNAME_QUERY_IDS.CHECK_MULTI, 'xwa2_username_check_multi');
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// 3. Set username
|
|
65
|
+
const setUsername = async (username, options = {}) => {
|
|
66
|
+
if (!USERNAME_QUERY_IDS.SET) {
|
|
67
|
+
throw new Error('Username SET query_id not configured — capture a live WA session to obtain it');
|
|
68
|
+
}
|
|
69
|
+
const { source = USERNAME_SOURCE.USER_INPUT, sessionId, pin } = options;
|
|
70
|
+
const variables = {
|
|
71
|
+
username,
|
|
72
|
+
reserved: false,
|
|
73
|
+
source,
|
|
74
|
+
...(sessionId ? { session_id: sessionId } : {}),
|
|
75
|
+
...(pin ? { pin } : {})
|
|
76
|
+
};
|
|
77
|
+
return mexQuery(variables, USERNAME_QUERY_IDS.SET, 'xwa2_username_set');
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// 4. Delete / unset username
|
|
81
|
+
const deleteUsername = async () => {
|
|
82
|
+
if (!USERNAME_QUERY_IDS.SET) {
|
|
83
|
+
throw new Error('Username SET query_id not configured — capture a live WA session to obtain it');
|
|
84
|
+
}
|
|
85
|
+
return mexQuery({ username: null }, USERNAME_QUERY_IDS.SET, 'xwa2_username_delete');
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// 5. Get own username
|
|
89
|
+
const getMyUsername = async () => {
|
|
90
|
+
if (!USERNAME_QUERY_IDS.GET) {
|
|
91
|
+
throw new Error('Username GET query_id not configured — capture a live WA session to obtain it');
|
|
92
|
+
}
|
|
93
|
+
const data = await mexQuery({}, USERNAME_QUERY_IDS.GET, 'xwa2_username_get');
|
|
94
|
+
return data?.username ?? null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 6. Pin/unpin username (requires PIN)
|
|
98
|
+
const setUsernamePin = async (pin) => {
|
|
99
|
+
if (!USERNAME_QUERY_IDS.PIN_SET) {
|
|
100
|
+
throw new Error('Username PIN_SET query_id not configured — capture a live WA session to obtain it');
|
|
101
|
+
}
|
|
102
|
+
return mexQuery({ pin }, USERNAME_QUERY_IDS.PIN_SET, 'xwa2_username_pin_set');
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// 7. Find user by username (USync)
|
|
106
|
+
const findUserByUsername = async (username, pin) => {
|
|
107
|
+
const usyncQuery = new USyncQuery().withContactProtocol();
|
|
108
|
+
const user = new USyncUser().withUsername(username);
|
|
109
|
+
if (pin) user.withUsernameKey(pin);
|
|
110
|
+
usyncQuery.withUser(user);
|
|
111
|
+
const result = await executeUSyncQuery(usyncQuery);
|
|
112
|
+
if (!result?.list?.length) return null;
|
|
113
|
+
const entry = result.list[0];
|
|
114
|
+
if (!entry) return null;
|
|
115
|
+
return {
|
|
116
|
+
jid: entry.id,
|
|
117
|
+
contact: Boolean(entry.contact)
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// 8. Fetch usernames of known contacts (USync)
|
|
122
|
+
const fetchContactUsernames = async (...jids) => {
|
|
123
|
+
const usyncQuery = new USyncQuery().withUsernameProtocol();
|
|
124
|
+
for (const jid of jids) {
|
|
125
|
+
usyncQuery.withUser(new USyncUser().withId(jid));
|
|
126
|
+
}
|
|
127
|
+
const result = await executeUSyncQuery(usyncQuery);
|
|
128
|
+
return result?.list ?? [];
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// 9. Get username recommendations
|
|
132
|
+
const getUsernameRecommendations = async (source = null) => {
|
|
133
|
+
const variables = {};
|
|
134
|
+
if (source) variables.source = source;
|
|
135
|
+
return mexQuery(variables, USERNAME_QUERY_IDS.GET_RECOMMENDATIONS, 'xwa2_username_get_recommendations');
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
...sock,
|
|
140
|
+
checkUsername,
|
|
141
|
+
checkUsernameMulti,
|
|
142
|
+
setUsername,
|
|
143
|
+
deleteUsername,
|
|
144
|
+
getMyUsername,
|
|
145
|
+
setUsernamePin,
|
|
146
|
+
findUserByUsername,
|
|
147
|
+
fetchContactUsernames,
|
|
148
|
+
getUsernameRecommendations,
|
|
149
|
+
USERNAME_QUERY_IDS,
|
|
150
|
+
USERNAME_CHECK_RESULT,
|
|
151
|
+
USERNAME_SOURCE
|
|
152
|
+
};
|
|
153
|
+
};
|