cli-whatsapp 0.1.0 → 0.1.2
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 +611 -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 +6 -4
- 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 -91
- 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
package/src/cli/commands/misc.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { existsSync, accessSync, constants, readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { loadConfig, saveConfig, PATHS, ensureDirs } from '../../config/index.ts';
|
|
4
|
-
import { hasSession } from '../../whatsapp/client.ts';
|
|
5
|
-
import { countMessages, listChats, getDb } from '../../db/sqlite.ts';
|
|
6
|
-
import { print, ok, warn, info, fail } from '../../utils/ui.ts';
|
|
7
|
-
import { c } from '../../utils/format.ts';
|
|
8
|
-
|
|
9
|
-
function pkgVersion(): string {
|
|
10
|
-
try {
|
|
11
|
-
const p = join(new URL('../../../', import.meta.url).pathname, 'package.json');
|
|
12
|
-
return JSON.parse(readFileSync(p, 'utf8')).version ?? '0.0.0';
|
|
13
|
-
} catch {
|
|
14
|
-
return '0.0.0';
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function version(): void {
|
|
19
|
-
print(`wa ${pkgVersion()}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** wa config [set <key> <value>] — view or edit preferences. */
|
|
23
|
-
export function config(action?: string, key?: string, value?: string): void {
|
|
24
|
-
const cfg = loadConfig();
|
|
25
|
-
if (!action) {
|
|
26
|
-
print(c.bold('Config') + c.dim(` (${PATHS.config})`));
|
|
27
|
-
print(JSON.stringify(cfg, null, 2));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (action !== 'set' || !key || value === undefined) {
|
|
31
|
-
fail('Usage: wa config set <openLimit|time24h|notifications|autoConnect|ai.enabled> <value>');
|
|
32
|
-
}
|
|
33
|
-
switch (key) {
|
|
34
|
-
case 'openLimit':
|
|
35
|
-
cfg.preferences.openLimit = Math.max(1, parseInt(value, 10) || cfg.preferences.openLimit);
|
|
36
|
-
break;
|
|
37
|
-
case 'time24h':
|
|
38
|
-
cfg.preferences.time24h = value === 'true' || value === '1';
|
|
39
|
-
break;
|
|
40
|
-
case 'notifications':
|
|
41
|
-
cfg.preferences.notifications = value === 'true' || value === '1';
|
|
42
|
-
break;
|
|
43
|
-
case 'autoConnect':
|
|
44
|
-
cfg.preferences.autoConnect = value === 'true' || value === '1';
|
|
45
|
-
break;
|
|
46
|
-
case 'ai.enabled':
|
|
47
|
-
cfg.ai.enabled = value === 'true' || value === '1';
|
|
48
|
-
break;
|
|
49
|
-
default:
|
|
50
|
-
fail(`Unknown key "${key}". Editable: openLimit, time24h, notifications, autoConnect, ai.enabled`);
|
|
51
|
-
}
|
|
52
|
-
saveConfig(cfg);
|
|
53
|
-
ok(`Set ${key} = ${value}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** wa doctor — self-diagnostics for a healthy install. */
|
|
57
|
-
export function doctor(): void {
|
|
58
|
-
print(c.bold('WhatsApp CLI — doctor'));
|
|
59
|
-
let problems = 0;
|
|
60
|
-
|
|
61
|
-
const nodeOk = satisfiesNode(process.versions.node, 22, 5);
|
|
62
|
-
line(nodeOk, `Node.js ${process.versions.node}`, 'Needs Node >= 22.5 (built-in node:sqlite + TS).');
|
|
63
|
-
if (!nodeOk) problems++;
|
|
64
|
-
|
|
65
|
-
ensureDirs();
|
|
66
|
-
for (const [label, dir] of [
|
|
67
|
-
['sessions dir', PATHS.sessions],
|
|
68
|
-
['data dir', PATHS.data],
|
|
69
|
-
['media dir', PATHS.media],
|
|
70
|
-
] as const) {
|
|
71
|
-
const writable = isWritable(dir);
|
|
72
|
-
line(writable, `${label} writable ${c.dim(dir)}`);
|
|
73
|
-
if (!writable) problems++;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let dbOk = true;
|
|
77
|
-
try {
|
|
78
|
-
getDb();
|
|
79
|
-
print(` ${c.green('✓')} database ok ${c.dim(`${countMessages()} messages, ${listChats(5000).length} chats`)}`);
|
|
80
|
-
} catch (e) {
|
|
81
|
-
dbOk = false;
|
|
82
|
-
problems++;
|
|
83
|
-
print(` ${c.red('✗')} database error: ${(e as Error).message}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (hasSession()) line(true, 'session linked');
|
|
87
|
-
else print(` ${c.dim('○')} no session ${c.dim('(run `wa login` to link)')}`);
|
|
88
|
-
|
|
89
|
-
print();
|
|
90
|
-
if (problems === 0) ok('All systems go.');
|
|
91
|
-
else warn(`${problems} issue${problems === 1 ? '' : 's'} found.`);
|
|
92
|
-
void dbOk;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function line(good: boolean, msg: string, hint?: string): void {
|
|
96
|
-
print(` ${good ? c.green('✓') : c.red('✗')} ${msg}`);
|
|
97
|
-
if (!good && hint) print(` ${c.dim(hint)}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function satisfiesNode(v: string, major: number, minor: number): boolean {
|
|
101
|
-
const [maj, min] = v.split('.').map((x) => parseInt(x, 10));
|
|
102
|
-
return maj > major || (maj === major && min >= minor);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function isWritable(dir: string): boolean {
|
|
106
|
-
try {
|
|
107
|
-
if (!existsSync(dir)) return false;
|
|
108
|
-
accessSync(dir, constants.W_OK);
|
|
109
|
-
return true;
|
|
110
|
-
} catch {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
}
|
package/src/cli/commands/read.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
listChats,
|
|
3
|
-
listContacts,
|
|
4
|
-
getMessages,
|
|
5
|
-
searchMessages,
|
|
6
|
-
findChat,
|
|
7
|
-
} from '../../db/sqlite.ts';
|
|
8
|
-
import { loadConfig } from '../../config/index.ts';
|
|
9
|
-
import {
|
|
10
|
-
renderChatList,
|
|
11
|
-
renderMessages,
|
|
12
|
-
displayName,
|
|
13
|
-
formatTime,
|
|
14
|
-
truncate,
|
|
15
|
-
c,
|
|
16
|
-
} from '../../utils/format.ts';
|
|
17
|
-
import { print, info, fail } from '../../utils/ui.ts';
|
|
18
|
-
|
|
19
|
-
/** wa chats [--all] [--limit N] */
|
|
20
|
-
export function chats(opts: { all?: boolean; limit?: string }): void {
|
|
21
|
-
const limit = clampLimit(opts.limit, 50);
|
|
22
|
-
const rows = listChats(limit, !!opts.all);
|
|
23
|
-
const cfg = loadConfig();
|
|
24
|
-
print(renderChatList(rows, cfg.preferences.time24h));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** wa contacts [--limit N] */
|
|
28
|
-
export function contacts(opts: { limit?: string }): void {
|
|
29
|
-
const limit = clampLimit(opts.limit, 500);
|
|
30
|
-
const rows = listContacts(limit);
|
|
31
|
-
if (rows.length === 0) {
|
|
32
|
-
print(c.dim('No contacts cached yet. Run `wa sync`.'));
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const width = String(rows.length).length;
|
|
36
|
-
print(
|
|
37
|
-
rows
|
|
38
|
-
.map((r, i) => {
|
|
39
|
-
const idx = c.dim(String(i + 1).padStart(width) + '.');
|
|
40
|
-
const num = c.gray(r.jid.split('@')[0]);
|
|
41
|
-
return `${idx} ${c.bold(r.name ?? '(unknown)')} ${num}`;
|
|
42
|
-
})
|
|
43
|
-
.join('\n'),
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** wa open <chat> [--limit N] */
|
|
48
|
-
export function open(target: string, opts: { limit?: string }): void {
|
|
49
|
-
const chat = findChat(target);
|
|
50
|
-
if (!chat) {
|
|
51
|
-
fail(`No chat matching "${target}". Try \`wa chats\` or \`wa sync\`.`);
|
|
52
|
-
}
|
|
53
|
-
const cfg = loadConfig();
|
|
54
|
-
const limit = clampLimit(opts.limit, cfg.preferences.openLimit);
|
|
55
|
-
print(c.bold(displayName(chat)) + c.dim(` ${chat.jid}`));
|
|
56
|
-
print(c.dim('─'.repeat(40)));
|
|
57
|
-
print(renderMessages(getMessages(chat.jid, limit), cfg.preferences.time24h));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** wa history <chat> [--limit N] — alias of open with a larger default. */
|
|
61
|
-
export function history(target: string, opts: { limit?: string }): void {
|
|
62
|
-
open(target, { limit: opts.limit ?? '200' });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** wa search <text> [--limit N] */
|
|
66
|
-
export function search(term: string, opts: { limit?: string }): void {
|
|
67
|
-
const limit = clampLimit(opts.limit, 50);
|
|
68
|
-
const results = searchMessages(term, limit);
|
|
69
|
-
const cfg = loadConfig();
|
|
70
|
-
if (results.length === 0) {
|
|
71
|
-
print(c.dim(`No messages matching "${term}".`));
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
info(`${results.length} match${results.length === 1 ? '' : 'es'} for "${term}":`);
|
|
75
|
-
print();
|
|
76
|
-
for (const m of results) {
|
|
77
|
-
const chat = findChat(m.chatJid);
|
|
78
|
-
const where = c.magenta(chat ? displayName(chat) : m.chatJid.split('@')[0]);
|
|
79
|
-
const time = c.gray(formatTime(m.timestamp, cfg.preferences.time24h));
|
|
80
|
-
const who = m.fromMe ? c.green('me') : c.cyan(m.sender ?? '');
|
|
81
|
-
const body = highlight(truncate(m.text ?? '', 80), term);
|
|
82
|
-
print(`${where} ${time} ${who}: ${body} ${c.dim(`#${m.id}`)}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function highlight(text: string, term: string): string {
|
|
87
|
-
try {
|
|
88
|
-
const re = new RegExp(`(${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'ig');
|
|
89
|
-
return text.replace(re, (m) => c.yellow(m));
|
|
90
|
-
} catch {
|
|
91
|
-
return text;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function clampLimit(raw: string | undefined, fallback: number): number {
|
|
96
|
-
const n = raw ? parseInt(raw, 10) : fallback;
|
|
97
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
98
|
-
return Math.min(n, 2000);
|
|
99
|
-
}
|
package/src/cli/commands/send.ts
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { existsSync, statSync } from 'node:fs';
|
|
2
|
-
import { basename, extname, resolve } from 'node:path';
|
|
3
|
-
import type { AnyMessageContent } from '@whiskeysockets/baileys';
|
|
4
|
-
import { runWithConnection, type Socket } from '../../whatsapp/client.ts';
|
|
5
|
-
import { resolveTarget } from '../../utils/jid.ts';
|
|
6
|
-
import { getMessageById, insertMessage, upsertChat, findChat } from '../../db/sqlite.ts';
|
|
7
|
-
import { toMs } from '../../whatsapp/events.ts';
|
|
8
|
-
import { ok, fail, info } from '../../utils/ui.ts';
|
|
9
|
-
import { displayName } from '../../utils/format.ts';
|
|
10
|
-
|
|
11
|
-
interface SendOpts {
|
|
12
|
-
file?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** wa send <chat> [message] [--file <path>] */
|
|
16
|
-
export async function send(
|
|
17
|
-
target: string,
|
|
18
|
-
message: string | undefined,
|
|
19
|
-
opts: SendOpts = {},
|
|
20
|
-
): Promise<void> {
|
|
21
|
-
const resolved = resolveTarget(target);
|
|
22
|
-
if (!resolved) {
|
|
23
|
-
fail(`Could not resolve "${target}". Use a known chat name or a phone number.`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (opts.file) {
|
|
27
|
-
const built = buildMediaContent(opts.file, message);
|
|
28
|
-
info(`Sending ${built.kind} to ${resolved.name}…`);
|
|
29
|
-
await deliver(resolved.jid, built.content, resolved.name, {
|
|
30
|
-
previewText: message?.trim() || `[${built.kind}]`,
|
|
31
|
-
type: built.kind,
|
|
32
|
-
mediaPath: built.path,
|
|
33
|
-
});
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!message || !message.trim()) {
|
|
38
|
-
fail('Nothing to send. Provide a message, or --file <path> for media.');
|
|
39
|
-
}
|
|
40
|
-
info(`Sending to ${resolved.name}…`);
|
|
41
|
-
await deliver(resolved.jid, { text: message }, resolved.name, {
|
|
42
|
-
previewText: message,
|
|
43
|
-
type: 'text',
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** wa reply <id> <message> — reply into the chat of message #id, quoting it. */
|
|
48
|
-
export async function reply(idRaw: string, message: string): Promise<void> {
|
|
49
|
-
const id = parseInt(idRaw, 10);
|
|
50
|
-
if (!Number.isFinite(id)) fail(`Invalid message id "${idRaw}".`);
|
|
51
|
-
const original = getMessageById(id);
|
|
52
|
-
if (!original) fail(`No cached message with id #${id}. Open the chat first.`);
|
|
53
|
-
if (!message || !message.trim()) fail('Message text is empty.');
|
|
54
|
-
|
|
55
|
-
const chat = findChat(original.chatJid);
|
|
56
|
-
const name = chat ? displayName(chat) : original.chatJid;
|
|
57
|
-
info(`Replying in ${name}…`);
|
|
58
|
-
|
|
59
|
-
// Reconstruct a minimal quoted message for the reply context.
|
|
60
|
-
const quoted = {
|
|
61
|
-
key: {
|
|
62
|
-
remoteJid: original.chatJid,
|
|
63
|
-
id: original.messageId,
|
|
64
|
-
fromMe: !!original.fromMe,
|
|
65
|
-
},
|
|
66
|
-
message: { conversation: original.text ?? '' },
|
|
67
|
-
};
|
|
68
|
-
await deliver(
|
|
69
|
-
original.chatJid,
|
|
70
|
-
{ text: message },
|
|
71
|
-
name,
|
|
72
|
-
{ previewText: message, type: 'text' },
|
|
73
|
-
quoted,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---- media content ---------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
export type MediaKind = 'image' | 'video' | 'audio' | 'document' | 'sticker';
|
|
80
|
-
|
|
81
|
-
export interface SendMeta {
|
|
82
|
-
previewText: string;
|
|
83
|
-
type: string;
|
|
84
|
-
mediaPath?: string;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const EXT_KIND: Record<string, MediaKind> = {
|
|
88
|
-
'.jpg': 'image', '.jpeg': 'image', '.png': 'image', '.gif': 'image',
|
|
89
|
-
'.mp4': 'video', '.mov': 'video', '.mkv': 'video', '.webm': 'video',
|
|
90
|
-
'.mp3': 'audio', '.ogg': 'audio', '.m4a': 'audio', '.opus': 'audio', '.wav': 'audio',
|
|
91
|
-
'.webp': 'sticker',
|
|
92
|
-
'.pdf': 'document', '.doc': 'document', '.docx': 'document',
|
|
93
|
-
'.xls': 'document', '.xlsx': 'document', '.ppt': 'document', '.pptx': 'document',
|
|
94
|
-
'.txt': 'document', '.zip': 'document', '.csv': 'document',
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const EXT_MIME: Record<string, string> = {
|
|
98
|
-
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif',
|
|
99
|
-
'.mp4': 'video/mp4', '.mov': 'video/quicktime', '.webm': 'video/webm',
|
|
100
|
-
'.mp3': 'audio/mpeg', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4', '.opus': 'audio/ogg; codecs=opus',
|
|
101
|
-
'.webp': 'image/webp', '.pdf': 'application/pdf', '.zip': 'application/zip',
|
|
102
|
-
'.txt': 'text/plain', '.csv': 'text/csv',
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export function buildMediaContent(
|
|
106
|
-
file: string,
|
|
107
|
-
caption: string | undefined,
|
|
108
|
-
): { content: AnyMessageContent; kind: MediaKind; path: string } {
|
|
109
|
-
const path = resolve(file);
|
|
110
|
-
if (!existsSync(path) || !statSync(path).isFile()) {
|
|
111
|
-
fail(`File not found: ${file}`);
|
|
112
|
-
}
|
|
113
|
-
const ext = extname(path).toLowerCase();
|
|
114
|
-
const kind = EXT_KIND[ext] ?? 'document';
|
|
115
|
-
const mimetype = EXT_MIME[ext];
|
|
116
|
-
const cap = caption?.trim() || undefined;
|
|
117
|
-
|
|
118
|
-
let content: AnyMessageContent;
|
|
119
|
-
switch (kind) {
|
|
120
|
-
case 'image':
|
|
121
|
-
content = { image: { url: path }, caption: cap };
|
|
122
|
-
break;
|
|
123
|
-
case 'video':
|
|
124
|
-
content = { video: { url: path }, caption: cap };
|
|
125
|
-
break;
|
|
126
|
-
case 'audio':
|
|
127
|
-
content = { audio: { url: path }, mimetype: mimetype ?? 'audio/mp4' };
|
|
128
|
-
break;
|
|
129
|
-
case 'sticker':
|
|
130
|
-
// Stickers can't carry a caption; if one was given, send as an image.
|
|
131
|
-
content = cap
|
|
132
|
-
? { image: { url: path }, caption: cap }
|
|
133
|
-
: { sticker: { url: path } };
|
|
134
|
-
break;
|
|
135
|
-
default:
|
|
136
|
-
content = {
|
|
137
|
-
document: { url: path },
|
|
138
|
-
mimetype: mimetype ?? 'application/octet-stream',
|
|
139
|
-
fileName: basename(path),
|
|
140
|
-
caption: cap,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
return { content, kind: kind === 'sticker' && cap ? 'image' : kind, path };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---- delivery --------------------------------------------------------------
|
|
147
|
-
|
|
148
|
-
/** Send content over an already-open socket and cache it locally. Reusable by
|
|
149
|
-
* both the one-shot CLI path and the persistent interactive session. */
|
|
150
|
-
export async function sendContent(
|
|
151
|
-
sock: Socket,
|
|
152
|
-
jid: string,
|
|
153
|
-
content: AnyMessageContent,
|
|
154
|
-
meta: SendMeta,
|
|
155
|
-
quoted?: unknown,
|
|
156
|
-
): Promise<void> {
|
|
157
|
-
const sent = (await sock.sendMessage(
|
|
158
|
-
jid,
|
|
159
|
-
content,
|
|
160
|
-
quoted ? { quoted: quoted as never } : undefined,
|
|
161
|
-
)) as { messageTimestamp?: number; key?: { id?: string } } | undefined;
|
|
162
|
-
cacheOutgoing(jid, content, sent, meta);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Optimistically cache an outgoing message we just sent. */
|
|
166
|
-
export function cacheOutgoing(
|
|
167
|
-
jid: string,
|
|
168
|
-
content: AnyMessageContent,
|
|
169
|
-
sent: { messageTimestamp?: number; key?: { id?: string } } | undefined,
|
|
170
|
-
meta: SendMeta,
|
|
171
|
-
): void {
|
|
172
|
-
const ts = toMs((sent?.messageTimestamp as number) ?? Date.now() / 1000);
|
|
173
|
-
insertMessage({
|
|
174
|
-
chatJid: jid,
|
|
175
|
-
messageId: sent?.key?.id ?? `local-${ts}`,
|
|
176
|
-
sender: 'me',
|
|
177
|
-
text: meta.type === 'text' ? meta.previewText : (content as { caption?: string }).caption ?? null,
|
|
178
|
-
timestamp: ts,
|
|
179
|
-
fromMe: true,
|
|
180
|
-
type: meta.type,
|
|
181
|
-
status: 'sent',
|
|
182
|
-
mediaPath: meta.mediaPath ?? null,
|
|
183
|
-
});
|
|
184
|
-
upsertChat({ jid, lastMessage: meta.previewText, lastTimestamp: ts });
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** One-shot delivery for the CLI: opens a connection, sends, closes. */
|
|
188
|
-
async function deliver(
|
|
189
|
-
jid: string,
|
|
190
|
-
content: AnyMessageContent,
|
|
191
|
-
name: string,
|
|
192
|
-
meta: SendMeta,
|
|
193
|
-
quoted?: unknown,
|
|
194
|
-
): Promise<void> {
|
|
195
|
-
await runWithConnection(
|
|
196
|
-
async (conn) => sendContent(conn.sock, jid, content, meta, quoted),
|
|
197
|
-
{ requireSession: true },
|
|
198
|
-
).catch((err) => fail((err as Error).message));
|
|
199
|
-
|
|
200
|
-
ok(`Sent to ${name}.`);
|
|
201
|
-
}
|