cli-whatsapp 0.1.1 → 0.1.3
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/bin/wa.js +4 -3
- package/dist/cli/commands/auth.js +220 -0
- package/dist/cli/commands/export.js +51 -0
- package/dist/cli/commands/filepicker.js +232 -0
- package/dist/cli/commands/group.js +139 -0
- package/dist/cli/commands/manage.js +41 -0
- package/dist/cli/commands/media.js +105 -0
- package/dist/cli/commands/misc.js +112 -0
- package/dist/cli/commands/read.js +79 -0
- package/dist/cli/commands/send.js +137 -0
- package/dist/cli/commands/shell.js +655 -0
- package/dist/cli/commands/sync.js +67 -0
- package/dist/cli/index.js +174 -0
- package/dist/config/index.js +59 -0
- package/{src/db/schema.ts → dist/db/schema.js} +0 -1
- package/dist/db/sqlite.js +237 -0
- package/dist/utils/format.js +89 -0
- package/dist/utils/jid.js +40 -0
- package/dist/utils/notify.js +29 -0
- package/dist/utils/ui.js +29 -0
- package/dist/whatsapp/client.js +157 -0
- package/dist/whatsapp/events.js +161 -0
- package/dist/whatsapp/logger.js +52 -0
- package/package.json +5 -3
- package/src/cli/commands/auth.ts +0 -236
- package/src/cli/commands/export.ts +0 -60
- package/src/cli/commands/filepicker.ts +0 -247
- package/src/cli/commands/group.ts +0 -139
- package/src/cli/commands/manage.ts +0 -46
- package/src/cli/commands/media.ts +0 -125
- package/src/cli/commands/misc.ts +0 -113
- package/src/cli/commands/read.ts +0 -99
- package/src/cli/commands/send.ts +0 -201
- package/src/cli/commands/shell.ts +0 -596
- package/src/cli/commands/sync.ts +0 -80
- package/src/cli/index.ts +0 -198
- package/src/config/index.ts +0 -92
- package/src/db/sqlite.ts +0 -354
- package/src/utils/format.ts +0 -102
- package/src/utils/jid.ts +0 -51
- package/src/utils/notify.ts +0 -29
- package/src/utils/ui.ts +0 -35
- package/src/whatsapp/client.ts +0 -207
- package/src/whatsapp/events.ts +0 -166
- package/src/whatsapp/logger.ts +0 -71
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { platform } from 'node:os';
|
|
3
|
+
/**
|
|
4
|
+
* Fire a native desktop notification. Best-effort and non-blocking: shells out
|
|
5
|
+
* to the platform notifier (macOS `osascript`, Linux `notify-send`). Any
|
|
6
|
+
* failure (tool missing, no display) is silently ignored — notifications must
|
|
7
|
+
* never break the CLI.
|
|
8
|
+
*/
|
|
9
|
+
export function notify(title, body) {
|
|
10
|
+
try {
|
|
11
|
+
const p = platform();
|
|
12
|
+
if (p === 'darwin') {
|
|
13
|
+
const esc = (s) => s.replace(/[\\"]/g, '\\$&');
|
|
14
|
+
const script = `display notification "${esc(clip(body))}" with title "${esc(clip(title))}"`;
|
|
15
|
+
execFile('osascript', ['-e', script], () => { });
|
|
16
|
+
}
|
|
17
|
+
else if (p === 'linux') {
|
|
18
|
+
execFile('notify-send', ['-a', 'WhatsApp CLI', clip(title), clip(body)], () => { });
|
|
19
|
+
}
|
|
20
|
+
// Windows: no reliable dependency-free path; skipped intentionally.
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
/* notifications are best-effort */
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function clip(s) {
|
|
27
|
+
const oneLine = s.replace(/\s+/g, ' ').trim();
|
|
28
|
+
return oneLine.length > 120 ? oneLine.slice(0, 119) + '…' : oneLine;
|
|
29
|
+
}
|
package/dist/utils/ui.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { c } from "./format.js";
|
|
2
|
+
export function ok(msg) {
|
|
3
|
+
process.stdout.write(`${c.green('✓')} ${msg}\n`);
|
|
4
|
+
}
|
|
5
|
+
export function info(msg) {
|
|
6
|
+
process.stdout.write(`${c.cyan('›')} ${msg}\n`);
|
|
7
|
+
}
|
|
8
|
+
export function warn(msg) {
|
|
9
|
+
process.stderr.write(`${c.yellow('!')} ${msg}\n`);
|
|
10
|
+
}
|
|
11
|
+
/** A user-facing, expected error (bad input, no session, etc.). */
|
|
12
|
+
export class AppError extends Error {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Abort the current command with a user-facing message. Throws an AppError so
|
|
16
|
+
* callers decide how to handle it: the CLI prints it and exits non-zero; the
|
|
17
|
+
* interactive shell prints it and keeps the session alive.
|
|
18
|
+
*/
|
|
19
|
+
export function fail(msg) {
|
|
20
|
+
throw new AppError(msg);
|
|
21
|
+
}
|
|
22
|
+
/** Render an error the way the CLI/shell should display it. */
|
|
23
|
+
export function printError(err) {
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
process.stderr.write(`${c.red('✗')} ${msg}\n`);
|
|
26
|
+
}
|
|
27
|
+
export function print(msg = '') {
|
|
28
|
+
process.stdout.write(msg + '\n');
|
|
29
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import makeWASocket, { useMultiFileAuthState, fetchLatestBaileysVersion, makeCacheableSignalKeyStore, Browsers, DisconnectReason, } from '@whiskeysockets/baileys';
|
|
2
|
+
import { existsSync, readFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { PATHS, ensureDirs } from "../config/index.js";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
/** Extract the numeric status code from a Baileys disconnect error. */
|
|
7
|
+
function statusCode(err) {
|
|
8
|
+
return err?.output?.statusCode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Serialized chain of pending credential writes. WhatsApp streams `creds.update`
|
|
12
|
+
* events whose `saveCreds()` writes to disk asynchronously; exiting the process
|
|
13
|
+
* mid-write corrupts the session. Callers must `await whenWritesSettled()`
|
|
14
|
+
* before `process.exit()`.
|
|
15
|
+
*/
|
|
16
|
+
let pendingWrites = Promise.resolve();
|
|
17
|
+
export function whenWritesSettled() {
|
|
18
|
+
return pendingWrites;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A session counts as linked when creds.json exists, parses, and carries the
|
|
22
|
+
* account identity (`me.id`) — that's set only after a device is fully linked
|
|
23
|
+
* and it persists. (The `registered` flag is unreliable: it can remain false
|
|
24
|
+
* after a successful QR link due to write ordering across the 515 reconnect.)
|
|
25
|
+
* A missing, corrupt, or half-written creds file has no `me.id` → "not linked".
|
|
26
|
+
*/
|
|
27
|
+
export function hasSession() {
|
|
28
|
+
const p = join(PATHS.sessions, 'creds.json');
|
|
29
|
+
if (!existsSync(p))
|
|
30
|
+
return false;
|
|
31
|
+
try {
|
|
32
|
+
const creds = JSON.parse(readFileSync(p, 'utf8'));
|
|
33
|
+
return !!creds?.me?.id || creds?.registered === true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Delete the local session (used when it's dead / logged out server-side). */
|
|
40
|
+
export function clearSession() {
|
|
41
|
+
rmSync(PATHS.sessions, { recursive: true, force: true });
|
|
42
|
+
mkdirSync(PATHS.sessions, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Establish a connection with a single QR/open lifecycle. Reconnection on
|
|
46
|
+
* transient drops is handled by callers via {@link runWithConnection}, which
|
|
47
|
+
* recreates the connection when appropriate.
|
|
48
|
+
*/
|
|
49
|
+
export async function connect(opts = {}) {
|
|
50
|
+
ensureDirs();
|
|
51
|
+
if (opts.requireSession && !hasSession()) {
|
|
52
|
+
throw new Error('Not logged in. Run `wa login` first.');
|
|
53
|
+
}
|
|
54
|
+
const { state, saveCreds } = await useMultiFileAuthState(PATHS.sessions);
|
|
55
|
+
const { version } = await fetchLatestBaileysVersion();
|
|
56
|
+
const sock = makeWASocket({
|
|
57
|
+
version,
|
|
58
|
+
logger: logger,
|
|
59
|
+
printQRInTerminal: false,
|
|
60
|
+
auth: {
|
|
61
|
+
creds: state.creds,
|
|
62
|
+
keys: makeCacheableSignalKeyStore(state.keys, logger),
|
|
63
|
+
},
|
|
64
|
+
browser: Browsers.macOS('WhatsApp CLI'),
|
|
65
|
+
syncFullHistory: opts.syncFullHistory ?? false,
|
|
66
|
+
markOnlineOnConnect: false,
|
|
67
|
+
generateHighQualityLinkPreview: false,
|
|
68
|
+
});
|
|
69
|
+
// Serialize creds writes and track them so the process never exits mid-write.
|
|
70
|
+
sock.ev.on('creds.update', () => {
|
|
71
|
+
pendingWrites = pendingWrites.then(() => saveCreds()).catch(() => { });
|
|
72
|
+
});
|
|
73
|
+
let resolveReady;
|
|
74
|
+
let rejectReady;
|
|
75
|
+
const ready = new Promise((res, rej) => {
|
|
76
|
+
resolveReady = res;
|
|
77
|
+
rejectReady = rej;
|
|
78
|
+
});
|
|
79
|
+
let pairingRequested = false;
|
|
80
|
+
sock.ev.on('connection.update', (update) => {
|
|
81
|
+
const { connection, lastDisconnect, qr } = update;
|
|
82
|
+
if (qr) {
|
|
83
|
+
if (opts.pairingPhone) {
|
|
84
|
+
// Pairing-code mode: request the code once on the first `qr` (which
|
|
85
|
+
// signals the socket is ready to pair). Ignore all later qr refreshes —
|
|
86
|
+
// never render a QR, and never re-request (that would change the code).
|
|
87
|
+
if (!pairingRequested) {
|
|
88
|
+
pairingRequested = true;
|
|
89
|
+
void sock
|
|
90
|
+
.requestPairingCode(opts.pairingPhone)
|
|
91
|
+
.then((code) => opts.onPairingCode?.(code))
|
|
92
|
+
.catch((e) => rejectReady(new Error(`PAIRING_FAILED:${e.message}`)));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (opts.onQR) {
|
|
96
|
+
opts.onQR(qr);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (connection === 'open') {
|
|
100
|
+
resolveReady();
|
|
101
|
+
}
|
|
102
|
+
else if (connection === 'close') {
|
|
103
|
+
const code = statusCode(lastDisconnect?.error);
|
|
104
|
+
const loggedOut = code === DisconnectReason.loggedOut;
|
|
105
|
+
const err = new Error(loggedOut
|
|
106
|
+
? 'LOGGED_OUT'
|
|
107
|
+
: `CONNECTION_CLOSED:${code ?? 'unknown'}`);
|
|
108
|
+
rejectReady(err);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
sock,
|
|
113
|
+
saveCreds,
|
|
114
|
+
ready,
|
|
115
|
+
close: () => {
|
|
116
|
+
try {
|
|
117
|
+
sock.end(undefined);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* already closed */
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Run a task with an open connection, auto-reconnecting on transient drops.
|
|
127
|
+
* Rejects on logout or after exhausting retries. Always closes the socket.
|
|
128
|
+
*/
|
|
129
|
+
export async function runWithConnection(task, opts = {}) {
|
|
130
|
+
const maxRetries = opts.maxRetries ?? 3;
|
|
131
|
+
let attempt = 0;
|
|
132
|
+
// eslint-disable-next-line no-constant-condition
|
|
133
|
+
while (true) {
|
|
134
|
+
const conn = await connect(opts);
|
|
135
|
+
try {
|
|
136
|
+
await conn.ready;
|
|
137
|
+
const result = await task(conn);
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const msg = err.message ?? '';
|
|
142
|
+
if (msg === 'LOGGED_OUT') {
|
|
143
|
+
conn.close();
|
|
144
|
+
throw new Error('Session logged out. Run `wa logout` then `wa login` again.');
|
|
145
|
+
}
|
|
146
|
+
if (attempt++ >= maxRetries) {
|
|
147
|
+
conn.close();
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
// brief backoff before reconnecting
|
|
151
|
+
await new Promise((r) => setTimeout(r, 1000 * attempt));
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
conn.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { proto } from '@whiskeysockets/baileys';
|
|
2
|
+
import { insertMessage, upsertChat, upsertContact } from "../db/sqlite.js";
|
|
3
|
+
/** Baileys timestamps are seconds (sometimes Long); normalize to ms. */
|
|
4
|
+
export function toMs(ts) {
|
|
5
|
+
if (ts == null)
|
|
6
|
+
return 0;
|
|
7
|
+
const n = typeof ts === 'number' ? ts : ts.toNumber();
|
|
8
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
9
|
+
return 0;
|
|
10
|
+
return n < 1e12 ? n * 1000 : n; // treat >=1e12 as already-ms
|
|
11
|
+
}
|
|
12
|
+
export function isGroupJid(jid) {
|
|
13
|
+
return !!jid && jid.endsWith('@g.us');
|
|
14
|
+
}
|
|
15
|
+
/** Best-effort plain-text extraction from any WhatsApp message content. */
|
|
16
|
+
export function extractText(message) {
|
|
17
|
+
if (!message)
|
|
18
|
+
return '';
|
|
19
|
+
return (message.conversation ||
|
|
20
|
+
message.extendedTextMessage?.text ||
|
|
21
|
+
message.imageMessage?.caption ||
|
|
22
|
+
message.videoMessage?.caption ||
|
|
23
|
+
message.documentMessage?.caption ||
|
|
24
|
+
message.documentWithCaptionMessage?.message?.documentMessage?.caption ||
|
|
25
|
+
message.buttonsResponseMessage?.selectedDisplayText ||
|
|
26
|
+
message.listResponseMessage?.title ||
|
|
27
|
+
message.templateButtonReplyMessage?.selectedDisplayText ||
|
|
28
|
+
(message.reactionMessage?.text
|
|
29
|
+
? `reacted ${message.reactionMessage.text}`
|
|
30
|
+
: '') ||
|
|
31
|
+
'');
|
|
32
|
+
}
|
|
33
|
+
/** Classify a message by its dominant content type (for filtering/media). */
|
|
34
|
+
export function messageType(message) {
|
|
35
|
+
if (!message)
|
|
36
|
+
return 'unknown';
|
|
37
|
+
if (message.conversation || message.extendedTextMessage)
|
|
38
|
+
return 'text';
|
|
39
|
+
if (message.imageMessage)
|
|
40
|
+
return 'image';
|
|
41
|
+
if (message.videoMessage)
|
|
42
|
+
return 'video';
|
|
43
|
+
if (message.audioMessage)
|
|
44
|
+
return 'audio';
|
|
45
|
+
if (message.documentMessage || message.documentWithCaptionMessage)
|
|
46
|
+
return 'document';
|
|
47
|
+
if (message.stickerMessage)
|
|
48
|
+
return 'sticker';
|
|
49
|
+
if (message.locationMessage)
|
|
50
|
+
return 'location';
|
|
51
|
+
if (message.contactMessage)
|
|
52
|
+
return 'contact';
|
|
53
|
+
if (message.reactionMessage)
|
|
54
|
+
return 'reaction';
|
|
55
|
+
return 'other';
|
|
56
|
+
}
|
|
57
|
+
/** Persist a single Baileys message into the local cache. */
|
|
58
|
+
export function persistMessage(msg) {
|
|
59
|
+
const jid = msg.key.remoteJid;
|
|
60
|
+
if (!jid || jid === 'status@broadcast')
|
|
61
|
+
return;
|
|
62
|
+
const text = extractText(msg.message);
|
|
63
|
+
const type = messageType(msg.message);
|
|
64
|
+
const ts = toMs(msg.messageTimestamp);
|
|
65
|
+
const fromMe = !!msg.key.fromMe;
|
|
66
|
+
const sender = fromMe ? 'me' : msg.pushName || msg.key.participant || jid;
|
|
67
|
+
// For media, keep the raw content so `wa download` can decrypt it later.
|
|
68
|
+
const isMedia = ['image', 'video', 'audio', 'document', 'sticker'].includes(type);
|
|
69
|
+
let raw = null;
|
|
70
|
+
if (isMedia && msg.message) {
|
|
71
|
+
try {
|
|
72
|
+
// Protobuf-encode so binary fields (mediaKey, etc.) survive round-trips.
|
|
73
|
+
raw = Buffer.from(proto.Message.encode(msg.message).finish()).toString('base64');
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
raw = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
insertMessage({
|
|
80
|
+
chatJid: jid,
|
|
81
|
+
messageId: msg.key.id ?? `${ts}`,
|
|
82
|
+
sender,
|
|
83
|
+
text: text || null,
|
|
84
|
+
timestamp: ts,
|
|
85
|
+
status: undefined,
|
|
86
|
+
type,
|
|
87
|
+
fromMe,
|
|
88
|
+
raw,
|
|
89
|
+
});
|
|
90
|
+
// Keep the chat row's "last message" preview fresh.
|
|
91
|
+
upsertChat({
|
|
92
|
+
jid,
|
|
93
|
+
name: isGroupJid(jid) ? undefined : !fromMe ? msg.pushName || undefined : undefined,
|
|
94
|
+
lastMessage: text || `[${type}]`,
|
|
95
|
+
lastTimestamp: ts,
|
|
96
|
+
isGroup: isGroupJid(jid) ? 1 : 0,
|
|
97
|
+
});
|
|
98
|
+
// Record the direct contact's push name when we learn it.
|
|
99
|
+
if (!isGroupJid(jid) && !fromMe && msg.pushName) {
|
|
100
|
+
upsertContact({ jid, name: msg.pushName });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Register all persistence handlers on a live socket. Used by `sync` (batch
|
|
105
|
+
* history) and `watch` (live stream). Returns nothing; handlers persist to DB.
|
|
106
|
+
*/
|
|
107
|
+
export function registerPersistence(sock, hooks = {}) {
|
|
108
|
+
// Live + recent messages.
|
|
109
|
+
sock.ev.on('messages.upsert', ({ messages }) => {
|
|
110
|
+
for (const msg of messages) {
|
|
111
|
+
persistMessage(msg);
|
|
112
|
+
hooks.onMessage?.(msg);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Bulk history sync delivered after login / on demand.
|
|
116
|
+
sock.ev.on('messaging-history.set', ({ chats, contacts, messages }) => {
|
|
117
|
+
for (const c of chats) {
|
|
118
|
+
if (!c.id)
|
|
119
|
+
continue;
|
|
120
|
+
upsertChat({
|
|
121
|
+
jid: c.id,
|
|
122
|
+
name: c.name ?? undefined,
|
|
123
|
+
unreadCount: c.unreadCount ?? 0,
|
|
124
|
+
archived: c.archived ? 1 : 0,
|
|
125
|
+
pinned: c.pinned ? 1 : 0,
|
|
126
|
+
lastTimestamp: toMs(c.conversationTimestamp),
|
|
127
|
+
isGroup: isGroupJid(c.id) ? 1 : 0,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
for (const ct of contacts) {
|
|
131
|
+
if (ct.id)
|
|
132
|
+
upsertContact({ jid: ct.id, name: ct.name ?? ct.notify ?? undefined });
|
|
133
|
+
}
|
|
134
|
+
for (const msg of messages)
|
|
135
|
+
persistMessage(msg);
|
|
136
|
+
});
|
|
137
|
+
// Chat metadata updates.
|
|
138
|
+
sock.ev.on('chats.upsert', (chats) => {
|
|
139
|
+
for (const c of chats) {
|
|
140
|
+
if (!c.id)
|
|
141
|
+
continue;
|
|
142
|
+
upsertChat({
|
|
143
|
+
jid: c.id,
|
|
144
|
+
name: c.name ?? undefined,
|
|
145
|
+
unreadCount: c.unreadCount ?? 0,
|
|
146
|
+
archived: c.archived ? 1 : 0,
|
|
147
|
+
pinned: c.pinned ? 1 : 0,
|
|
148
|
+
isGroup: isGroupJid(c.id) ? 1 : 0,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// Contact names.
|
|
153
|
+
const onContacts = (contacts) => {
|
|
154
|
+
for (const ct of contacts) {
|
|
155
|
+
if (ct.id)
|
|
156
|
+
upsertContact({ jid: ct.id, name: ct.name ?? ct.notify ?? undefined });
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
sock.ev.on('contacts.upsert', onContacts);
|
|
160
|
+
sock.ev.on('contacts.update', onContacts);
|
|
161
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const DEBUG = !!process.env.WA_DEBUG;
|
|
2
|
+
function make(level) {
|
|
3
|
+
const emit = (tag) => (...args) => {
|
|
4
|
+
if (!DEBUG)
|
|
5
|
+
return;
|
|
6
|
+
// Baileys calls logger.error(obj, msg); print compactly.
|
|
7
|
+
process.stderr.write(`[wa:${tag}] ${args.map(fmt).join(' ')}\n`);
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
level,
|
|
11
|
+
trace: emit('trace'),
|
|
12
|
+
debug: emit('debug'),
|
|
13
|
+
info: emit('info'),
|
|
14
|
+
warn: emit('warn'),
|
|
15
|
+
error: emit('error'),
|
|
16
|
+
fatal: emit('fatal'),
|
|
17
|
+
child: () => make(level),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function fmt(v) {
|
|
21
|
+
if (typeof v === 'string')
|
|
22
|
+
return v;
|
|
23
|
+
try {
|
|
24
|
+
return JSON.stringify(v);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return String(v);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const logger = make(DEBUG ? 'debug' : 'silent');
|
|
31
|
+
/**
|
|
32
|
+
* Baileys' vendored libsignal logs session churn via `console.log`/`console.error`
|
|
33
|
+
* directly, bypassing the logger above and cluttering the terminal (e.g.
|
|
34
|
+
* "Closing session: SessionEntry { … }"). Filter those specific lines out
|
|
35
|
+
* unless debugging. Call once at startup.
|
|
36
|
+
*/
|
|
37
|
+
// libsignal (node_modules/libsignal/src/session_record.js) logs session churn
|
|
38
|
+
// directly via console.info/warn/error, bypassing the logger above.
|
|
39
|
+
const NOISE = /^(Closing (open |stale )?session|Opening session|Session already (closed|open)|Migrating session|Removing old closed session|V1 session storage migration|Session error|Bad MAC|SessionEntry)/;
|
|
40
|
+
export function installConsoleFilter() {
|
|
41
|
+
if (DEBUG)
|
|
42
|
+
return;
|
|
43
|
+
for (const method of ['log', 'info', 'error', 'warn']) {
|
|
44
|
+
const original = console[method].bind(console);
|
|
45
|
+
console[method] = (...args) => {
|
|
46
|
+
const first = args[0];
|
|
47
|
+
if (typeof first === 'string' && NOISE.test(first))
|
|
48
|
+
return;
|
|
49
|
+
original(...args);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli-whatsapp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A lightweight, fast, terminal-first WhatsApp client. Read, search, send, media, groups, and live notifications — from your terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
|
-
"
|
|
11
|
+
"dist/",
|
|
12
12
|
"README.md",
|
|
13
13
|
"COMMANDS.md",
|
|
14
14
|
"LICENSE"
|
|
@@ -25,10 +25,12 @@
|
|
|
25
25
|
"local-first"
|
|
26
26
|
],
|
|
27
27
|
"scripts": {
|
|
28
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
29
|
+
"dev": "node src/cli/index.ts",
|
|
28
30
|
"wa": "node bin/wa.js",
|
|
29
31
|
"typecheck": "tsc --noEmit",
|
|
30
32
|
"test": "node --test tests/",
|
|
31
|
-
"prepublishOnly": "npm run typecheck"
|
|
33
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
32
34
|
},
|
|
33
35
|
"engines": {
|
|
34
36
|
"node": ">=22.6.0"
|