@vanzxy/baileys 1.6.2 → 1.6.4
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.
Potentially problematic release.
This version of @vanzxy/baileys might be problematic. Click here for more details.
- package/NOTICE.md +50 -0
- package/lib/Utils/A2UI.js +217 -0
- package/lib/Utils/MessageBuilder.js +332 -46
- package/lib/Utils/MessageBuilder_d.ts +45 -0
- package/lib/Utils/PersistentStore.js +592 -0
- package/lib/Utils/PersistentStore_d.ts +60 -0
- package/lib/Utils/anti-delete.d.ts +68 -0
- package/lib/Utils/anti-delete.js +185 -0
- package/lib/Utils/auto-reply.d.ts +47 -0
- package/lib/Utils/auto-reply.js +155 -0
- package/lib/Utils/button-helper-utils.js +314 -0
- package/lib/Utils/button-sender.js +817 -0
- package/lib/Utils/chat-history-helpers.d.ts +21 -0
- package/lib/Utils/chat-history-helpers.js +71 -0
- package/lib/Utils/index.d.ts +11 -0
- package/lib/Utils/index.js +16 -0
- package/lib/Utils/media-messages.d.ts +18 -0
- package/lib/Utils/media-messages.js +71 -0
- package/lib/Utils/media-set.d.ts +13 -0
- package/lib/Utils/media-set.js +165 -0
- package/lib/Utils/message-kind.js +139 -0
- package/lib/Utils/message-search.d.ts +44 -0
- package/lib/Utils/message-search.js +174 -0
- package/lib/Utils/scheduling.d.ts +42 -0
- package/lib/Utils/scheduling.js +140 -0
- package/lib/Utils/status.d.ts +50 -0
- package/lib/Utils/status.js +108 -0
- package/lib/Utils/stickerpack.d.ts +51 -0
- package/lib/Utils/stickerpack.js +276 -0
- package/lib/Utils/templates.d.ts +76 -0
- package/lib/Utils/templates.js +151 -0
- package/lib/Utils/use-sqlite-auth-state.js +28 -1
- package/lib/Utils/vcard.d.ts +58 -0
- package/lib/Utils/vcard.js +94 -0
- package/lib/VoIP/audio-feeder.d.ts +15 -0
- package/lib/VoIP/audio-feeder.js +132 -0
- package/lib/VoIP/index.js +277 -0
- package/lib/VoIP/relay-transport.d.ts +43 -0
- package/lib/VoIP/relay-transport.js +559 -0
- package/lib/VoIP/signaling.js +624 -0
- package/lib/VoIP/types.d.ts +69 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +103 -0
- package/lib/VoIP/wasm-engine.js +1214 -0
- package/lib/VoIP/worker-bootstrap.js +1042 -0
- package/lib/WABinary/generic-utils.js +8 -0
- package/lib/assets/wasm/loader.js +5 -0
- package/lib/assets/wasm/whatsapp.wasm +0 -0
- package/lib/assets/wasm/worker-modules.js +273 -0
- package/lib/index.js +4 -0
- package/package.json +22 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vanz@Add --- ported from Bail-master addons/stickerpack.ts.
|
|
3
|
+
*
|
|
4
|
+
* Adds support for building/sending WhatsApp Sticker Pack messages.
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* - convertToWebP() — converts a Buffer, URL string, or Stream into a WebP
|
|
8
|
+
* sticker buffer (passthrough if already WebP), sharp → @napi-rs/image
|
|
9
|
+
* fallback chain, 512x512 'inside' fit, quality 80.
|
|
10
|
+
* - generateStickerPackId() — generates a random pack ID
|
|
11
|
+
* - buildStickerPackProto() — builds the proto-level payload for a StickerPackMessage
|
|
12
|
+
* - STICKER_PACK_MESSAGE_TYPE — the message type string 'sticker_pack'
|
|
13
|
+
* - prepareStickerPackMessageItsliaaa() — full ZIP-build + upload + thumbnail
|
|
14
|
+
* pipeline for a ready-to-send stickerPackMessage.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: source imported isWebPBuffer/isAnimatedWebP from its own
|
|
17
|
+
* from-messages.ts, which is not part of this fork's port. Reimplemented
|
|
18
|
+
* below as small standalone binary-sniffing helpers (RIFF/WEBP header +
|
|
19
|
+
* ANIM chunk check) so this file has no external dependency on that addon.
|
|
20
|
+
*/
|
|
21
|
+
import { Boom } from '@hapi/boom';
|
|
22
|
+
import { zip } from 'fflate';
|
|
23
|
+
import { promises as fsPromises } from 'fs';
|
|
24
|
+
import { proto } from '../../WAProto/index.js';
|
|
25
|
+
import { sha256 } from './crypto.js';
|
|
26
|
+
import { generateMessageIDV2, unixTimestampSeconds } from './generics.js';
|
|
27
|
+
import { encryptedStream, getImageProcessingLibrary, getStream, toBuffer } from './messages-media.js';
|
|
28
|
+
/** True if `buffer` starts with a RIFF....WEBP container header. */
|
|
29
|
+
export const isWebPBuffer = (buffer) => {
|
|
30
|
+
return (buffer.length > 12 &&
|
|
31
|
+
buffer.toString('ascii', 0, 4) === 'RIFF' &&
|
|
32
|
+
buffer.toString('ascii', 8, 12) === 'WEBP');
|
|
33
|
+
};
|
|
34
|
+
/** True if a WebP buffer contains an ANIM chunk (animated sticker). */
|
|
35
|
+
export const isAnimatedWebP = (buffer) => {
|
|
36
|
+
return isWebPBuffer(buffer) && buffer.includes(Buffer.from('ANIM', 'ascii'));
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Convert a Buffer, URL string, or Stream into a WebP sticker buffer.
|
|
40
|
+
* If the input is already a valid WebP, it's returned untouched (and
|
|
41
|
+
* `isAnimated` reflects whether it's an animated WebP).
|
|
42
|
+
*/
|
|
43
|
+
export const convertToWebP = async (input) => {
|
|
44
|
+
const { stream } = await getStream(input);
|
|
45
|
+
const buffer = await toBuffer(stream);
|
|
46
|
+
if (isWebPBuffer(buffer)) {
|
|
47
|
+
return { buffer, isAnimated: isAnimatedWebP(buffer) };
|
|
48
|
+
}
|
|
49
|
+
const lib = await getImageProcessingLibrary();
|
|
50
|
+
const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
|
|
51
|
+
const hasImage = 'image' in lib && !!lib.image?.Transformer;
|
|
52
|
+
if (!hasSharp && !hasImage) {
|
|
53
|
+
throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
|
|
54
|
+
}
|
|
55
|
+
let webpBuffer;
|
|
56
|
+
if (hasSharp) {
|
|
57
|
+
webpBuffer = await lib.sharp
|
|
58
|
+
.default(buffer)
|
|
59
|
+
.resize(512, 512, { fit: 'inside' })
|
|
60
|
+
.webp({ quality: 80 })
|
|
61
|
+
.toBuffer();
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
webpBuffer = await new lib.image.Transformer(buffer).resize(512, 512).webp(80);
|
|
65
|
+
}
|
|
66
|
+
return { buffer: webpBuffer, isAnimated: false };
|
|
67
|
+
};
|
|
68
|
+
/** Generate a random sticker pack ID (16 hex chars). */
|
|
69
|
+
export const generateStickerPackId = () => {
|
|
70
|
+
const arr = new Uint8Array(8);
|
|
71
|
+
for (let i = 0; i < 8; i++)
|
|
72
|
+
arr[i] = Math.floor(Math.random() * 256);
|
|
73
|
+
return Array.from(arr)
|
|
74
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
75
|
+
.join('');
|
|
76
|
+
};
|
|
77
|
+
/** Build the proto-level stickerPackMessage payload (name/publisher/packId/description only). */
|
|
78
|
+
export const buildStickerPackProto = (pack) => ({
|
|
79
|
+
name: pack.name,
|
|
80
|
+
publisher: pack.publisher,
|
|
81
|
+
packId: pack.packId ?? generateStickerPackId(),
|
|
82
|
+
description: pack.description ?? ''
|
|
83
|
+
});
|
|
84
|
+
/** stickerPack message type marker — for use with getMediaType()-style dispatch. */
|
|
85
|
+
export const STICKER_PACK_MESSAGE_TYPE = 'sticker_pack';
|
|
86
|
+
const ITSL_CONCURRENCY_LIMIT = 15;
|
|
87
|
+
/**
|
|
88
|
+
* Build a complete, ready-to-send stickerPackMessage (ZIP built, encrypted,
|
|
89
|
+
* and uploaded) — full pipeline: per-sticker WebP conversion (15-way
|
|
90
|
+
* concurrency batching), 1MB per-sticker size limit, 60-sticker pack limit,
|
|
91
|
+
* cover→trayIcon-in-ZIP, and a separate 252×252 JPEG thumbnail upload.
|
|
92
|
+
*/
|
|
93
|
+
export const prepareStickerPackMessageItsliaaa = async (message, options) => {
|
|
94
|
+
const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: itsliaaa', description = '🏷️ itsliaaa/baileys' } = message;
|
|
95
|
+
if (stickers.length > 60) {
|
|
96
|
+
throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
|
|
97
|
+
}
|
|
98
|
+
if (stickers.length === 0) {
|
|
99
|
+
throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
|
|
100
|
+
}
|
|
101
|
+
if (!cover) {
|
|
102
|
+
throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
|
|
103
|
+
}
|
|
104
|
+
const { logger } = options;
|
|
105
|
+
// Media caching (keyed by concatenated sticker URLs, if all stickers are URL-based)
|
|
106
|
+
let cacheableKey = false;
|
|
107
|
+
if (stickers.length && options.mediaCache) {
|
|
108
|
+
const urls = [];
|
|
109
|
+
for (const s of stickers) {
|
|
110
|
+
const data = s.data;
|
|
111
|
+
if (typeof data === 'object' && data?.url)
|
|
112
|
+
urls.push(data.url);
|
|
113
|
+
}
|
|
114
|
+
if (urls.length > 0)
|
|
115
|
+
cacheableKey = 'sticker:' + urls.join('@');
|
|
116
|
+
}
|
|
117
|
+
if (cacheableKey) {
|
|
118
|
+
const mediaBuff = await options.mediaCache.get(cacheableKey);
|
|
119
|
+
if (mediaBuff) {
|
|
120
|
+
logger?.debug({ cacheableKey }, 'got media cache hit');
|
|
121
|
+
return proto.Message.StickerPackMessage.decode(mediaBuff);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const lib = await getImageProcessingLibrary();
|
|
125
|
+
const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
|
|
126
|
+
const hasImage = 'image' in lib && !!lib.image?.Transformer;
|
|
127
|
+
const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
|
|
128
|
+
if (!hasSharp && !hasImage) {
|
|
129
|
+
throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
|
|
130
|
+
}
|
|
131
|
+
const stickerPackIdValue = generateMessageIDV2();
|
|
132
|
+
const stickerData = {};
|
|
133
|
+
const stickerMetadata = new Array(stickers.length);
|
|
134
|
+
for (let i = 0; i < stickers.length; i += ITSL_CONCURRENCY_LIMIT) {
|
|
135
|
+
const chunkEnd = Math.min(i + ITSL_CONCURRENCY_LIMIT, stickers.length);
|
|
136
|
+
const promises = [];
|
|
137
|
+
for (let j = i; j < chunkEnd; j++) {
|
|
138
|
+
promises.push((async (index) => {
|
|
139
|
+
const sticker = stickers[index];
|
|
140
|
+
const { stream } = await getStream(sticker.data);
|
|
141
|
+
const buffer = await toBuffer(stream);
|
|
142
|
+
let webpBuffer;
|
|
143
|
+
let isAnimated = false;
|
|
144
|
+
if (isWebPBuffer(buffer)) {
|
|
145
|
+
webpBuffer = buffer;
|
|
146
|
+
isAnimated = isAnimatedWebP(buffer);
|
|
147
|
+
}
|
|
148
|
+
else if (hasSharp) {
|
|
149
|
+
webpBuffer = await lib.sharp
|
|
150
|
+
.default(buffer)
|
|
151
|
+
.resize(512, 512, { fit: 'inside' })
|
|
152
|
+
.webp({ quality: 80 })
|
|
153
|
+
.toBuffer();
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
webpBuffer = await new lib.image.Transformer(buffer).resize(512, 512).webp(80);
|
|
157
|
+
}
|
|
158
|
+
if (webpBuffer.length > 1024 * 1024) {
|
|
159
|
+
throw new Boom(`Sticker at index ${index} exceeds the 1MB size limit`, { statusCode: 400 });
|
|
160
|
+
}
|
|
161
|
+
const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
|
|
162
|
+
const fileName = `${hash}.webp`;
|
|
163
|
+
stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
|
|
164
|
+
stickerMetadata[index] = {
|
|
165
|
+
fileName,
|
|
166
|
+
mimetype: 'image/webp',
|
|
167
|
+
isAnimated,
|
|
168
|
+
emojis: sticker.emojis || ['✨'],
|
|
169
|
+
accessibilityLabel: sticker.accessibilityLabel || ''
|
|
170
|
+
};
|
|
171
|
+
})(j));
|
|
172
|
+
}
|
|
173
|
+
await Promise.all(promises);
|
|
174
|
+
}
|
|
175
|
+
const trayIconFileName = `${stickerPackIdValue}.webp`;
|
|
176
|
+
const { stream: coverStream } = await getStream(cover);
|
|
177
|
+
const coverBuffer = await toBuffer(coverStream);
|
|
178
|
+
let coverWebpBuffer;
|
|
179
|
+
if (isWebPBuffer(coverBuffer)) {
|
|
180
|
+
coverWebpBuffer = coverBuffer;
|
|
181
|
+
}
|
|
182
|
+
else if (hasSharp) {
|
|
183
|
+
coverWebpBuffer = await lib.sharp
|
|
184
|
+
.default(coverBuffer)
|
|
185
|
+
.resize(512, 512, { fit: 'inside' })
|
|
186
|
+
.webp({ quality: 80 })
|
|
187
|
+
.toBuffer();
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
coverWebpBuffer = await new lib.image.Transformer(coverBuffer).resize(512, 512).webp(80);
|
|
191
|
+
}
|
|
192
|
+
stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
|
|
193
|
+
const zipBuffer = await new Promise((resolve, reject) => {
|
|
194
|
+
zip(stickerData, (error, data) => (error ? reject(error) : resolve(Buffer.from(data))));
|
|
195
|
+
});
|
|
196
|
+
const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', { logger, opts: options.options });
|
|
197
|
+
let stickerPackUploadResult;
|
|
198
|
+
try {
|
|
199
|
+
stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
|
|
200
|
+
fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
|
|
201
|
+
mediaType: 'sticker-pack',
|
|
202
|
+
timeoutMs: options.mediaUploadTimeoutMs
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
fsPromises.unlink(stickerPackUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
|
|
207
|
+
}
|
|
208
|
+
const obj = {
|
|
209
|
+
name,
|
|
210
|
+
publisher,
|
|
211
|
+
stickerPackId: stickerPackIdValue,
|
|
212
|
+
packDescription: description,
|
|
213
|
+
stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
|
|
214
|
+
stickerPackSize: zipBuffer.length,
|
|
215
|
+
stickers: stickerMetadata,
|
|
216
|
+
fileSha256: stickerPackUpload.fileSha256,
|
|
217
|
+
fileEncSha256: stickerPackUpload.fileEncSha256,
|
|
218
|
+
mediaKey: stickerPackUpload.mediaKey,
|
|
219
|
+
directPath: stickerPackUploadResult.directPath,
|
|
220
|
+
fileLength: stickerPackUpload.fileLength,
|
|
221
|
+
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
222
|
+
trayIconFileName
|
|
223
|
+
};
|
|
224
|
+
try {
|
|
225
|
+
let thumbnailBuffer;
|
|
226
|
+
if (hasSharp) {
|
|
227
|
+
thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
|
|
228
|
+
}
|
|
229
|
+
else if (hasImage) {
|
|
230
|
+
thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
|
|
231
|
+
}
|
|
232
|
+
else if (hasJimp) {
|
|
233
|
+
const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
|
|
234
|
+
thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
throw new Error('No image processing library available for thumbnail generation');
|
|
238
|
+
}
|
|
239
|
+
if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
|
|
240
|
+
throw new Error('Failed to generate thumbnail buffer');
|
|
241
|
+
}
|
|
242
|
+
const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
|
|
243
|
+
logger,
|
|
244
|
+
opts: options.options,
|
|
245
|
+
mediaKey: stickerPackUpload.mediaKey
|
|
246
|
+
});
|
|
247
|
+
let thumbUploadResult;
|
|
248
|
+
try {
|
|
249
|
+
thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
|
|
250
|
+
fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
|
|
251
|
+
mediaType: 'thumbnail-sticker-pack',
|
|
252
|
+
timeoutMs: options.mediaUploadTimeoutMs
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
fsPromises.unlink(thumbUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
|
|
257
|
+
}
|
|
258
|
+
Object.assign(obj, {
|
|
259
|
+
thumbnailDirectPath: thumbUploadResult.directPath,
|
|
260
|
+
thumbnailSha256: thumbUpload.fileSha256,
|
|
261
|
+
thumbnailEncSha256: thumbUpload.fileEncSha256,
|
|
262
|
+
thumbnailHeight: 252,
|
|
263
|
+
thumbnailWidth: 252,
|
|
264
|
+
imageDataHash: sha256(thumbnailBuffer).toString('base64')
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
logger?.warn(`Thumbnail generation failed: ${error}`);
|
|
269
|
+
}
|
|
270
|
+
if (cacheableKey) {
|
|
271
|
+
logger?.debug({ cacheableKey }, 'set cache (background)');
|
|
272
|
+
options.mediaCache.set(cacheableKey, Buffer.from(proto.Message.StickerPackMessage.encode(obj).finish()));
|
|
273
|
+
}
|
|
274
|
+
return proto.Message.StickerPackMessage.fromObject(obj);
|
|
275
|
+
};
|
|
276
|
+
//# sourceMappingURL=stickerpack.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface TemplateVariable {
|
|
2
|
+
name: string;
|
|
3
|
+
defaultValue?: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface MessageTemplate {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
content: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
category?: string;
|
|
12
|
+
variables: TemplateVariable[];
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
updatedAt: Date;
|
|
15
|
+
}
|
|
16
|
+
export type TemplateData = Record<string, string | number | undefined | null>;
|
|
17
|
+
export declare class TemplateManager {
|
|
18
|
+
private templates;
|
|
19
|
+
private generateId;
|
|
20
|
+
private extractVariables;
|
|
21
|
+
create(options: {
|
|
22
|
+
id?: string;
|
|
23
|
+
name: string;
|
|
24
|
+
content: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
category?: string;
|
|
27
|
+
}): MessageTemplate;
|
|
28
|
+
get(id: string): MessageTemplate | undefined;
|
|
29
|
+
getByName(name: string): MessageTemplate | undefined;
|
|
30
|
+
getAll(): MessageTemplate[];
|
|
31
|
+
getByCategory(category: string): MessageTemplate[];
|
|
32
|
+
update(id: string, updates: Partial<MessageTemplate>): MessageTemplate | undefined;
|
|
33
|
+
delete(id: string): boolean;
|
|
34
|
+
renderContent(content: string, data?: TemplateData): string;
|
|
35
|
+
render(id: string, data?: TemplateData): string;
|
|
36
|
+
validate(id: string, data: TemplateData): {
|
|
37
|
+
valid: boolean;
|
|
38
|
+
missing: string[];
|
|
39
|
+
};
|
|
40
|
+
export(): string;
|
|
41
|
+
import(json: string, overwrite?: boolean): number;
|
|
42
|
+
}
|
|
43
|
+
export declare const PRESET_TEMPLATES: {
|
|
44
|
+
ORDER_CONFIRMATION: {
|
|
45
|
+
name: string;
|
|
46
|
+
category: string;
|
|
47
|
+
content: string;
|
|
48
|
+
};
|
|
49
|
+
WELCOME: {
|
|
50
|
+
name: string;
|
|
51
|
+
category: string;
|
|
52
|
+
content: string;
|
|
53
|
+
};
|
|
54
|
+
REMINDER: {
|
|
55
|
+
name: string;
|
|
56
|
+
category: string;
|
|
57
|
+
content: string;
|
|
58
|
+
};
|
|
59
|
+
SUPPORT_TICKET: {
|
|
60
|
+
name: string;
|
|
61
|
+
category: string;
|
|
62
|
+
content: string;
|
|
63
|
+
};
|
|
64
|
+
BIRTHDAY: {
|
|
65
|
+
name: string;
|
|
66
|
+
category: string;
|
|
67
|
+
content: string;
|
|
68
|
+
};
|
|
69
|
+
INVOICE: {
|
|
70
|
+
name: string;
|
|
71
|
+
category: string;
|
|
72
|
+
content: string;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
export declare const createTemplateManager: (includePresets?: boolean) => TemplateManager;
|
|
76
|
+
export declare const renderTemplate: (content: string, data?: TemplateData) => string;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Vanz@Add --- ported from Bail-master addons/templates.ts (type-only
|
|
2
|
+
// annotations dropped; behavior unchanged).
|
|
3
|
+
export class TemplateManager {
|
|
4
|
+
templates = new Map();
|
|
5
|
+
generateId() {
|
|
6
|
+
return `tpl_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
7
|
+
}
|
|
8
|
+
extractVariables(content) {
|
|
9
|
+
const regex = /\{\{(\w+)(?::([^}]*))?\}\}/g;
|
|
10
|
+
const variables = [];
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
let match;
|
|
13
|
+
while ((match = regex.exec(content)) !== null) {
|
|
14
|
+
const name = match[1];
|
|
15
|
+
if (!name || seen.has(name))
|
|
16
|
+
continue;
|
|
17
|
+
seen.add(name);
|
|
18
|
+
variables.push({ name, defaultValue: match[2], required: !match[2] });
|
|
19
|
+
}
|
|
20
|
+
return variables;
|
|
21
|
+
}
|
|
22
|
+
create(options) {
|
|
23
|
+
const template = {
|
|
24
|
+
id: options.id ?? this.generateId(),
|
|
25
|
+
name: options.name,
|
|
26
|
+
content: options.content,
|
|
27
|
+
description: options.description,
|
|
28
|
+
category: options.category,
|
|
29
|
+
variables: this.extractVariables(options.content),
|
|
30
|
+
createdAt: new Date(),
|
|
31
|
+
updatedAt: new Date()
|
|
32
|
+
};
|
|
33
|
+
this.templates.set(template.id, template);
|
|
34
|
+
return template;
|
|
35
|
+
}
|
|
36
|
+
get(id) {
|
|
37
|
+
return this.templates.get(id);
|
|
38
|
+
}
|
|
39
|
+
getByName(name) {
|
|
40
|
+
return Array.from(this.templates.values()).find((t) => t.name === name);
|
|
41
|
+
}
|
|
42
|
+
getAll() {
|
|
43
|
+
return Array.from(this.templates.values());
|
|
44
|
+
}
|
|
45
|
+
getByCategory(category) {
|
|
46
|
+
return Array.from(this.templates.values()).filter((t) => t.category === category);
|
|
47
|
+
}
|
|
48
|
+
update(id, updates) {
|
|
49
|
+
const template = this.templates.get(id);
|
|
50
|
+
if (!template)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (updates.content)
|
|
53
|
+
updates.variables = this.extractVariables(updates.content);
|
|
54
|
+
const updated = { ...template, ...updates, updatedAt: new Date() };
|
|
55
|
+
this.templates.set(id, updated);
|
|
56
|
+
return updated;
|
|
57
|
+
}
|
|
58
|
+
delete(id) {
|
|
59
|
+
return this.templates.delete(id);
|
|
60
|
+
}
|
|
61
|
+
renderContent(content, data = {}) {
|
|
62
|
+
return content.replace(/\{\{(\w+)(?::([^}]*))?\}\}/g, (match, name, defaultValue) => {
|
|
63
|
+
const value = data[name];
|
|
64
|
+
if (value !== undefined && value !== null)
|
|
65
|
+
return String(value);
|
|
66
|
+
if (defaultValue !== undefined)
|
|
67
|
+
return defaultValue;
|
|
68
|
+
return match;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
render(id, data = {}) {
|
|
72
|
+
const template = this.templates.get(id);
|
|
73
|
+
if (!template)
|
|
74
|
+
throw new Error(`Template not found: ${id}`);
|
|
75
|
+
return this.renderContent(template.content, data);
|
|
76
|
+
}
|
|
77
|
+
validate(id, data) {
|
|
78
|
+
const template = this.templates.get(id);
|
|
79
|
+
if (!template)
|
|
80
|
+
throw new Error(`Template not found: ${id}`);
|
|
81
|
+
const missing = template.variables.filter((v) => v.required && !(v.name in data)).map((v) => v.name);
|
|
82
|
+
return { valid: missing.length === 0, missing };
|
|
83
|
+
}
|
|
84
|
+
export() {
|
|
85
|
+
return JSON.stringify(Array.from(this.templates.values()), null, 2);
|
|
86
|
+
}
|
|
87
|
+
import(json, overwrite = false) {
|
|
88
|
+
const templates = JSON.parse(json);
|
|
89
|
+
let imported = 0;
|
|
90
|
+
for (const t of templates) {
|
|
91
|
+
if (!overwrite && this.templates.has(t.id))
|
|
92
|
+
continue;
|
|
93
|
+
this.templates.set(t.id, { ...t, createdAt: new Date(t.createdAt), updatedAt: new Date(t.updatedAt) });
|
|
94
|
+
imported++;
|
|
95
|
+
}
|
|
96
|
+
return imported;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Ready-made templates covering common bot scenarios (order, welcome, reminder, etc). */
|
|
100
|
+
export const PRESET_TEMPLATES = {
|
|
101
|
+
ORDER_CONFIRMATION: {
|
|
102
|
+
name: 'Order Confirmation',
|
|
103
|
+
category: 'order',
|
|
104
|
+
content: `✅ *Order Confirmed!*\n\nOrder ID: #{{orderId}}\nCustomer: {{customerName}}\nDate: {{orderDate}}\n\n📦 *Items:*\n{{items}}\n\n💰 *Total: {{total}}*\n\nThank you! 🙏`
|
|
105
|
+
},
|
|
106
|
+
WELCOME: {
|
|
107
|
+
name: 'Welcome Message',
|
|
108
|
+
category: 'greeting',
|
|
109
|
+
content: `👋 *Welcome, {{name}}!*\n\nThank you for joining {{companyName:us}}!\nNeed help? Reply to this message!`
|
|
110
|
+
},
|
|
111
|
+
REMINDER: {
|
|
112
|
+
name: 'Reminder',
|
|
113
|
+
category: 'notification',
|
|
114
|
+
content: `⏰ *Reminder*\n\nHi {{name}},\n\n📋 {{subject}}\n📅 Date: {{date}}\n🕐 Time: {{time}}\n📍 Location: {{location:TBD}}`
|
|
115
|
+
},
|
|
116
|
+
SUPPORT_TICKET: {
|
|
117
|
+
name: 'Support Ticket',
|
|
118
|
+
category: 'support',
|
|
119
|
+
content: `🎫 *Support Ticket Created*\n\nTicket #: {{ticketId}}\nSubject: {{subject}}\n\nHi {{name}},\n\nWe received your request! Response time: {{responseTime:24 hours}} 🙏`
|
|
120
|
+
},
|
|
121
|
+
BIRTHDAY: {
|
|
122
|
+
name: 'Birthday Wishes',
|
|
123
|
+
category: 'greeting',
|
|
124
|
+
content: `🎂 *Happy Birthday, {{name}}!* 🎉\n\nWishing you a wonderful day!\n\n🎁 Use code: {{code}} for {{discount:10}}% off! 🥳`
|
|
125
|
+
},
|
|
126
|
+
INVOICE: {
|
|
127
|
+
name: 'Invoice',
|
|
128
|
+
category: 'invoice',
|
|
129
|
+
content: `🧾 *Invoice {{invoiceNumber}}*\n\nBilled to: {{customerName}}\nInvoice date: {{invoiceDate}}\nDue date: {{dueDate:on receipt}}\n\n📋 *Items:*\n{{items}}\n\nSubtotal: {{subtotal}}\n💰 *Total: {{total}}*\n\nThank you for your business! 🙏`
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
export const createTemplateManager = (includePresets = true) => {
|
|
133
|
+
const manager = new TemplateManager();
|
|
134
|
+
if (includePresets) {
|
|
135
|
+
for (const [key, template] of Object.entries(PRESET_TEMPLATES)) {
|
|
136
|
+
manager.create({ ...template, id: key.toLowerCase() });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return manager;
|
|
140
|
+
};
|
|
141
|
+
export const renderTemplate = (content, data = {}) => {
|
|
142
|
+
return content.replace(/\{\{(\w+)(?::([^}]*))?\}\}/g, (match, name, defaultValue) => {
|
|
143
|
+
const value = data[name];
|
|
144
|
+
if (value !== undefined && value !== null)
|
|
145
|
+
return String(value);
|
|
146
|
+
if (defaultValue !== undefined)
|
|
147
|
+
return defaultValue;
|
|
148
|
+
return match;
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -30,6 +30,33 @@ CREATE TABLE IF NOT EXISTS signal_keys (
|
|
|
30
30
|
);
|
|
31
31
|
CREATE INDEX IF NOT EXISTS signal_keys_type_idx ON signal_keys(type);
|
|
32
32
|
`;
|
|
33
|
+
// Vanz@Add 29-08-26 --- Migration tracking, ported in spirit from zapo-js's
|
|
34
|
+
// wa_migrations pattern (packages/store-sqlite/src/migrations.ts). Each entry
|
|
35
|
+
// is `{ id, sql }`; applied migrations are recorded by id in `wa_migrations`
|
|
36
|
+
// so `db.exec(...)` schema changes are only ever run once per database file,
|
|
37
|
+
// letting future releases evolve the schema without a manual ALTER step from
|
|
38
|
+
// the user. Kept as an ordered array (not a folder of files) since the schema
|
|
39
|
+
// here is a single flat file, unlike zapo's per-domain migration modules.
|
|
40
|
+
const MIGRATIONS = [
|
|
41
|
+
{ id: '0001_init', sql: CREATE_SCHEMA_SQL }
|
|
42
|
+
];
|
|
43
|
+
function ensureMigrationsTable(db) {
|
|
44
|
+
db.exec('CREATE TABLE IF NOT EXISTS wa_migrations (id TEXT PRIMARY KEY, applied_at INTEGER NOT NULL)');
|
|
45
|
+
}
|
|
46
|
+
function runMigrations(db) {
|
|
47
|
+
ensureMigrationsTable(db);
|
|
48
|
+
const applied = new Set(db.prepare('SELECT id FROM wa_migrations').all().map((r) => r.id));
|
|
49
|
+
const insertMigration = db.prepare('INSERT INTO wa_migrations (id, applied_at) VALUES (?, ?)');
|
|
50
|
+
for (const migration of MIGRATIONS) {
|
|
51
|
+
if (applied.has(migration.id))
|
|
52
|
+
continue;
|
|
53
|
+
const tx = db.transaction(() => {
|
|
54
|
+
db.exec(migration.sql);
|
|
55
|
+
insertMigration.run(migration.id, Date.now());
|
|
56
|
+
});
|
|
57
|
+
tx();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
33
60
|
export async function useSqliteAuthState(opts) {
|
|
34
61
|
let db;
|
|
35
62
|
if (opts.database) {
|
|
@@ -43,7 +70,7 @@ export async function useSqliteAuthState(opts) {
|
|
|
43
70
|
// what SQLite recommends for read-heavy workloads with sporadic writes.
|
|
44
71
|
db.pragma('journal_mode = WAL');
|
|
45
72
|
db.pragma('synchronous = NORMAL');
|
|
46
|
-
db
|
|
73
|
+
runMigrations(db);
|
|
47
74
|
// Vanz@Fix (bug 8): periodic WAL checkpoint + cleanup old signal keys to prevent DB bloat.
|
|
48
75
|
// Runs every 30 minutes. WAL checkpoint reclaims WAL file space; key cleanup removes stale session entries.
|
|
49
76
|
const _walCleanupInterval = setInterval(() => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface ContactPhone {
|
|
2
|
+
number: string;
|
|
3
|
+
type?: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ContactEmail {
|
|
7
|
+
email: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ContactUrl {
|
|
11
|
+
url: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ContactAddress {
|
|
15
|
+
street?: string;
|
|
16
|
+
city?: string;
|
|
17
|
+
state?: string;
|
|
18
|
+
postalCode?: string;
|
|
19
|
+
country?: string;
|
|
20
|
+
type?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ContactData {
|
|
23
|
+
fullName: string;
|
|
24
|
+
displayName?: string;
|
|
25
|
+
organization?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
phones?: ContactPhone[];
|
|
28
|
+
emails?: ContactEmail[];
|
|
29
|
+
urls?: ContactUrl[];
|
|
30
|
+
addresses?: ContactAddress[];
|
|
31
|
+
birthday?: string;
|
|
32
|
+
note?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare const escapeVCard: (s: string) => string;
|
|
35
|
+
export declare const formatPhone: (p: string) => string;
|
|
36
|
+
export declare const generateVCard: (c: ContactData) => string;
|
|
37
|
+
export declare const generateVCards: (contacts: ContactData[]) => string;
|
|
38
|
+
export declare const parseVCard: (vcard: string) => Partial<ContactData>;
|
|
39
|
+
export declare const createContactCard: (contact: ContactData) => {
|
|
40
|
+
contacts: {
|
|
41
|
+
displayName: string;
|
|
42
|
+
contacts: {
|
|
43
|
+
vcard: string;
|
|
44
|
+
}[];
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export declare const createContactCards: (contacts: ContactData[]) => {
|
|
48
|
+
contacts: {
|
|
49
|
+
displayName: string;
|
|
50
|
+
contacts: {
|
|
51
|
+
vcard: string;
|
|
52
|
+
}[];
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export declare const quickContact: (name: string, phone: string, options?: {
|
|
56
|
+
organization?: string;
|
|
57
|
+
email?: string;
|
|
58
|
+
}) => ContactData;
|