@whanext/core 0.5.0 → 0.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/CHANGELOG.md +43 -0
- package/CONTRIBUTING.md +3 -0
- package/README.md +84 -6
- package/SECURITY.md +1 -1
- package/dist/index.d.ts +33 -2
- package/dist/index.js +140 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/cache/memory-cache.ts
|
|
2
2
|
var MemoryCache = class {
|
|
3
3
|
#entries = /* @__PURE__ */ new Map();
|
|
4
|
+
#maxEntries;
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.#maxEntries = Math.max(1, options.maxEntries ?? 1e3);
|
|
7
|
+
}
|
|
4
8
|
async get(key) {
|
|
5
9
|
const entry = this.#entries.get(key);
|
|
6
10
|
if (!entry) {
|
|
@@ -10,6 +14,8 @@ var MemoryCache = class {
|
|
|
10
14
|
this.#entries.delete(key);
|
|
11
15
|
return void 0;
|
|
12
16
|
}
|
|
17
|
+
this.#entries.delete(key);
|
|
18
|
+
this.#entries.set(key, entry);
|
|
13
19
|
return entry.value;
|
|
14
20
|
}
|
|
15
21
|
async set(key, value, ttlMs) {
|
|
@@ -18,6 +24,11 @@ var MemoryCache = class {
|
|
|
18
24
|
entry.expiresAt = Date.now() + ttlMs;
|
|
19
25
|
}
|
|
20
26
|
this.#entries.set(key, entry);
|
|
27
|
+
while (this.#entries.size > this.#maxEntries) {
|
|
28
|
+
const oldest = this.#entries.keys().next().value;
|
|
29
|
+
if (oldest === void 0) return;
|
|
30
|
+
this.#entries.delete(oldest);
|
|
31
|
+
}
|
|
21
32
|
}
|
|
22
33
|
async delete(key) {
|
|
23
34
|
this.#entries.delete(key);
|
|
@@ -932,6 +943,7 @@ var GroupService = class {
|
|
|
932
943
|
#provider;
|
|
933
944
|
#cache;
|
|
934
945
|
#ttlMs;
|
|
946
|
+
#requests = /* @__PURE__ */ new Map();
|
|
935
947
|
constructor(provider, cache, ttlMs = 3e5) {
|
|
936
948
|
this.#provider = provider;
|
|
937
949
|
this.#cache = cache;
|
|
@@ -979,9 +991,17 @@ var GroupService = class {
|
|
|
979
991
|
return cached;
|
|
980
992
|
}
|
|
981
993
|
}
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
994
|
+
const pending = this.#requests.get(key);
|
|
995
|
+
if (pending) {
|
|
996
|
+
return pending;
|
|
997
|
+
}
|
|
998
|
+
const request = this.#loadMetadata(key, groupId);
|
|
999
|
+
this.#requests.set(key, request);
|
|
1000
|
+
try {
|
|
1001
|
+
return await request;
|
|
1002
|
+
} finally {
|
|
1003
|
+
this.#requests.delete(key);
|
|
1004
|
+
}
|
|
985
1005
|
}
|
|
986
1006
|
async isAdmin(groupId, memberIds) {
|
|
987
1007
|
if (!groupId.endsWith("@g.us")) {
|
|
@@ -1030,6 +1050,11 @@ var GroupService = class {
|
|
|
1030
1050
|
#key(groupId) {
|
|
1031
1051
|
return `group:${groupId}`;
|
|
1032
1052
|
}
|
|
1053
|
+
async #loadMetadata(key, groupId) {
|
|
1054
|
+
const group = await this.#provider.getGroup(groupId);
|
|
1055
|
+
await this.#cache.set(key, group, this.#ttlMs);
|
|
1056
|
+
return group;
|
|
1057
|
+
}
|
|
1033
1058
|
#matchesParticipant(participant, identities) {
|
|
1034
1059
|
const participantIds = [participant.id, participant.lid, participant.phoneNumber].filter((identity) => identity !== void 0);
|
|
1035
1060
|
return identities.some((identity) => participantIds.some((participantId) => identitiesMatch(identity, participantId)));
|
|
@@ -1051,6 +1076,10 @@ var MediaService = class {
|
|
|
1051
1076
|
audio(chatId, content) {
|
|
1052
1077
|
return this.#provider.sendMessage(chatId, content);
|
|
1053
1078
|
}
|
|
1079
|
+
download(message) {
|
|
1080
|
+
const key = "keys" in message ? message.keys : message;
|
|
1081
|
+
return this.#provider.downloadMedia(key);
|
|
1082
|
+
}
|
|
1054
1083
|
};
|
|
1055
1084
|
|
|
1056
1085
|
// src/services/member-service.ts
|
|
@@ -1141,6 +1170,14 @@ var MessageService = class {
|
|
|
1141
1170
|
const key = "keys" in message ? message.keys : message;
|
|
1142
1171
|
return this.#provider.deleteMessage(key);
|
|
1143
1172
|
}
|
|
1173
|
+
react(message, emoji) {
|
|
1174
|
+
const key = "keys" in message ? message.keys : message;
|
|
1175
|
+
return this.#provider.reactToMessage(key, emoji);
|
|
1176
|
+
}
|
|
1177
|
+
unreact(message) {
|
|
1178
|
+
const key = "keys" in message ? message.keys : message;
|
|
1179
|
+
return this.#provider.reactToMessage(key);
|
|
1180
|
+
}
|
|
1144
1181
|
text(chatId, text, mentions) {
|
|
1145
1182
|
const content = { text };
|
|
1146
1183
|
if (mentions !== void 0) {
|
|
@@ -1199,7 +1236,9 @@ var WhaNextApp = class {
|
|
|
1199
1236
|
this.#provider = provider;
|
|
1200
1237
|
this.#phone = options.phone;
|
|
1201
1238
|
this.logger = logger;
|
|
1202
|
-
const cache = options.cache?.store ?? new MemoryCache(
|
|
1239
|
+
const cache = options.cache?.store ?? new MemoryCache(
|
|
1240
|
+
options.cache?.memoryMaxEntries === void 0 ? void 0 : { maxEntries: options.cache.memoryMaxEntries }
|
|
1241
|
+
);
|
|
1203
1242
|
this.group = new GroupService(provider, cache, options.cache?.groupTtlMs);
|
|
1204
1243
|
this.member = new MemberService(provider, this.group);
|
|
1205
1244
|
this.message = new MessageService(provider);
|
|
@@ -1307,6 +1346,10 @@ var WhaNextApp = class {
|
|
|
1307
1346
|
await this.#events.emit("connection", update);
|
|
1308
1347
|
});
|
|
1309
1348
|
this.#provider.on("groupChanged", ({ groupId }) => this.group.invalidate(groupId));
|
|
1349
|
+
this.#provider.on("groupParticipantsChanged", async (change) => {
|
|
1350
|
+
await this.group.invalidate(change.groupId);
|
|
1351
|
+
await this.#events.emit("groupParticipantsChanged", change);
|
|
1352
|
+
});
|
|
1310
1353
|
this.#provider.on("call", async (call) => {
|
|
1311
1354
|
this.logger.debug("Call received", {
|
|
1312
1355
|
callId: call.id,
|
|
@@ -1401,6 +1444,7 @@ var Browser = /* @__PURE__ */ ((Browser2) => {
|
|
|
1401
1444
|
import {
|
|
1402
1445
|
Browsers,
|
|
1403
1446
|
DisconnectReason,
|
|
1447
|
+
downloadMediaMessage,
|
|
1404
1448
|
makeWASocket,
|
|
1405
1449
|
proto,
|
|
1406
1450
|
useMultiFileAuthState
|
|
@@ -1611,6 +1655,7 @@ var BaileysProvider = class {
|
|
|
1611
1655
|
#events = new TypedEventEmitter();
|
|
1612
1656
|
#logger;
|
|
1613
1657
|
#messageStore = /* @__PURE__ */ new Map();
|
|
1658
|
+
#messageCacheSize;
|
|
1614
1659
|
#socket;
|
|
1615
1660
|
#saveCredentials;
|
|
1616
1661
|
#saveQueue = Promise.resolve();
|
|
@@ -1621,6 +1666,7 @@ var BaileysProvider = class {
|
|
|
1621
1666
|
constructor(options) {
|
|
1622
1667
|
this.#options = options;
|
|
1623
1668
|
this.#logger = options.logger ?? new Logger("silent");
|
|
1669
|
+
this.#messageCacheSize = Math.max(1, options.messageCacheSize ?? 1e3);
|
|
1624
1670
|
}
|
|
1625
1671
|
on(event, listener) {
|
|
1626
1672
|
return this.#events.on(event, listener);
|
|
@@ -1645,7 +1691,7 @@ var BaileysProvider = class {
|
|
|
1645
1691
|
markOnlineOnConnect: false,
|
|
1646
1692
|
enableAutoSessionRecreation: true,
|
|
1647
1693
|
enableRecentMessageCache: true,
|
|
1648
|
-
getMessage: async (key) =>
|
|
1694
|
+
getMessage: async (key) => this.#messageStore.get(this.#messageStoreKey(key))?.message ?? void 0
|
|
1649
1695
|
});
|
|
1650
1696
|
this.#socket = socket;
|
|
1651
1697
|
this.#bind(socket);
|
|
@@ -1724,6 +1770,39 @@ var BaileysProvider = class {
|
|
|
1724
1770
|
const result = await socket.sendMessage(chatId, this.#toContent(content), options);
|
|
1725
1771
|
return this.#sent(result);
|
|
1726
1772
|
}
|
|
1773
|
+
async reactToMessage(key, emoji) {
|
|
1774
|
+
const result = await this.#requireSocket().sendMessage(key.chatId, {
|
|
1775
|
+
react: {
|
|
1776
|
+
text: emoji ?? "",
|
|
1777
|
+
key: this.#toWaKey(key)
|
|
1778
|
+
}
|
|
1779
|
+
});
|
|
1780
|
+
return this.#sent(result);
|
|
1781
|
+
}
|
|
1782
|
+
async downloadMedia(key) {
|
|
1783
|
+
const message = this.#messageStore.get(this.#messageStoreKey(key));
|
|
1784
|
+
if (!message?.message) {
|
|
1785
|
+
throw new WhaNextError(
|
|
1786
|
+
"MEDIA_NOT_AVAILABLE",
|
|
1787
|
+
"The media is unavailable. Download it from the received message event while it is cached.",
|
|
1788
|
+
{ recoverable: true }
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
const data = await downloadMediaMessage(message, "buffer", {}, {
|
|
1792
|
+
reuploadRequest: (current) => this.#requireSocket().updateMediaMessage(current),
|
|
1793
|
+
logger: createBaileysLogger(this.#logger.child("media"))
|
|
1794
|
+
});
|
|
1795
|
+
const media = normalizeBaileysMessage(message)?.media;
|
|
1796
|
+
if (!media) {
|
|
1797
|
+
throw new WhaNextError("MEDIA_NOT_AVAILABLE", "The selected message does not contain media.");
|
|
1798
|
+
}
|
|
1799
|
+
return {
|
|
1800
|
+
data,
|
|
1801
|
+
kind: media.kind,
|
|
1802
|
+
...media.mimetype ? { mimetype: media.mimetype } : {},
|
|
1803
|
+
...media.fileName ? { fileName: media.fileName } : {}
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1727
1806
|
async editMessage(key, content) {
|
|
1728
1807
|
const result = await this.#requireSocket().sendMessage(key.chatId, {
|
|
1729
1808
|
text: content,
|
|
@@ -1792,7 +1871,6 @@ var BaileysProvider = class {
|
|
|
1792
1871
|
}
|
|
1793
1872
|
async setPresence(chatId, state) {
|
|
1794
1873
|
const socket = this.#requireSocket();
|
|
1795
|
-
await socket.presenceSubscribe(chatId);
|
|
1796
1874
|
const presence = state === "typing" ? "composing" : state === "recording" ? "recording" : "paused";
|
|
1797
1875
|
await socket.sendPresenceUpdate(presence, chatId);
|
|
1798
1876
|
}
|
|
@@ -1809,7 +1887,7 @@ var BaileysProvider = class {
|
|
|
1809
1887
|
socket.ev.on("messages.upsert", ({ messages, type }) => {
|
|
1810
1888
|
if (type !== "notify") return;
|
|
1811
1889
|
for (const raw of messages) {
|
|
1812
|
-
if (raw.key.id && raw.message) this.#remember(raw
|
|
1890
|
+
if (raw.key.id && raw.message) this.#remember(raw);
|
|
1813
1891
|
const message = normalizeBaileysMessage(raw);
|
|
1814
1892
|
if (message) void this.#events.emit("message", message);
|
|
1815
1893
|
}
|
|
@@ -1819,7 +1897,10 @@ var BaileysProvider = class {
|
|
|
1819
1897
|
if (group.id) void this.#events.emit("groupChanged", { groupId: group.id });
|
|
1820
1898
|
}
|
|
1821
1899
|
});
|
|
1822
|
-
socket.ev.on("group-participants.update", (
|
|
1900
|
+
socket.ev.on("group-participants.update", (update) => {
|
|
1901
|
+
const change = this.#groupParticipantsChanged(update);
|
|
1902
|
+
void this.#events.emit("groupParticipantsChanged", change);
|
|
1903
|
+
const { id } = update;
|
|
1823
1904
|
void this.#events.emit("groupChanged", { groupId: id });
|
|
1824
1905
|
});
|
|
1825
1906
|
socket.ev.on("call", (calls) => {
|
|
@@ -1949,7 +2030,7 @@ var BaileysProvider = class {
|
|
|
1949
2030
|
if (!message?.key.id || !message.key.remoteJid) {
|
|
1950
2031
|
throw new WhaNextError("PROVIDER_ERROR", "WhatsApp did not confirm the sent message.");
|
|
1951
2032
|
}
|
|
1952
|
-
if (message.message) this.#remember(message
|
|
2033
|
+
if (message.message) this.#remember(message);
|
|
1953
2034
|
return {
|
|
1954
2035
|
id: message.key.id,
|
|
1955
2036
|
chatId: message.key.remoteJid,
|
|
@@ -1968,6 +2049,15 @@ var BaileysProvider = class {
|
|
|
1968
2049
|
date: call.date ?? /* @__PURE__ */ new Date()
|
|
1969
2050
|
};
|
|
1970
2051
|
}
|
|
2052
|
+
#groupParticipantsChanged(change) {
|
|
2053
|
+
const participantIds = change.participants.map((participant) => participant.id).filter((id) => Boolean(id));
|
|
2054
|
+
return {
|
|
2055
|
+
groupId: change.id,
|
|
2056
|
+
action: change.action,
|
|
2057
|
+
participantIds,
|
|
2058
|
+
...change.author ? { authorId: change.author } : {}
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
1971
2061
|
#callStatus(status) {
|
|
1972
2062
|
const known = [
|
|
1973
2063
|
"offer",
|
|
@@ -1979,13 +2069,19 @@ var BaileysProvider = class {
|
|
|
1979
2069
|
];
|
|
1980
2070
|
return known.find((value) => value === status) ?? "timeout";
|
|
1981
2071
|
}
|
|
1982
|
-
#remember(
|
|
1983
|
-
this.#
|
|
1984
|
-
|
|
2072
|
+
#remember(message) {
|
|
2073
|
+
const key = this.#messageStoreKey(message.key);
|
|
2074
|
+
this.#messageStore.delete(key);
|
|
2075
|
+
this.#messageStore.set(key, message);
|
|
2076
|
+
while (this.#messageStore.size > this.#messageCacheSize) {
|
|
1985
2077
|
const oldest = this.#messageStore.keys().next().value;
|
|
1986
2078
|
if (oldest) this.#messageStore.delete(oldest);
|
|
1987
2079
|
}
|
|
1988
2080
|
}
|
|
2081
|
+
#messageStoreKey(key) {
|
|
2082
|
+
const chatId = "chatId" in key ? key.chatId : key.remoteJid;
|
|
2083
|
+
return `${chatId ?? ""}:${key.id ?? ""}`;
|
|
2084
|
+
}
|
|
1989
2085
|
};
|
|
1990
2086
|
|
|
1991
2087
|
// src/app/create.ts
|
|
@@ -1995,6 +2091,7 @@ async function create(options = {}) {
|
|
|
1995
2091
|
auth: options.auth ?? "./session",
|
|
1996
2092
|
browser: options.browser ?? "windows" /* Windows */,
|
|
1997
2093
|
logger: logger.child("provider"),
|
|
2094
|
+
...options.messageCacheSize !== void 0 ? { messageCacheSize: options.messageCacheSize } : {},
|
|
1998
2095
|
...options.reconnect ? { reconnect: options.reconnect } : {}
|
|
1999
2096
|
});
|
|
2000
2097
|
return new WhaNextApp(provider, {
|
|
@@ -2011,6 +2108,9 @@ async function create(options = {}) {
|
|
|
2011
2108
|
function defineCommand(command) {
|
|
2012
2109
|
return command;
|
|
2013
2110
|
}
|
|
2111
|
+
function defineCommands(...commands) {
|
|
2112
|
+
return commands;
|
|
2113
|
+
}
|
|
2014
2114
|
|
|
2015
2115
|
// src/commands/load-commands.ts
|
|
2016
2116
|
import { readdir } from "fs/promises";
|
|
@@ -2023,16 +2123,20 @@ async function loadCommands(registrar, dirPath, options = {}) {
|
|
|
2023
2123
|
const entries = await readEntries(dirPath, recursive);
|
|
2024
2124
|
const loaded = [];
|
|
2025
2125
|
const skipped = [];
|
|
2126
|
+
const commands = [];
|
|
2026
2127
|
for (const filePath of entries) {
|
|
2027
2128
|
if (!extensions.includes(path.extname(filePath))) {
|
|
2028
2129
|
skipped.push(filePath);
|
|
2029
2130
|
continue;
|
|
2030
2131
|
}
|
|
2031
|
-
const
|
|
2032
|
-
|
|
2132
|
+
const definitions = await importCommands(filePath);
|
|
2133
|
+
for (const definition of definitions) {
|
|
2134
|
+
registrar.command(definition);
|
|
2135
|
+
commands.push({ name: definition.name, filePath });
|
|
2136
|
+
}
|
|
2033
2137
|
loaded.push(filePath);
|
|
2034
2138
|
}
|
|
2035
|
-
return { loaded, skipped };
|
|
2139
|
+
return { loaded, skipped, commands };
|
|
2036
2140
|
}
|
|
2037
2141
|
async function readEntries(dirPath, recursive) {
|
|
2038
2142
|
let dirents;
|
|
@@ -2044,9 +2148,9 @@ async function readEntries(dirPath, recursive) {
|
|
|
2044
2148
|
context: { dirPath }
|
|
2045
2149
|
});
|
|
2046
2150
|
}
|
|
2047
|
-
return dirents.filter((dirent) => dirent.isFile()).map((dirent) => path.join(dirent.parentPath, dirent.name));
|
|
2151
|
+
return dirents.filter((dirent) => dirent.isFile()).map((dirent) => path.join(dirent.parentPath, dirent.name)).sort((left, right) => left.localeCompare(right));
|
|
2048
2152
|
}
|
|
2049
|
-
async function
|
|
2153
|
+
async function importCommands(filePath) {
|
|
2050
2154
|
let module;
|
|
2051
2155
|
try {
|
|
2052
2156
|
module = await import(pathToFileURL(filePath).href);
|
|
@@ -2056,13 +2160,27 @@ async function importCommand(filePath) {
|
|
|
2056
2160
|
context: { filePath }
|
|
2057
2161
|
});
|
|
2058
2162
|
}
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
2163
|
+
const definitions = [];
|
|
2164
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2165
|
+
const exports = [
|
|
2166
|
+
module.default,
|
|
2167
|
+
...Object.entries(module).filter(([name]) => name !== "default").map(([, value]) => value)
|
|
2168
|
+
];
|
|
2169
|
+
for (const value of exports) {
|
|
2170
|
+
const candidates = Array.isArray(value) ? value : [value];
|
|
2171
|
+
for (const candidate of candidates) {
|
|
2172
|
+
if (isCommandDefinition(candidate) && !seen.has(candidate)) {
|
|
2173
|
+
seen.add(candidate);
|
|
2174
|
+
definitions.push(candidate);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
if (definitions.length === 0) {
|
|
2179
|
+
throw new WhaNextError("COMMAND_LOAD_FAILED", `The file "${filePath}" does not export any valid commands.`, {
|
|
2062
2180
|
context: { filePath }
|
|
2063
2181
|
});
|
|
2064
2182
|
}
|
|
2065
|
-
return
|
|
2183
|
+
return definitions;
|
|
2066
2184
|
}
|
|
2067
2185
|
function isCommandDefinition(value) {
|
|
2068
2186
|
return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.execute === "function";
|
|
@@ -2080,6 +2198,7 @@ export {
|
|
|
2080
2198
|
WhaNextError,
|
|
2081
2199
|
create,
|
|
2082
2200
|
defineCommand,
|
|
2201
|
+
defineCommands,
|
|
2083
2202
|
loadCommands,
|
|
2084
2203
|
toWhaNextError
|
|
2085
2204
|
};
|