plogme 1.0.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.
Files changed (154) hide show
  1. package/LICENSE +61 -0
  2. package/README.md +212 -0
  3. package/WAProto/V +1 -0
  4. package/WAProto/WAProto.proto +8051 -0
  5. package/WAProto/index.js +289292 -0
  6. package/engine-requirements.js +51 -0
  7. package/lib/Defaults/ban-checker.js +44 -0
  8. package/lib/Defaults/index.js +158 -0
  9. package/lib/Signal/Group/ciphertext-message.js +11 -0
  10. package/lib/Signal/Group/group-session-builder.js +29 -0
  11. package/lib/Signal/Group/group_cipher.js +81 -0
  12. package/lib/Signal/Group/index.js +11 -0
  13. package/lib/Signal/Group/keyhelper.js +17 -0
  14. package/lib/Signal/Group/sender-chain-key.js +25 -0
  15. package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
  16. package/lib/Signal/Group/sender-key-message.js +65 -0
  17. package/lib/Signal/Group/sender-key-name.js +47 -0
  18. package/lib/Signal/Group/sender-key-record.js +40 -0
  19. package/lib/Signal/Group/sender-key-state.js +83 -0
  20. package/lib/Signal/Group/sender-message-key.js +25 -0
  21. package/lib/Signal/libsignal.js +428 -0
  22. package/lib/Signal/lid-mapping.js +280 -0
  23. package/lib/Socket/Client/index.js +2 -0
  24. package/lib/Socket/Client/types.js +10 -0
  25. package/lib/Socket/Client/websocket.js +53 -0
  26. package/lib/Socket/aigroups.js +315 -0
  27. package/lib/Socket/ban-checker.js +153 -0
  28. package/lib/Socket/business.js +407 -0
  29. package/lib/Socket/chats.js +1831 -0
  30. package/lib/Socket/chats.js[old] +1227 -0
  31. package/lib/Socket/communities.js +431 -0
  32. package/lib/Socket/graphql.js +899 -0
  33. package/lib/Socket/groups.js +549 -0
  34. package/lib/Socket/groups.js[old] +347 -0
  35. package/lib/Socket/index.js +71 -0
  36. package/lib/Socket/interactive-handler.js +600 -0
  37. package/lib/Socket/interop.js +563 -0
  38. package/lib/Socket/managed-account.js +208 -0
  39. package/lib/Socket/messages-recv.js +2144 -0
  40. package/lib/Socket/messages-send.js +2650 -0
  41. package/lib/Socket/messages-send.js[latest working] +1759 -0
  42. package/lib/Socket/mex.js +41 -0
  43. package/lib/Socket/newsletter.js +226 -0
  44. package/lib/Socket/privacy.js +446 -0
  45. package/lib/Socket/registration.js +428 -0
  46. package/lib/Socket/socket.js +1050 -0
  47. package/lib/Socket/username.js +229 -0
  48. package/lib/Store/index.js +4 -0
  49. package/lib/Store/make-cache-manager-store.js +89 -0
  50. package/lib/Store/make-in-memory-store.js +420 -0
  51. package/lib/Store/make-ordered-dictionary.js +78 -0
  52. package/lib/Store/object-repository.js +23 -0
  53. package/lib/Types/Auth.js +1 -0
  54. package/lib/Types/Bussines.js +1 -0
  55. package/lib/Types/Call.js +1 -0
  56. package/lib/Types/Chat.js +7 -0
  57. package/lib/Types/Contact.js +1 -0
  58. package/lib/Types/Events.js +1 -0
  59. package/lib/Types/GroupMetadata.js +1 -0
  60. package/lib/Types/Label.js +24 -0
  61. package/lib/Types/LabelAssociation.js +6 -0
  62. package/lib/Types/Message.js +17 -0
  63. package/lib/Types/Mex.js +39 -0
  64. package/lib/Types/Product.js +1 -0
  65. package/lib/Types/RichType.js +22 -0
  66. package/lib/Types/Signal.js +1 -0
  67. package/lib/Types/Socket.js +2 -0
  68. package/lib/Types/State.js +48 -0
  69. package/lib/Types/USync.js +1 -0
  70. package/lib/Types/index.js +25 -0
  71. package/lib/Utils/auth-utils.js +301 -0
  72. package/lib/Utils/bot-planning-replay.js +169 -0
  73. package/lib/Utils/browser-utils.js +28 -0
  74. package/lib/Utils/business.js +230 -0
  75. package/lib/Utils/channel-policy.js +1 -0
  76. package/lib/Utils/chat-utils.js +885 -0
  77. package/lib/Utils/chat-utils.js[old] +871 -0
  78. package/lib/Utils/companion-reg-client-utils.js +32 -0
  79. package/lib/Utils/consumer-application.js +102 -0
  80. package/lib/Utils/crypto.js +117 -0
  81. package/lib/Utils/decode-wa-message.js +484 -0
  82. package/lib/Utils/event-buffer.js +589 -0
  83. package/lib/Utils/games/slot-machine.js +258 -0
  84. package/lib/Utils/games/website-preview.js +293 -0
  85. package/lib/Utils/generics.js +390 -0
  86. package/lib/Utils/generics.js@old +385 -0
  87. package/lib/Utils/group-history.js +56 -0
  88. package/lib/Utils/history.js +130 -0
  89. package/lib/Utils/identity-change-handler.js +48 -0
  90. package/lib/Utils/index.js +35 -0
  91. package/lib/Utils/integrity.js +296 -0
  92. package/lib/Utils/jid-display-normalization.js +209 -0
  93. package/lib/Utils/link-preview.js +84 -0
  94. package/lib/Utils/logger.js +2 -0
  95. package/lib/Utils/lt-hash.js +7 -0
  96. package/lib/Utils/make-mutex.js +32 -0
  97. package/lib/Utils/message-composer.js +467 -0
  98. package/lib/Utils/message-inspect.js +67 -0
  99. package/lib/Utils/message-retry-manager.js +241 -0
  100. package/lib/Utils/messages-media.js +1006 -0
  101. package/lib/Utils/messages-media.js[old] +916 -0
  102. package/lib/Utils/messages.js +1982 -0
  103. package/lib/Utils/messages.js[old] +1902 -0
  104. package/lib/Utils/meta-ai-msmsg.js +130 -0
  105. package/lib/Utils/meta-compositing.js +202 -0
  106. package/lib/Utils/native-flow.js +100 -0
  107. package/lib/Utils/noise-handler.js +200 -0
  108. package/lib/Utils/offline-node-processor.js +39 -0
  109. package/lib/Utils/pre-key-manager.js +105 -0
  110. package/lib/Utils/process-message.js +582 -0
  111. package/lib/Utils/reporting-utils.js +257 -0
  112. package/lib/Utils/rich-message-utils.js +942 -0
  113. package/lib/Utils/rich-message-utils.js[Old] +387 -0
  114. package/lib/Utils/signal.js +200 -0
  115. package/lib/Utils/stanza-ack.js +37 -0
  116. package/lib/Utils/sync-action-utils.js +47 -0
  117. package/lib/Utils/tc-token-utils.js +162 -0
  118. package/lib/Utils/use-multi-file-auth-state.js +120 -0
  119. package/lib/Utils/use-single-file-auth-state.js +96 -0
  120. package/lib/Utils/use-sqlite-auth-state.js +108 -0
  121. package/lib/Utils/validate-connection.js +206 -0
  122. package/lib/Utils/view-once-utils.js +206 -0
  123. package/lib/Utils/voip-rekey.js +20 -0
  124. package/lib/Utils/welcome-flow.js +220 -0
  125. package/lib/WABinary/constants.js +1463 -0
  126. package/lib/WABinary/decode.js +261 -0
  127. package/lib/WABinary/encode.js +219 -0
  128. package/lib/WABinary/generic-utils.js +227 -0
  129. package/lib/WABinary/index.js +5 -0
  130. package/lib/WABinary/jid-utils.js +95 -0
  131. package/lib/WABinary/types.js +1 -0
  132. package/lib/WAM/BinaryInfo.js +9 -0
  133. package/lib/WAM/constants.js +22852 -0
  134. package/lib/WAM/encode.js +149 -0
  135. package/lib/WAM/index.js +3 -0
  136. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +96 -0
  137. package/lib/WAUSync/Protocols/USyncContactProtocol.js +56 -0
  138. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -0
  139. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
  140. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +70 -0
  141. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +28 -0
  142. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +25 -0
  143. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
  144. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +34 -0
  145. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
  146. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
  147. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
  148. package/lib/WAUSync/Protocols/index.js +12 -0
  149. package/lib/WAUSync/USyncQuery.js +201 -0
  150. package/lib/WAUSync/USyncUser.js +54 -0
  151. package/lib/WAUSync/index.js +3 -0
  152. package/lib/index.d.ts +406 -0
  153. package/lib/index.js +11 -0
  154. package/package.json +125 -0
@@ -0,0 +1,206 @@
1
+ /**
2
+ * View-Once Message Utilities
3
+ *
4
+ * Provides auto-detection, decryption, and handling of view-once messages.
5
+ * - autoVV: Automatically decrypt view-once messages and re-emit as regular messages
6
+ * - antiVV: Automatically detect and delete/view view-once messages
7
+ */
8
+
9
+ import { downloadContentFromMessage } from './messages-media.js';
10
+ import { extractMessageContent, getContentType } from './messages.js';
11
+
12
+ /**
13
+ * Detect if a message is a view-once message
14
+ * @param {object} message - The message object
15
+ * @returns {boolean}
16
+ */
17
+ export const isViewOnceMessage = (message) => {
18
+ if (!message) return false;
19
+ const content = message.message || message;
20
+ return !!(
21
+ content.viewOnceMessage ||
22
+ content.viewOnceMessageV2 ||
23
+ content.viewOnceMessageV2Extension
24
+ );
25
+ };
26
+
27
+ /**
28
+ * Extract the inner content from a view-once message wrapper
29
+ * @param {object} message - The message object with viewOnce wrapper
30
+ * @returns {object|null} The inner message content or null
31
+ */
32
+ export const extractViewOnceContent = (message) => {
33
+ const content = message.message || message;
34
+ const viewOnceWrapper =
35
+ content.viewOnceMessage ||
36
+ content.viewOnceMessageV2 ||
37
+ content.viewOnceMessageV2Extension;
38
+
39
+ if (!viewOnceWrapper) return null;
40
+ return viewOnceWrapper.message || viewOnceWrapper;
41
+ };
42
+
43
+ /**
44
+ * Get the media type from a view-once message
45
+ * @param {object} message - The message object
46
+ * @returns {string|null} The media type (image, video, audio, document) or null
47
+ */
48
+ export const getViewOnceMediaType = (message) => {
49
+ const innerContent = extractViewOnceContent(message);
50
+ if (!innerContent) return null;
51
+
52
+ if (innerContent.imageMessage) return 'image';
53
+ if (innerContent.videoMessage) return 'video';
54
+ if (innerContent.audioMessage) return 'audio';
55
+ if (innerContent.documentMessage) return 'document';
56
+ if (innerContent.stickerMessage) return 'sticker';
57
+
58
+ return null;
59
+ };
60
+
61
+ /**
62
+ * Get the media content from a view-once message
63
+ * @param {object} message - The message object
64
+ * @returns {object|null} The media content object with url, mediaKey, etc.
65
+ */
66
+ export const getViewOnceMediaContent = (message) => {
67
+ const innerContent = extractViewOnceContent(message);
68
+ if (!innerContent) return null;
69
+
70
+ return innerContent.imageMessage ||
71
+ innerContent.videoMessage ||
72
+ innerContent.audioMessage ||
73
+ innerContent.documentMessage ||
74
+ innerContent.stickerMessage ||
75
+ null;
76
+ };
77
+
78
+ /**
79
+ * Download and decrypt a view-once message's media content
80
+ * @param {object} message - The full message object
81
+ * @param {object} options - Options for download
82
+ * @returns {Promise<Buffer>} The decrypted media buffer
83
+ */
84
+ export const downloadViewOnceMedia = async (message, options = {}) => {
85
+ const mediaContent = getViewOnceMediaContent(message);
86
+ if (!mediaContent) {
87
+ throw new Error('No media content found in view-once message');
88
+ }
89
+
90
+ const mediaType = getViewOnceMediaType(message);
91
+ if (!mediaType) {
92
+ throw new Error('Could not determine media type');
93
+ }
94
+
95
+ const stream = await downloadContentFromMessage(mediaContent, mediaType, options);
96
+ const chunks = [];
97
+ for await (const chunk of stream) {
98
+ chunks.push(chunk);
99
+ }
100
+ return Buffer.concat(chunks);
101
+ };
102
+
103
+ /**
104
+ * Create a regular message from a decrypted view-once message
105
+ * @param {object} originalMessage - The original view-once message
106
+ * @param {Buffer} mediaBuffer - The decrypted media buffer
107
+ * @returns {object} A regular message object with the media
108
+ */
109
+ export const createRegularMessageFromViewOnce = (originalMessage, mediaBuffer) => {
110
+ const innerContent = extractViewOnceContent(originalMessage);
111
+ if (!innerContent) return null;
112
+
113
+ const mediaType = getViewOnceMediaType(originalMessage);
114
+ const mediaContent = getViewOnceMediaContent(originalMessage);
115
+
116
+ // Create a new message with the decrypted content
117
+ const newMessage = {
118
+ key: originalMessage.key,
119
+ messageTimestamp: originalMessage.messageTimestamp,
120
+ pushName: originalMessage.pushName,
121
+ participant: originalMessage.participant,
122
+ // Store the decrypted media
123
+ decryptedMedia: {
124
+ type: mediaType,
125
+ buffer: mediaBuffer,
126
+ mimeType: mediaContent?.mimetype,
127
+ fileName: mediaContent?.fileName,
128
+ caption: mediaContent?.caption,
129
+ fileLength: mediaContent?.fileLength,
130
+ },
131
+ // Keep original message for reference
132
+ originalViewOnceMessage: originalMessage.message,
133
+ };
134
+
135
+ return newMessage;
136
+ };
137
+
138
+ /**
139
+ * Check if a message should be auto-decrypted based on configuration
140
+ * @param {object} message - The message object
141
+ * @param {object} config - Configuration object
142
+ * @returns {boolean}
143
+ */
144
+ export const shouldAutoDecryptViewOnce = (message, config = {}) => {
145
+ if (!isViewOnceMessage(message)) return false;
146
+
147
+ // Check if auto-decryption is enabled
148
+ if (config.autoVV === false) return false;
149
+
150
+ // Check if it's from self (don't decrypt own messages)
151
+ if (message.key?.fromMe) return false;
152
+
153
+ return true;
154
+ };
155
+
156
+ /**
157
+ * Check if a message should be deleted based on anti-VV configuration
158
+ * @param {object} message - The message object
159
+ * @param {object} config - Configuration object
160
+ * @returns {boolean}
161
+ */
162
+ export const shouldDeleteViewOnce = (message, config = {}) => {
163
+ if (!isViewOnceMessage(message)) return false;
164
+
165
+ // Check if anti-VV is enabled
166
+ if (config.antiVV === false) return false;
167
+
168
+ // Check if it's from self (don't delete own messages)
169
+ if (message.key?.fromMe) return false;
170
+
171
+ return true;
172
+ };
173
+
174
+ /**
175
+ * Parse view-once message info for logging/display
176
+ * @param {object} message - The message object
177
+ * @returns {object} Parsed view-once info
178
+ */
179
+ export const parseViewOnceInfo = (message) => {
180
+ const mediaType = getViewOnceMediaType(message);
181
+ const mediaContent = getViewOnceMediaContent(message);
182
+
183
+ return {
184
+ isViewOnce: isViewOnceMessage(message),
185
+ mediaType,
186
+ mimeType: mediaContent?.mimetype,
187
+ fileName: mediaContent?.fileName,
188
+ caption: mediaContent?.caption,
189
+ fileLength: mediaContent?.fileLength,
190
+ hasUrl: !!mediaContent?.url,
191
+ hasDirectPath: !!mediaContent?.directPath,
192
+ hasMediaKey: !!mediaContent?.mediaKey,
193
+ };
194
+ };
195
+
196
+ export default {
197
+ isViewOnceMessage,
198
+ extractViewOnceContent,
199
+ getViewOnceMediaType,
200
+ getViewOnceMediaContent,
201
+ downloadViewOnceMedia,
202
+ createRegularMessageFromViewOnce,
203
+ shouldAutoDecryptViewOnce,
204
+ shouldDeleteViewOnce,
205
+ parseViewOnceInfo,
206
+ };
@@ -0,0 +1,20 @@
1
+ import { proto } from '../../WAProto/index.js';
2
+
3
+ const REKEY_TYPE_NAMES = {
4
+ [proto.RekeyKeyType.REKEY_KEY_AUDIO]: 'REKEY_KEY_AUDIO',
5
+ [proto.RekeyKeyType.REKEY_KEY_VIDEO]: 'REKEY_KEY_VIDEO',
6
+ [proto.RekeyKeyType.REKEY_KEY_APPDATA]: 'REKEY_KEY_APPDATA'
7
+ };
8
+
9
+ export const decodeE2eRekeyPayload = (buffer) => {
10
+ if (!Buffer.isBuffer(buffer) && !(buffer instanceof Uint8Array)) {
11
+ throw new TypeError('decodeE2eRekeyPayload: buffer must be Buffer or Uint8Array');
12
+ }
13
+ const decoded = proto.E2eRekeyPayload.decode(buffer);
14
+ return {
15
+ keys: (decoded.keys || []).map(entry => ({
16
+ type: REKEY_TYPE_NAMES[entry.type] ?? entry.type,
17
+ key: entry.key ? Buffer.from(entry.key) : Buffer.alloc(0)
18
+ }))
19
+ };
20
+ };
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Lia@Changes [WIP]
3
+ * Welcome Flow — Meta Business-style startup template.
4
+ *
5
+ * Fires automatically on first message from any new contact.
6
+ * Sends a fully configurable greeting with interactive FAQ buttons,
7
+ * exactly like Meta Business account welcome templates.
8
+ *
9
+ * Tracks seen JIDs in memory + optional persistent JSON file so the
10
+ * greeting fires ONCE per contact — never on repeat messages.
11
+ *
12
+ * Usage:
13
+ * const welcome = createWelcomeFlow(sock, config)
14
+ * welcome.listen() // start listening
15
+ * welcome.stop() // stop listening
16
+ * welcome.reset(jid) // force re-greet a contact
17
+ * welcome.resetAll() // clear all seen contacts
18
+ *
19
+ * If you use or copy this code, please credit @crysnovax/bailey.
20
+ */
21
+
22
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
23
+ import { isJidGroup, isJidNewsletter, isJidBroadcast, jidNormalizedUser } from '../WABinary/index.js';
24
+ import { premiumEnabled } from './integrity.js';
25
+ import { delay } from './generics.js';
26
+
27
+ // ─── Defaults ────────────────────────────────────────────────────────────────
28
+
29
+ const DEFAULT_CONFIG = {
30
+ greeting: '👋 Welcome! How can I help you today?',
31
+ footer: 'Powered by @crysnovax/bailey',
32
+ buttonText: '📋 Choose an option',
33
+ faqs: [
34
+ { id: 'faq_1', title: '📦 Track my order', description: 'Check order status' },
35
+ { id: 'faq_2', title: '💳 Billing & payments', description: 'Payment issues & invoices' },
36
+ { id: 'faq_3', title: '🛠️ Technical support', description: 'Get help with a problem' },
37
+ { id: 'faq_4', title: '📞 Talk to a human', description: 'Connect with support staff' },
38
+ ],
39
+ sectionTitle: 'How can we help?',
40
+ typingDelayMs: 1200, // simulate typing before greeting appears
41
+ persistPath: null, // set to a file path to persist seen JIDs across restarts
42
+ ignoreGroups: true, // don't fire in group chats
43
+ ignoreNewsletter: true, // don't fire for newsletter messages
44
+ ignoreBroadcast: true, // don't fire for broadcast messages
45
+ onGreet: null, // async (jid, message) => {} — called after greeting sent
46
+ onFaqReply: null, // async (jid, faqId, message) => {} — called when user picks a FAQ
47
+ };
48
+
49
+ // ─── Persistent seen-JID store ───────────────────────────────────────────────
50
+
51
+ const createSeenStore = (persistPath) => {
52
+ const seen = new Set();
53
+
54
+ // Load from file if path given and file exists
55
+ if (persistPath && existsSync(persistPath)) {
56
+ try {
57
+ const data = JSON.parse(readFileSync(persistPath, 'utf8'));
58
+ if (Array.isArray(data)) data.forEach(jid => seen.add(jid));
59
+ } catch (_) {}
60
+ }
61
+
62
+ const save = () => {
63
+ if (!persistPath) return;
64
+ try {
65
+ writeFileSync(persistPath, JSON.stringify([...seen]), 'utf8');
66
+ } catch (_) {}
67
+ };
68
+
69
+ return {
70
+ has: (jid) => seen.has(jid),
71
+ add: (jid) => { seen.add(jid); save(); },
72
+ delete: (jid) => { seen.delete(jid); save(); },
73
+ clear: () => { seen.clear(); save(); }
74
+ };
75
+ };
76
+
77
+ // ─── Build the welcome interactive list message ───────────────────────────────
78
+
79
+ const buildWelcomeMessage = (config, quotedMsg = null) => {
80
+ const { greeting, footer, buttonText, faqs, sectionTitle } = config;
81
+
82
+ const content = {
83
+ text: greeting,
84
+ footer,
85
+ buttonText,
86
+ sections: [
87
+ {
88
+ title: sectionTitle,
89
+ rows: faqs.map(faq => ({
90
+ title: faq.title,
91
+ description: faq.description || '',
92
+ rowId: faq.id
93
+ }))
94
+ }
95
+ ]
96
+ };
97
+
98
+ if (quotedMsg) {
99
+ content.quoted = quotedMsg;
100
+ }
101
+
102
+ return content;
103
+ };
104
+
105
+ // ─── Main factory ─────────────────────────────────────────────────────────────
106
+
107
+ /**
108
+ * createWelcomeFlow — attach a Meta Business-style welcome template to a socket.
109
+ *
110
+ * @param {object} sock - Baileys socket
111
+ * @param {object} config - Partial config — merges with DEFAULT_CONFIG
112
+ * @returns {{ listen, stop, reset, resetAll }}
113
+ */
114
+ export const createWelcomeFlow = (sock, config = {}) => {
115
+ const cfg = { ...DEFAULT_CONFIG, ...config };
116
+ const seen = createSeenStore(cfg.persistPath);
117
+
118
+ // ── FAQ reply handler — called when user selects a list option ────────────
119
+ const handleFaqReply = async (msg) => {
120
+ if (typeof cfg.onFaqReply !== 'function') return;
121
+ const reply = msg.message?.listResponseMessage;
122
+ if (!reply) return;
123
+ const faqId = reply.singleSelectReply?.selectedRowId;
124
+ const jid = jidNormalizedUser(msg.key.remoteJid);
125
+ if (faqId) {
126
+ await cfg.onFaqReply(jid, faqId, msg);
127
+ }
128
+ };
129
+
130
+ // ── First-message handler ─────────────────────────────────────────────────
131
+ const handleMessage = async ({ messages, type }) => {
132
+ if (type !== 'notify') return;
133
+
134
+ for (const msg of messages) {
135
+ try {
136
+ const jid = msg.key?.remoteJid;
137
+ if (!jid || msg.key?.fromMe) continue;
138
+
139
+ // Skip ignored JID types
140
+ if (cfg.ignoreGroups && isJidGroup(jid)) continue;
141
+ if (cfg.ignoreNewsletter && isJidNewsletter(jid)) continue;
142
+ if (cfg.ignoreBroadcast && isJidBroadcast(jid)) continue;
143
+
144
+ const normalizedJid = jidNormalizedUser(jid);
145
+
146
+ // Check if it's a FAQ reply first
147
+ if (msg.message?.listResponseMessage) {
148
+ await handleFaqReply(msg);
149
+ continue;
150
+ }
151
+
152
+ // Already greeted this contact — skip
153
+ if (seen.has(normalizedJid)) continue;
154
+
155
+ // Mark as seen immediately to prevent race conditions
156
+ seen.add(normalizedJid);
157
+
158
+ // Simulate typing before the greeting
159
+ if (cfg.typingDelayMs > 0) {
160
+ await sock.sendPresenceUpdate('composing', jid);
161
+ await delay(cfg.typingDelayMs);
162
+ await sock.sendPresenceUpdate('paused', jid);
163
+ }
164
+
165
+ // Send the welcome message (plain text greeting on unlicensed copies)
166
+ const welcomeContent = premiumEnabled(sock.config?.logger)
167
+ ? buildWelcomeMessage(cfg, msg)
168
+ : { text: cfg.greeting };
169
+ await sock.sendMessage(jid, welcomeContent);
170
+
171
+ // Fire onGreet callback if provided
172
+ if (typeof cfg.onGreet === 'function') {
173
+ await cfg.onGreet(normalizedJid, msg).catch(() => {});
174
+ }
175
+ } catch (_) {}
176
+ }
177
+ };
178
+
179
+ // ── Public API ────────────────────────────────────────────────────────────
180
+ return {
181
+ /**
182
+ * Start listening for new contacts.
183
+ */
184
+ listen() {
185
+ sock.ev.on('messages.upsert', handleMessage);
186
+ },
187
+
188
+ /**
189
+ * Stop listening.
190
+ */
191
+ stop() {
192
+ sock.ev.off('messages.upsert', handleMessage);
193
+ },
194
+
195
+ /**
196
+ * Force re-greet a specific contact next time they message.
197
+ * @param {string} jid
198
+ */
199
+ reset(jid) {
200
+ seen.delete(jidNormalizedUser(jid));
201
+ },
202
+
203
+ /**
204
+ * Clear all seen contacts — everyone gets re-greeted.
205
+ */
206
+ resetAll() {
207
+ seen.clear();
208
+ },
209
+
210
+ /**
211
+ * Check if a contact has already been greeted.
212
+ * @param {string} jid
213
+ * @returns {boolean}
214
+ */
215
+ hasGreeted(jid) {
216
+ return seen.has(jidNormalizedUser(jid));
217
+ }
218
+ };
219
+ };
220
+