whats-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +12 -0
- package/.gitlab-ci.yml +54 -0
- package/CHANGELOG.md +38 -0
- package/README.md +205 -0
- package/TODO.md +6 -0
- package/config.json +19 -0
- package/package.json +46 -0
- package/src/.env.example +21 -0
- package/src/admin/cli.js +916 -0
- package/src/admin/service.js +271 -0
- package/src/admin/telegram.js +178 -0
- package/src/admin.js +12 -0
- package/src/config.js +147 -0
- package/src/connection.js +334 -0
- package/src/helpers.js +264 -0
- package/src/http_app.js +267 -0
- package/src/index.js +4 -0
- package/src/main.js +71 -0
- package/src/server.js +67 -0
- package/src/store.js +925 -0
- package/src/tools/analytics.js +157 -0
- package/src/tools/channels.js +215 -0
- package/src/tools/chats.js +291 -0
- package/src/tools/contacts.js +259 -0
- package/src/tools/digest.js +249 -0
- package/src/tools/groups.js +529 -0
- package/src/tools/history-support.js +114 -0
- package/src/tools/labels.js +168 -0
- package/src/tools/messaging.js +510 -0
- package/src/tools/overview.js +416 -0
- package/src/tools/profile.js +155 -0
- package/src/tools/registry.js +105 -0
- package/src/tools/tags.js +104 -0
- package/src/tools/utils.js +325 -0
- package/src/tools/watchlists.js +136 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* whats-mcp — Group tools (10 tools).
|
|
3
|
+
*
|
|
4
|
+
* create_group, get_group_info, list_groups, update_group_subject,
|
|
5
|
+
* update_group_description, manage_group_participants, leave_group,
|
|
6
|
+
* manage_group_invite, update_group_settings, set_group_picture
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
phoneToJid, groupJid, isGroupJid, jidToPhone, resolveMedia, okResult, errResult, formatMessage,
|
|
13
|
+
} = require("../helpers");
|
|
14
|
+
const { fetchAdditionalHistory } = require("./history-support");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a JID that is expected to be a group.
|
|
18
|
+
* - If already @g.us → pass through.
|
|
19
|
+
* - If contains @ (some other domain) → pass through as-is.
|
|
20
|
+
* - Otherwise → append @g.us (assume bare group ID).
|
|
21
|
+
*/
|
|
22
|
+
function _ensureGroupJid(jid) {
|
|
23
|
+
if (!jid) return jid;
|
|
24
|
+
if (jid.includes("@")) return jid;
|
|
25
|
+
return groupJid(jid);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _fmtParticipant(p) {
|
|
29
|
+
return {
|
|
30
|
+
jid: p.id,
|
|
31
|
+
phone: jidToPhone(p.id),
|
|
32
|
+
admin: p.admin || null, // "admin" | "superadmin" | null
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function _fmtGroupMeta(meta, options = {}) {
|
|
37
|
+
const allParticipants = (meta.participants || []).map(_fmtParticipant);
|
|
38
|
+
const includeParticipants = options.includeParticipants !== false;
|
|
39
|
+
const participantLimit = includeParticipants
|
|
40
|
+
? Math.max(0, options.participantLimit ?? 200)
|
|
41
|
+
: 0;
|
|
42
|
+
const participants = includeParticipants
|
|
43
|
+
? allParticipants.slice(0, participantLimit)
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
jid: meta.id,
|
|
48
|
+
subject: meta.subject,
|
|
49
|
+
subject_owner: meta.subjectOwner || null,
|
|
50
|
+
subject_time: meta.subjectTime ? Number(meta.subjectTime) : null,
|
|
51
|
+
description: meta.desc || null,
|
|
52
|
+
description_id: meta.descId || null,
|
|
53
|
+
owner: meta.owner || null,
|
|
54
|
+
creation_time: meta.creation ? Number(meta.creation) : null,
|
|
55
|
+
recent_messages: options.recentMessages || [],
|
|
56
|
+
recent_message_count: (options.recentMessages || []).length,
|
|
57
|
+
history_sync: options.historySync || null,
|
|
58
|
+
participant_count: allParticipants.length,
|
|
59
|
+
participants_returned: includeParticipants ? participants.length : 0,
|
|
60
|
+
participants_truncated: includeParticipants ? participants.length < allParticipants.length : allParticipants.length > 0,
|
|
61
|
+
participants,
|
|
62
|
+
size: meta.size || allParticipants.length,
|
|
63
|
+
announce: meta.announce ?? false, // only admins can send
|
|
64
|
+
restrict: meta.restrict ?? false, // only admins can edit info
|
|
65
|
+
ephemeral: meta.ephemeralDuration || 0, // disappearing timer
|
|
66
|
+
invite_code: meta.inviteCode || null,
|
|
67
|
+
linked_parent: meta.linkedParent || null, // community parent
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = [
|
|
72
|
+
// 1. create_group
|
|
73
|
+
{
|
|
74
|
+
definition: {
|
|
75
|
+
name: "create_group",
|
|
76
|
+
description:
|
|
77
|
+
"Create a new WhatsApp group." +
|
|
78
|
+
" You must provide at least 1 participant besides yourself.",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
subject: { type: "string", description: "Group name/subject." },
|
|
83
|
+
participants: {
|
|
84
|
+
type: "array",
|
|
85
|
+
items: { type: "string" },
|
|
86
|
+
description: "Array of participant JIDs or phone numbers to add.",
|
|
87
|
+
},
|
|
88
|
+
description: { type: "string", description: "Optional group description." },
|
|
89
|
+
},
|
|
90
|
+
required: ["subject", "participants"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
handler: async ({ subject, participants, description }, { sock }) => {
|
|
94
|
+
const jids = participants.map(phoneToJid);
|
|
95
|
+
const result = await sock.groupCreate(subject, jids);
|
|
96
|
+
if (description && result.id) {
|
|
97
|
+
try {
|
|
98
|
+
await sock.groupUpdateDescription(result.id, description);
|
|
99
|
+
} catch { /* ignore description failure */ }
|
|
100
|
+
}
|
|
101
|
+
return okResult({
|
|
102
|
+
status: "created",
|
|
103
|
+
jid: result.id,
|
|
104
|
+
subject: result.subject || subject,
|
|
105
|
+
participants: result.participants || jids.map((j) => ({ jid: j })),
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// 2. get_group_info
|
|
111
|
+
{
|
|
112
|
+
definition: {
|
|
113
|
+
name: "get_group_info",
|
|
114
|
+
description:
|
|
115
|
+
"Get full metadata for a group: subject, description, participants, settings, etc.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
jid: { type: "string", description: "Group JID (e.g. 120363xxx@g.us)." },
|
|
120
|
+
recent_messages_limit: {
|
|
121
|
+
type: "integer",
|
|
122
|
+
description: "Include up to this many recent cached messages before the participant list (default 10, max 50).",
|
|
123
|
+
},
|
|
124
|
+
hydrate_messages: {
|
|
125
|
+
type: "boolean",
|
|
126
|
+
description: "If true (default), request additional older history from WhatsApp when the local cache is too small.",
|
|
127
|
+
},
|
|
128
|
+
history_count: {
|
|
129
|
+
type: "integer",
|
|
130
|
+
description: "How many older messages to request during on-demand history sync (default: max(recent_messages_limit, 50), max 200).",
|
|
131
|
+
},
|
|
132
|
+
history_wait_ms: {
|
|
133
|
+
type: "integer",
|
|
134
|
+
description: "How long to wait for incoming history-sync events after requesting older messages (default 3500ms, max 15000ms).",
|
|
135
|
+
},
|
|
136
|
+
include_participants: {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
description: "Whether to include participant details in the response (default true).",
|
|
139
|
+
},
|
|
140
|
+
participant_limit: {
|
|
141
|
+
type: "integer",
|
|
142
|
+
description: "Maximum number of participants to include in the response (default 200).",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ["jid"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
handler: async ({
|
|
149
|
+
jid,
|
|
150
|
+
recent_messages_limit,
|
|
151
|
+
hydrate_messages,
|
|
152
|
+
history_count,
|
|
153
|
+
history_wait_ms,
|
|
154
|
+
include_participants,
|
|
155
|
+
participant_limit,
|
|
156
|
+
}, { sock, store }) => {
|
|
157
|
+
const gJid = _ensureGroupJid(jid);
|
|
158
|
+
if (!isGroupJid(gJid)) {
|
|
159
|
+
return errResult("Provided JID is not a group. Group JIDs end with @g.us.");
|
|
160
|
+
}
|
|
161
|
+
// Try live fetch first, fallback to cache
|
|
162
|
+
let meta;
|
|
163
|
+
try {
|
|
164
|
+
meta = await sock.groupMetadata(gJid);
|
|
165
|
+
} catch {
|
|
166
|
+
meta = store.getGroupMeta(gJid);
|
|
167
|
+
if (!meta) return errResult(`Could not retrieve metadata for group ${gJid}.`);
|
|
168
|
+
}
|
|
169
|
+
// Also cache it
|
|
170
|
+
store.setGroupMeta(gJid, meta);
|
|
171
|
+
|
|
172
|
+
const recentLimit = Math.min(Math.max(recent_messages_limit || 10, 0), 50);
|
|
173
|
+
let historySync = {
|
|
174
|
+
enabled: hydrate_messages !== false,
|
|
175
|
+
requested: false,
|
|
176
|
+
received: false,
|
|
177
|
+
reason: recentLimit > 0 ? "cache_sufficient" : "disabled",
|
|
178
|
+
before_count: store.countMessages(gJid),
|
|
179
|
+
after_count: store.countMessages(gJid),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (recentLimit > 0 && hydrate_messages !== false) {
|
|
183
|
+
const cachedMessages = store.getMessages(gJid, recentLimit);
|
|
184
|
+
if (cachedMessages.length < recentLimit) {
|
|
185
|
+
historySync = await fetchAdditionalHistory({
|
|
186
|
+
sock,
|
|
187
|
+
store,
|
|
188
|
+
jid: gJid,
|
|
189
|
+
limit: recentLimit,
|
|
190
|
+
historyCount: history_count,
|
|
191
|
+
waitMs: history_wait_ms,
|
|
192
|
+
enabled: hydrate_messages !== false,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const recentMessages = recentLimit > 0
|
|
198
|
+
? store.getMessages(gJid, recentLimit).map(formatMessage).filter(Boolean)
|
|
199
|
+
: [];
|
|
200
|
+
|
|
201
|
+
return okResult(_fmtGroupMeta(meta, {
|
|
202
|
+
recentMessages,
|
|
203
|
+
historySync,
|
|
204
|
+
includeParticipants: include_participants,
|
|
205
|
+
participantLimit: participant_limit,
|
|
206
|
+
}));
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// 3. list_groups
|
|
211
|
+
{
|
|
212
|
+
definition: {
|
|
213
|
+
name: "list_groups",
|
|
214
|
+
description: "List all groups you are a member of.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
limit: { type: "integer", description: "Max number of groups to return (default 50)." },
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
handler: async ({ limit }, { sock, store }) => {
|
|
223
|
+
const seen = new Set();
|
|
224
|
+
const groups = [];
|
|
225
|
+
const lim = limit || 50;
|
|
226
|
+
|
|
227
|
+
for (const chat of store.listChats(10000)) {
|
|
228
|
+
if (!isGroupJid(chat.id) || seen.has(chat.id)) continue;
|
|
229
|
+
seen.add(chat.id);
|
|
230
|
+
groups.push(chat);
|
|
231
|
+
if (groups.length >= lim) break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (groups.length < lim) {
|
|
235
|
+
for (const meta of store.groupMeta.values()) {
|
|
236
|
+
if (!meta?.id || seen.has(meta.id)) continue;
|
|
237
|
+
seen.add(meta.id);
|
|
238
|
+
groups.push({
|
|
239
|
+
id: meta.id,
|
|
240
|
+
name: meta.subject,
|
|
241
|
+
conversationTimestamp: meta.subjectTime || meta.creation || 0,
|
|
242
|
+
});
|
|
243
|
+
if (groups.length >= lim) break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Enrich with metadata if available
|
|
248
|
+
const results = [];
|
|
249
|
+
for (const g of groups) {
|
|
250
|
+
let meta = store.getGroupMeta(g.id);
|
|
251
|
+
if (!meta) {
|
|
252
|
+
try {
|
|
253
|
+
meta = await sock.groupMetadata(g.id);
|
|
254
|
+
store.setGroupMeta(g.id, meta);
|
|
255
|
+
} catch { /* skip */ }
|
|
256
|
+
}
|
|
257
|
+
results.push({
|
|
258
|
+
jid: g.id,
|
|
259
|
+
subject: meta?.subject || g.name || g.id,
|
|
260
|
+
participant_count: meta?.participants?.length || meta?.size || null,
|
|
261
|
+
creation_time: meta?.creation ? Number(meta.creation) : null,
|
|
262
|
+
announce: meta?.announce ?? null,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return okResult({ count: results.length, groups: results });
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// 4. update_group_subject
|
|
271
|
+
{
|
|
272
|
+
definition: {
|
|
273
|
+
name: "update_group_subject",
|
|
274
|
+
description: "Change the group name/subject.",
|
|
275
|
+
inputSchema: {
|
|
276
|
+
type: "object",
|
|
277
|
+
properties: {
|
|
278
|
+
jid: { type: "string", description: "Group JID." },
|
|
279
|
+
subject: { type: "string", description: "New group name (max 25 characters)." },
|
|
280
|
+
},
|
|
281
|
+
required: ["jid", "subject"],
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
handler: async ({ jid, subject }, { sock }) => {
|
|
285
|
+
const gJid = _ensureGroupJid(jid);
|
|
286
|
+
await sock.groupUpdateSubject(gJid, subject);
|
|
287
|
+
return okResult({ status: "updated", jid: gJid, subject });
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// 5. update_group_description
|
|
292
|
+
{
|
|
293
|
+
definition: {
|
|
294
|
+
name: "update_group_description",
|
|
295
|
+
description: "Update or clear the group description.",
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: "object",
|
|
298
|
+
properties: {
|
|
299
|
+
jid: { type: "string", description: "Group JID." },
|
|
300
|
+
description: { type: "string", description: "New description. Empty string to clear." },
|
|
301
|
+
},
|
|
302
|
+
required: ["jid", "description"],
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
handler: async ({ jid, description }, { sock }) => {
|
|
306
|
+
const gJid = _ensureGroupJid(jid);
|
|
307
|
+
await sock.groupUpdateDescription(gJid, description || undefined);
|
|
308
|
+
return okResult({ status: "updated", jid: gJid });
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
// 6. manage_group_participants
|
|
313
|
+
{
|
|
314
|
+
definition: {
|
|
315
|
+
name: "manage_group_participants",
|
|
316
|
+
description:
|
|
317
|
+
"Add, remove, promote (to admin), or demote (from admin) group participants.",
|
|
318
|
+
inputSchema: {
|
|
319
|
+
type: "object",
|
|
320
|
+
properties: {
|
|
321
|
+
jid: { type: "string", description: "Group JID." },
|
|
322
|
+
action: {
|
|
323
|
+
type: "string",
|
|
324
|
+
enum: ["add", "remove", "promote", "demote"],
|
|
325
|
+
description: "Action to perform on participants.",
|
|
326
|
+
},
|
|
327
|
+
participants: {
|
|
328
|
+
type: "array",
|
|
329
|
+
items: { type: "string" },
|
|
330
|
+
description: "Array of participant JIDs or phone numbers.",
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: ["jid", "action", "participants"],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
handler: async ({ jid, action, participants }, { sock }) => {
|
|
337
|
+
const gJid = _ensureGroupJid(jid);
|
|
338
|
+
const pJids = participants.map(phoneToJid);
|
|
339
|
+
const result = await sock.groupParticipantsUpdate(gJid, pJids, action);
|
|
340
|
+
return okResult({
|
|
341
|
+
status: action,
|
|
342
|
+
jid: gJid,
|
|
343
|
+
participants: result || pJids.map((p) => ({ jid: p, status: "ok" })),
|
|
344
|
+
});
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// 7. leave_group
|
|
349
|
+
{
|
|
350
|
+
definition: {
|
|
351
|
+
name: "leave_group",
|
|
352
|
+
description: "Leave a group.",
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: {
|
|
356
|
+
jid: { type: "string", description: "Group JID." },
|
|
357
|
+
},
|
|
358
|
+
required: ["jid"],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
handler: async ({ jid }, { sock }) => {
|
|
362
|
+
const gJid = _ensureGroupJid(jid);
|
|
363
|
+
await sock.groupLeave(gJid);
|
|
364
|
+
return okResult({ status: "left", jid: gJid });
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// 8. manage_group_invite
|
|
369
|
+
{
|
|
370
|
+
definition: {
|
|
371
|
+
name: "manage_group_invite",
|
|
372
|
+
description:
|
|
373
|
+
"Get, revoke, or join a group via invite link/code." +
|
|
374
|
+
" 'get' returns the current invite link, 'revoke' generates a new one," +
|
|
375
|
+
" 'join' joins the group given an invite code.",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: {
|
|
379
|
+
action: {
|
|
380
|
+
type: "string",
|
|
381
|
+
enum: ["get", "revoke", "join"],
|
|
382
|
+
description: "Action to perform.",
|
|
383
|
+
},
|
|
384
|
+
jid: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "Group JID (required for 'get' and 'revoke').",
|
|
387
|
+
},
|
|
388
|
+
code: {
|
|
389
|
+
type: "string",
|
|
390
|
+
description: "Invite code or full link (required for 'join'). E.g. 'ABcdEfGhIjK' or 'https://chat.whatsapp.com/ABcdEfGhIjK'.",
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
required: ["action"],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
handler: async ({ action, jid, code }, { sock }) => {
|
|
397
|
+
if (action === "get") {
|
|
398
|
+
if (!jid) return errResult("JID is required for 'get' action.");
|
|
399
|
+
const gJid = _ensureGroupJid(jid);
|
|
400
|
+
const inviteCode = await sock.groupInviteCode(gJid);
|
|
401
|
+
return okResult({
|
|
402
|
+
jid: gJid,
|
|
403
|
+
invite_code: inviteCode,
|
|
404
|
+
invite_link: `https://chat.whatsapp.com/${inviteCode}`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (action === "revoke") {
|
|
408
|
+
if (!jid) return errResult("JID is required for 'revoke' action.");
|
|
409
|
+
const gJid = _ensureGroupJid(jid);
|
|
410
|
+
const newCode = await sock.groupRevokeInvite(gJid);
|
|
411
|
+
return okResult({
|
|
412
|
+
jid: gJid,
|
|
413
|
+
invite_code: newCode,
|
|
414
|
+
invite_link: `https://chat.whatsapp.com/${newCode}`,
|
|
415
|
+
note: "Previous invite link has been revoked.",
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (action === "join") {
|
|
419
|
+
if (!code) return errResult("Invite code is required for 'join' action.");
|
|
420
|
+
// Extract code from full URL if given
|
|
421
|
+
const inviteCode = code.replace("https://chat.whatsapp.com/", "").trim();
|
|
422
|
+
const gJid = await sock.groupAcceptInvite(inviteCode);
|
|
423
|
+
return okResult({ status: "joined", jid: gJid, invite_code: inviteCode });
|
|
424
|
+
}
|
|
425
|
+
return errResult(`Unknown action: ${action}`);
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
// 9. update_group_settings
|
|
430
|
+
{
|
|
431
|
+
definition: {
|
|
432
|
+
name: "update_group_settings",
|
|
433
|
+
description:
|
|
434
|
+
"Update group settings: announcement mode (only admins send)," +
|
|
435
|
+
" locked mode (only admins edit info), disappearing messages, member add mode," +
|
|
436
|
+
" and join approval mode.",
|
|
437
|
+
inputSchema: {
|
|
438
|
+
type: "object",
|
|
439
|
+
properties: {
|
|
440
|
+
jid: { type: "string", description: "Group JID." },
|
|
441
|
+
announce: {
|
|
442
|
+
type: "boolean",
|
|
443
|
+
description: "true = only admins can send messages, false = all members can send.",
|
|
444
|
+
},
|
|
445
|
+
locked: {
|
|
446
|
+
type: "boolean",
|
|
447
|
+
description: "true = only admins can edit group info, false = all members can.",
|
|
448
|
+
},
|
|
449
|
+
ephemeral: {
|
|
450
|
+
type: "integer",
|
|
451
|
+
description: "Disappearing messages timer in seconds: 0=off, 86400=24h, 604800=7d, 7776000=90d.",
|
|
452
|
+
},
|
|
453
|
+
member_add_mode: {
|
|
454
|
+
type: "boolean",
|
|
455
|
+
description: "true = all members can add participants, false = only admins.",
|
|
456
|
+
},
|
|
457
|
+
join_approval_mode: {
|
|
458
|
+
type: "boolean",
|
|
459
|
+
description: "true = admin approval required for join requests.",
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
required: ["jid"],
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
handler: async ({ jid, announce, locked, ephemeral, member_add_mode, join_approval_mode }, { sock }) => {
|
|
466
|
+
const gJid = _ensureGroupJid(jid);
|
|
467
|
+
const updates = [];
|
|
468
|
+
|
|
469
|
+
if (announce !== undefined) {
|
|
470
|
+
await sock.groupSettingUpdate(gJid, announce ? "announcement" : "not_announcement");
|
|
471
|
+
updates.push(`announce=${announce}`);
|
|
472
|
+
}
|
|
473
|
+
if (locked !== undefined) {
|
|
474
|
+
await sock.groupSettingUpdate(gJid, locked ? "locked" : "unlocked");
|
|
475
|
+
updates.push(`locked=${locked}`);
|
|
476
|
+
}
|
|
477
|
+
if (ephemeral !== undefined) {
|
|
478
|
+
await sock.sendMessage(gJid, { disappearingMessagesInChat: ephemeral });
|
|
479
|
+
updates.push(`ephemeral=${ephemeral}`);
|
|
480
|
+
}
|
|
481
|
+
if (member_add_mode !== undefined) {
|
|
482
|
+
await sock.groupMemberAddMode(gJid, member_add_mode ? "all_member_add" : "admin_add");
|
|
483
|
+
updates.push(`member_add_mode=${member_add_mode}`);
|
|
484
|
+
}
|
|
485
|
+
if (join_approval_mode !== undefined) {
|
|
486
|
+
await sock.groupJoinApprovalMode(gJid, join_approval_mode ? "on" : "off");
|
|
487
|
+
updates.push(`join_approval_mode=${join_approval_mode}`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (updates.length === 0) {
|
|
491
|
+
return errResult("No settings provided. Specify at least one setting to update.");
|
|
492
|
+
}
|
|
493
|
+
return okResult({ status: "updated", jid: gJid, changes: updates });
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
// 10. set_group_picture
|
|
498
|
+
{
|
|
499
|
+
definition: {
|
|
500
|
+
name: "set_group_picture",
|
|
501
|
+
description: "Set or update the group profile picture.",
|
|
502
|
+
inputSchema: {
|
|
503
|
+
type: "object",
|
|
504
|
+
properties: {
|
|
505
|
+
jid: { type: "string", description: "Group JID." },
|
|
506
|
+
source: { type: "string", description: "Image source: URL, base64, or local file path." },
|
|
507
|
+
},
|
|
508
|
+
required: ["jid", "source"],
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
handler: async ({ jid, source }, { sock }) => {
|
|
512
|
+
const gJid = _ensureGroupJid(jid);
|
|
513
|
+
const media = resolveMedia(source);
|
|
514
|
+
// updateProfilePicture expects a Buffer
|
|
515
|
+
let imgBuf;
|
|
516
|
+
if (Buffer.isBuffer(media)) {
|
|
517
|
+
imgBuf = media;
|
|
518
|
+
} else if (media.url) {
|
|
519
|
+
// Fetch from URL
|
|
520
|
+
const resp = await fetch(media.url);
|
|
521
|
+
imgBuf = Buffer.from(await resp.arrayBuffer());
|
|
522
|
+
} else {
|
|
523
|
+
imgBuf = media;
|
|
524
|
+
}
|
|
525
|
+
await sock.updateProfilePicture(gJid, imgBuf);
|
|
526
|
+
return okResult({ status: "updated", jid: gJid });
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function sleep(ms) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function getMessageTimestampSeconds(message) {
|
|
8
|
+
if (!message?.messageTimestamp) return 0;
|
|
9
|
+
return typeof message.messageTimestamp === "number"
|
|
10
|
+
? message.messageTimestamp
|
|
11
|
+
: Number(message.messageTimestamp);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getMessageTimestampMs(message) {
|
|
15
|
+
return getMessageTimestampSeconds(message) * 1000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function fetchAdditionalHistory({
|
|
19
|
+
sock,
|
|
20
|
+
store,
|
|
21
|
+
jid,
|
|
22
|
+
beforeId,
|
|
23
|
+
limit = 50,
|
|
24
|
+
historyCount,
|
|
25
|
+
waitMs = 3500,
|
|
26
|
+
enabled = true,
|
|
27
|
+
}) {
|
|
28
|
+
const beforeCount = typeof store.countMessages === "function"
|
|
29
|
+
? store.countMessages(jid)
|
|
30
|
+
: (store.messages.get(jid) || []).length;
|
|
31
|
+
|
|
32
|
+
const result = {
|
|
33
|
+
enabled: enabled !== false,
|
|
34
|
+
requested: false,
|
|
35
|
+
received: false,
|
|
36
|
+
reason: null,
|
|
37
|
+
before_count: beforeCount,
|
|
38
|
+
after_count: beforeCount,
|
|
39
|
+
anchor_id: null,
|
|
40
|
+
requested_count: 0,
|
|
41
|
+
wait_ms: Math.max(250, Math.min(waitMs || 3500, 15000)),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (enabled === false) {
|
|
45
|
+
result.reason = "disabled";
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!sock || typeof sock.fetchMessageHistory !== "function") {
|
|
50
|
+
result.reason = "unsupported";
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let anchor = beforeId ? store.getMessage(beforeId) : null;
|
|
55
|
+
if (!anchor && typeof store.getOldestMessage === "function") {
|
|
56
|
+
anchor = store.getOldestMessage(jid);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!anchor?.key?.id || !anchor?.key?.remoteJid) {
|
|
60
|
+
result.reason = "no_anchor";
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const anchorTimestampSeconds = getMessageTimestampSeconds(anchor);
|
|
65
|
+
if (!anchorTimestampSeconds) {
|
|
66
|
+
result.reason = "missing_anchor_timestamp";
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const initialOldest = typeof store.getOldestMessage === "function"
|
|
71
|
+
? store.getOldestMessage(jid)
|
|
72
|
+
: anchor;
|
|
73
|
+
const initialOldestId = initialOldest?.key?.id || null;
|
|
74
|
+
const initialOldestTs = getMessageTimestampSeconds(initialOldest) || anchorTimestampSeconds;
|
|
75
|
+
const requestedCount = Math.max(1, Math.min(historyCount || Math.max(limit, 50), 200));
|
|
76
|
+
|
|
77
|
+
await sock.fetchMessageHistory(requestedCount, anchor.key, getMessageTimestampMs(anchor));
|
|
78
|
+
result.requested = true;
|
|
79
|
+
result.anchor_id = anchor.key.id;
|
|
80
|
+
result.requested_count = requestedCount;
|
|
81
|
+
|
|
82
|
+
const deadline = Date.now() + result.wait_ms;
|
|
83
|
+
while (Date.now() < deadline) {
|
|
84
|
+
await sleep(250);
|
|
85
|
+
|
|
86
|
+
const afterCount = typeof store.countMessages === "function"
|
|
87
|
+
? store.countMessages(jid)
|
|
88
|
+
: (store.messages.get(jid) || []).length;
|
|
89
|
+
const oldest = typeof store.getOldestMessage === "function"
|
|
90
|
+
? store.getOldestMessage(jid)
|
|
91
|
+
: null;
|
|
92
|
+
const oldestId = oldest?.key?.id || null;
|
|
93
|
+
const oldestTs = getMessageTimestampSeconds(oldest);
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
afterCount > beforeCount
|
|
97
|
+
|| (oldestId && oldestId !== initialOldestId)
|
|
98
|
+
|| (oldestTs && oldestTs < initialOldestTs)
|
|
99
|
+
) {
|
|
100
|
+
result.received = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
result.after_count = typeof store.countMessages === "function"
|
|
106
|
+
? store.countMessages(jid)
|
|
107
|
+
: (store.messages.get(jid) || []).length;
|
|
108
|
+
result.reason = result.received ? "history_updated" : "timeout";
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
fetchAdditionalHistory,
|
|
114
|
+
};
|