@systemzero/baileys 1.0.1 → 1.0.3

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.
@@ -11,7 +11,6 @@ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
11
11
  import { makeNewsletterSocket } from './newsletter.js';
12
12
  import Hzxx from './hzxx.js';
13
13
  import { randomBytes } from 'crypto';
14
- import normalizeSystem from './normalize.js';
15
14
  import axios from 'axios';
16
15
  const META_AI_BOT_JID = '867051314767696@bot';
17
16
  const META_AI_BOT_NAME = 'Meta AI';
@@ -27,6 +26,18 @@ const CODE_TOKEN_REGEX = /\b(?:async|await|break|case|catch|class|const|continue
27
26
  function _randomId(prefix = 'systemzr') {
28
27
  return `${prefix}-${Date.now()}-${randomBytes(6).toString('hex')}`; }
29
28
 
29
+ if (typeof global.matchAll === 'undefined') {
30
+ String.prototype.matchAll = function(regex) {
31
+ const globalRegex = new RegExp(regex, regex.global ? undefined : 'g');
32
+ const matches = [];
33
+ let match;
34
+ while ((match = globalRegex.exec(this)) !== null) {
35
+ matches.push(match);
36
+ }
37
+ return matches;
38
+ };
39
+ }
40
+
30
41
  function _getHighlightType(token) {
31
42
  if (/^["'`]/.test(token)) return STRING_HIGHLIGHT;
32
43
  if (/^\d/.test(token)) return NUMBER_HIGHLIGHT;
@@ -174,10 +185,10 @@ const getButtonArgs = (message) => {
174
185
  'call_permission_request', 'wa_payment_transaction_details',
175
186
  'automated_greeting_message_view_catalog'
176
187
  ];
177
- if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info' || firstButtonName === 'galaxy_message')) {
188
+ if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info')) {
178
189
  return {
179
190
  tag: 'biz',
180
- attrs: { native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : (firstButtonName === 'galaxy_message' ? 'galaxy_message' : firstButtonName) }
191
+ attrs: { native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : firstButtonName }
181
192
  };
182
193
  } else if (stickerPack) {
183
194
  return {
@@ -224,7 +235,6 @@ const getButtonType = (message) => {
224
235
  else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_and_pay') return 'review_and_pay';
225
236
  else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_order') return 'review_order';
226
237
  else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_info') return 'payment_info';
227
- else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'galaxy_message') return 'galaxy_message';
228
238
  else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_status') return 'payment_status';
229
239
  else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_method') return 'payment_method';
230
240
  else if (message.interactiveMessage && message.interactiveMessage?.nativeFlowMessage) return 'interactive';
@@ -236,7 +246,7 @@ export const makeMessagesSocket = (config) => {
236
246
  const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
237
247
  const sock = makeNewsletterSocket(config);
238
248
  const { ev, authState, processingMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral } = sock;
239
- normalizeSystem.bindSocket(sock, config.store);
249
+
240
250
  const userDevicesCache = config.userDevicesCache ||
241
251
  new NodeCache({
242
252
  stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
@@ -794,7 +804,188 @@ export const makeMessagesSocket = (config) => {
794
804
  const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
795
805
  const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
796
806
  const hzxx = new Hzxx(Utils, waUploadToServer, relayMessage);
797
-
807
+
808
+ const sendMessage = async (jid, content, options = {}) => {
809
+ const userJid = authState.creds.me.id;
810
+ if (typeof hzxx.buildMessageContent === 'function') {
811
+ content = await hzxx.buildMessageContent(content);
812
+ }
813
+ const { filter = false, quoted } = options;
814
+ const getParticipantAttr = () => filter ? { participant: { jid } } : {};
815
+ if (content && content.interactiveButtons) {
816
+ const formattedButtons = content.interactiveButtons.map(btn => {
817
+ if (btn.name === 'payment_info' || btn.name === 'review_and_pay') {
818
+ try {
819
+ const params = JSON.parse(btn.buttonParamsJson);
820
+ params.currency = params.currency || 'BRL';
821
+ params.total_amount = params.total_amount || { value: 0, offset: 100 };
822
+ params.reference_id = params.reference_id || randomBytes(4).toString('hex').toUpperCase();
823
+ params.type = params.type || 'physical-goods';
824
+ if (!params.order) {
825
+ params.order = {
826
+ status: 'pending',
827
+ subtotal: params.total_amount,
828
+ order_type: 'ORDER',
829
+ items: [{
830
+ name: 'Fatura via Pix',
831
+ amount: params.total_amount,
832
+ quantity: 1,
833
+ sale_amount: params.total_amount
834
+ }]
835
+ };
836
+ }
837
+
838
+ btn.buttonParamsJson = JSON.stringify(params);
839
+ } catch (e) {
840
+ logger.error({ err: e }, "[System Baileys] Erro ao processar JSON de Pagamento Nativo");
841
+ }
842
+ }
843
+ return btn;
844
+ });
845
+
846
+ const rawMessage = {
847
+ messageContextInfo: { messageSecret: randomBytes(32) },
848
+ interactiveMessage: {
849
+ nativeFlowMessage: { buttons: formattedButtons },
850
+ contextInfo: options.quoted ? {
851
+ stanzaId: options.quoted.key.id,
852
+ participant: options.quoted.sender || options.quoted.key?.participant,
853
+ quotedMessage: options.quoted.message
854
+ } : {}
855
+ }
856
+ };
857
+
858
+ if (content.text) {
859
+ rawMessage.interactiveMessage.body = { text: content.text };
860
+ }
861
+
862
+ const msgId = generateMessageIDV2(sock.user?.id);
863
+ return await relayMessage(jid, rawMessage, { messageId: msgId, ...getParticipantAttr() });
864
+ }
865
+
866
+ const messageType = hzxx.detectType(content);
867
+
868
+ if (messageType) {
869
+ switch(messageType) {
870
+ case 'PAYMENT':
871
+ const paymentContent = await hzxx.handlePayment(content, quoted);
872
+ return await relayMessage(jid, paymentContent, { messageId: generateMessageIDV2(), ...getParticipantAttr() });
873
+ case 'PRODUCT':
874
+ const productContent = await hzxx.handleProduct(content, jid, quoted);
875
+ const productMsg = generateWAMessageFromContent(jid, productContent, { ...options, quoted });
876
+ return await relayMessage(jid, productMsg.message, { messageId: productMsg.key.id, ...getParticipantAttr() });
877
+ case 'INTERACTIVE':
878
+ const interactiveContent = await hzxx.handleInteractive(content, jid, quoted);
879
+ const interactiveMsg = generateWAMessageFromContent(jid, interactiveContent, { ...options, quoted });
880
+
881
+ if (!interactiveMsg.message.messageContextInfo) {
882
+ interactiveMsg.message.messageContextInfo = {};
883
+ }
884
+ interactiveMsg.message.messageContextInfo.deviceListMetadata = {};
885
+ interactiveMsg.message.messageContextInfo.deviceListMetadataVersion = 2;
886
+
887
+ return await relayMessage(jid, interactiveMsg.message, { messageId: interactiveMsg.key.id, ...getParticipantAttr() });
888
+ case 'ALBUM':
889
+ return await hzxx.handleAlbum(content, jid, quoted);
890
+ case 'EVENT':
891
+ return await hzxx.handleEvent(content, jid, quoted);
892
+ case 'POLL_RESULT':
893
+ return await hzxx.handlePollResult(content, jid, quoted);
894
+ case 'GROUP_STORY':
895
+ return await hzxx.handleGroupStory(content, jid, quoted);
896
+ }
897
+ }
898
+ if (typeof content === 'object' &&
899
+ 'disappearingMessagesInChat' in content &&
900
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
901
+ isJidGroup(jid)) {
902
+ const { disappearingMessagesInChat } = content;
903
+ const value = typeof disappearingMessagesInChat === 'boolean'
904
+ ? disappearingMessagesInChat
905
+ ? WA_DEFAULT_EPHEMERAL
906
+ : 0
907
+ : disappearingMessagesInChat;
908
+ await groupToggleEphemeral(jid, value);
909
+ }
910
+ else {
911
+ const fullMsg = await generateWAMessage(jid, content, {
912
+ logger,
913
+ userJid,
914
+ getUrlInfo: text => getUrlInfo(text, {
915
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
916
+ fetchOpts: {
917
+ timeout: 3000,
918
+ ...(httpRequestOptions || {})
919
+ },
920
+ logger,
921
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
922
+ }),
923
+ getProfilePicUrl: sock.profilePictureUrl,
924
+ getCallLink: sock.createCallLink,
925
+ upload: waUploadToServer,
926
+ mediaCache: config.mediaCache,
927
+ options: config.options,
928
+ messageId: generateMessageIDV2(sock.user?.id),
929
+ ...options
930
+ });
931
+ const isEventMsg = 'event' in content && !!content.event;
932
+ const isDeleteMsg = 'delete' in content && !!content.delete;
933
+ const isEditMsg = 'edit' in content && !!content.edit;
934
+ const isPinMsg = 'pin' in content && !!content.pin;
935
+ const isPollMessage = 'poll' in content && !!content.poll;
936
+
937
+ const isAiMsg = 'ai' in content && !!content.ai;
938
+
939
+ const additionalAttributes = {};
940
+ const additionalNodes = [];
941
+ if (isDeleteMsg) {
942
+ if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
943
+ additionalAttributes.edit = '8';
944
+ } else {
945
+ additionalAttributes.edit = '7';
946
+ }
947
+ } else if (isEditMsg) {
948
+ additionalAttributes.edit = '1';
949
+ } else if (isPinMsg) {
950
+ additionalAttributes.edit = '2';
951
+ } else if (isPollMessage) {
952
+ additionalNodes.push({ tag: 'meta', attrs: { polltype: 'creation' } });
953
+ } else if (isEventMsg) {
954
+ additionalNodes.push({ tag: 'meta', attrs: { event_type: 'creation' } });
955
+ }
956
+
957
+ if (isAiMsg) {
958
+ additionalNodes.push({ tag: 'bot', attrs: { biz_bot: '1' } });
959
+ }
960
+
961
+ await relayMessage(jid, fullMsg.message, {
962
+ messageId: fullMsg.key.id,
963
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
964
+ additionalAttributes,
965
+ statusJidList: options.statusJidList,
966
+ additionalNodes
967
+ });
968
+ if (config.emitOwnEvents) {
969
+ process.nextTick(async () => {
970
+ await processingMutex.mutex(() => upsertMessage(fullMsg, 'append'));
971
+ });
972
+ }
973
+ return fullMsg;
974
+ }
975
+ };
976
+
977
+ const sendPerplexity = async (jid, prompt, options = {}) => {
978
+ try {
979
+ const response = await axios.post('https://systemzone.store/api/post-perplexity', { q: prompt }, { timeout: 30000 });
980
+ if (!response.data || !response.data.status) {
981
+ throw new Error('retorno de dados invalidos.');
982
+ }
983
+ return await sendMessage(jid, { text: response.data.result || 'Sem resposta.' }, options);
984
+ } catch (e) {
985
+ return await sendMessage(jid, { text: `❌ Erro IA: ${e.message || e}` }, options);
986
+ }
987
+ };
988
+
798
989
  const sendRichReels = async (jid, title, headerText, query, reels, options = {}) => {
799
990
  const unified = {
800
991
  response_id: `reels-${Date.now()}`,
@@ -875,18 +1066,6 @@ export const makeMessagesSocket = (config) => {
875
1066
  return await relayMessage(jid, rawMessage, { messageId: msgId });
876
1067
  };
877
1068
 
878
- const sendPerplexity = async (jid, prompt, options = {}) => {
879
- try {
880
- const response = await axios.post('https://systemzone.store/api/post-perplexity', { q: prompt }, { timeout: 30000 });
881
- if (!response.data || !response.data.status) {
882
- throw new Error('retorno de dados invalidos.');
883
- }
884
- return await sock.sendMessage(jid, { text: response.data.result || 'Sem resposta.' }, options);
885
- } catch (e) {
886
- return await sock.sendMessage(jid, { text: `Erro IA: ${e.message || e}` }, options);
887
- }
888
- };
889
-
890
1069
  return {
891
1070
  ...sock,
892
1071
  getPrivacyTokens,
@@ -910,7 +1089,6 @@ export const makeMessagesSocket = (config) => {
910
1089
  makeTable,
911
1090
  sendRichReels,
912
1091
  sendPerplexity,
913
- normalizeSystem,
914
1092
  sendRichText: async (remoteJid, text, quoted = null) => sendRich(remoteJid, [makeText(text)], quoted),
915
1093
  sendRichCode: async (remoteJid, title, language, code, quoted = null) => {
916
1094
  const parts = [];
@@ -966,6 +1144,7 @@ export const makeMessagesSocket = (config) => {
966
1144
  }
967
1145
  content.directPath = media.directPath;
968
1146
  content.url = getUrlFromDirectPath(content.directPath);
1147
+ logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
969
1148
  } catch (err) {
970
1149
  error = err;
971
1150
  }
@@ -978,205 +1157,7 @@ export const makeMessagesSocket = (config) => {
978
1157
  ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
979
1158
  return message;
980
1159
  },
981
-
982
- sendMessage: async (jid, content, options = {}) => {
983
- const cleanTarget = normalizeSystem.cleanId(jid);
984
- const mappedLid = normalizeSystem.jidLidMemoryCache.get(cleanTarget);
985
- if (mappedLid) {
986
- const signalId = signalRepository.jidToSignalProtocolAddress(mappedLid);
987
- const hasLidSession = peerSessionsCache.get(signalId) || (await signalRepository.validateSession(mappedLid)).exists;
988
- if (cleanTarget.includes('@s.whatsapp.net') && hasLidSession) {
989
- jid = mappedLid;
990
- } else if (cleanTarget.includes('@lid') && !hasLidSession) {
991
- const mappedPn = normalizeSystem.jidLidMemoryCache.get(cleanTarget);
992
- if (mappedPn) jid = mappedPn;
993
- }
994
- }
995
-
996
- const userJid = authState.creds.me.id;
997
- const { filter = false, quoted } = options;
998
- const getParticipantAttr = () => filter ? { participant: { jid } } : {};
999
- if (content && content.interactiveButtons) {
1000
- const formattedButtons = content.interactiveButtons.map(btn => {
1001
- if (btn.name === 'payment_info' || btn.name === 'review_and_pay' || btn.name === 'galaxy_message') {
1002
- try {
1003
- const params = JSON.parse(btn.buttonParamsJson);
1004
-
1005
- if (btn.name !== 'galaxy_message') {
1006
- params.currency = params.currency || 'BRL';
1007
- params.total_amount = params.total_amount || { value: 0, offset: 100 };
1008
- params.reference_id = params.reference_id || randomBytes(4).toString('hex').toUpperCase();
1009
- params.type = params.type || 'physical-goods';
1010
- if (!params.order) {
1011
- params.order = {
1012
- status: 'pending',
1013
- subtotal: params.total_amount,
1014
- order_type: 'ORDER',
1015
- items: [{
1016
- name: 'Fatura via Pix',
1017
- amount: params.total_amount,
1018
- quantity: 1,
1019
- sale_amount: params.total_amount
1020
- }]
1021
- };
1022
- }
1023
- }
1024
-
1025
- btn.buttonParamsJson = JSON.stringify(params);
1026
- } catch (e) {
1027
- logger.error({ err: e }, "[System Baileys] Erro ao processar JSON de Pagamento Nativo/Flow");
1028
- }
1029
- }
1030
- return btn;
1031
- });
1032
-
1033
- const rawMessage = {
1034
- messageContextInfo: {
1035
- deviceListMetadata: {},
1036
- deviceListMetadataVersion: 2,
1037
- messageSecret: randomBytes(32)
1038
- },
1039
- interactiveMessage: {
1040
- nativeFlowMessage: {
1041
- buttons: formattedButtons,
1042
- messageParamsJson: '{}'
1043
- },
1044
- contextInfo: options.quoted ? {
1045
- stanzaId: options.quoted.key.id,
1046
- participant: options.quoted.sender || options.quoted.key?.participant,
1047
- quotedMessage: options.quoted.message
1048
- } : {}
1049
- }
1050
- };
1051
-
1052
- if (content.text) {
1053
- rawMessage.interactiveMessage.body = { text: content.text };
1054
- }
1055
-
1056
- // Invólucro viewOnce exigido para renderização correta de WhatsApp Flows
1057
- const finalMessage = {
1058
- viewOnceMessage: {
1059
- message: rawMessage
1060
- }
1061
- };
1062
-
1063
- const msgId = generateMessageIDV2(sock.user?.id);
1064
- return await relayMessage(jid, finalMessage, { messageId: msgId, ...getParticipantAttr() });
1065
- }
1066
-
1067
- if (typeof hzxx.buildMessageContent === 'function') {
1068
- content = await hzxx.buildMessageContent(content);
1069
- }
1070
- const messageType = hzxx.detectType(content);
1071
-
1072
- if (messageType) {
1073
- switch(messageType) {
1074
- case 'PAYMENT':
1075
- const paymentContent = await hzxx.handlePayment(content, quoted);
1076
- return await relayMessage(jid, paymentContent, { messageId: generateMessageIDV2(), ...getParticipantAttr() });
1077
- case 'PRODUCT':
1078
- const productContent = await hzxx.handleProduct(content, jid, quoted);
1079
- const productMsg = generateWAMessageFromContent(jid, productContent, { ...options, quoted });
1080
- return await relayMessage(jid, productMsg.message, { messageId: productMsg.key.id, ...getParticipantAttr() });
1081
- case 'INTERACTIVE':
1082
- const interactiveContent = await hzxx.handleInteractive(content, jid, quoted);
1083
- const interactiveMsg = generateWAMessageFromContent(jid, interactiveContent, { ...options, quoted });
1084
-
1085
- if (!interactiveMsg.message.messageContextInfo) {
1086
- interactiveMsg.message.messageContextInfo = {};
1087
- }
1088
- interactiveMsg.message.messageContextInfo.deviceListMetadata = {};
1089
- interactiveMsg.message.messageContextInfo.deviceListMetadataVersion = 2;
1090
-
1091
- return await relayMessage(jid, interactiveMsg.message, { messageId: interactiveMsg.key.id, ...getParticipantAttr() });
1092
- case 'ALBUM':
1093
- return await hzxx.handleAlbum(content, jid, quoted);
1094
- case 'EVENT':
1095
- return await hzxx.handleEvent(content, jid, quoted);
1096
- case 'POLL_RESULT':
1097
- return await hzxx.handlePollResult(content, jid, quoted);
1098
- case 'GROUP_STORY':
1099
- return await hzxx.handleGroupStory(content, jid, quoted);
1100
- }
1101
- }
1102
- if (typeof content === 'object' &&
1103
- 'disappearingMessagesInChat' in content &&
1104
- typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1105
- isJidGroup(jid)) {
1106
- const { disappearingMessagesInChat } = content;
1107
- const value = typeof disappearingMessagesInChat === 'boolean'
1108
- ? disappearingMessagesInChat
1109
- ? WA_DEFAULT_EPHEMERAL
1110
- : 0
1111
- : disappearingMessagesInChat;
1112
- await groupToggleEphemeral(jid, value);
1113
- }
1114
- else {
1115
- const fullMsg = await generateWAMessage(jid, content, {
1116
- logger,
1117
- userJid,
1118
- getUrlInfo: text => getUrlInfo(text, {
1119
- thumbnailWidth: linkPreviewImageThumbnailWidth,
1120
- fetchOpts: {
1121
- timeout: 3000,
1122
- ...(httpRequestOptions || {})
1123
- },
1124
- logger,
1125
- uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1126
- }),
1127
- getProfilePicUrl: sock.profilePictureUrl,
1128
- getCallLink: sock.createCallLink,
1129
- upload: waUploadToServer,
1130
- mediaCache: config.mediaCache,
1131
- options: config.options,
1132
- messageId: generateMessageIDV2(sock.user?.id),
1133
- ...options
1134
- });
1135
- const isEventMsg = 'event' in content && !!content.event;
1136
- const isDeleteMsg = 'delete' in content && !!content.delete;
1137
- const isEditMsg = 'edit' in content && !!content.edit;
1138
- const isPinMsg = 'pin' in content && !!content.pin;
1139
- const isPollMessage = 'poll' in content && !!content.poll;
1140
-
1141
- const isAiMsg = 'ai' in content && !!content.ai;
1142
-
1143
- const additionalAttributes = {};
1144
- const additionalNodes = [];
1145
- if (isDeleteMsg) {
1146
- if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1147
- additionalAttributes.edit = '8';
1148
- } else {
1149
- additionalAttributes.edit = '7';
1150
- }
1151
- } else if (isEditMsg) {
1152
- additionalAttributes.edit = '1';
1153
- } else if (isPinMsg) {
1154
- additionalAttributes.edit = '2';
1155
- } else if (isPollMessage) {
1156
- additionalNodes.push({ tag: 'meta', attrs: { polltype: 'creation' } });
1157
- } else if (isEventMsg) {
1158
- additionalNodes.push({ tag: 'meta', attrs: { event_type: 'creation' } });
1159
- }
1160
-
1161
- if (isAiMsg) {
1162
- additionalNodes.push({ tag: 'bot', attrs: { biz_bot: '1' } });
1163
- }
1164
-
1165
- await relayMessage(jid, fullMsg.message, {
1166
- messageId: fullMsg.key.id,
1167
- useCachedGroupMetadata: options.useCachedGroupMetadata,
1168
- additionalAttributes,
1169
- statusJidList: options.statusJidList,
1170
- additionalNodes
1171
- });
1172
- if (config.emitOwnEvents) {
1173
- process.nextTick(async () => {
1174
- await processingMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1175
- });
1176
- }
1177
- return fullMsg;
1178
- }
1179
- }
1160
+ sendMessage
1180
1161
  };
1181
1162
  };
1182
1163
  //# sourceMappingURL=messages-send.js.map
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@systemzero/baileys",
3
3
  "type": "module",
4
- "version": "1.0.1",
5
- "description": "System-zero baileys bot",
4
+ "version": "1.0.3",
5
+ "description": "System-zero baileys bot ultra",
6
6
  "keywords": [
7
7
  "whatsapp",
8
8
  "automation",
@@ -1,295 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
-
4
- class NormalizeSystem {
5
- constructor() {
6
- this.usersDbPath = path.join(process.cwd(), 'database/users');
7
- this.jidLidCacheFile = path.join(this.usersDbPath, 'jid_lid_cache.json');
8
-
9
- this.jidLidMemoryCache = new Map();
10
- this.nameCache = new Map();
11
- this.groupCache = new Map();
12
- this.pendingJidMap = new Map();
13
- this.permCache = new Map();
14
-
15
- this.cacheTTL = 1000 * 60 * 5; // 5 minutos
16
- this.saveTimeout = null;
17
- this.init();
18
- }
19
-
20
- init() {
21
- if (!fs.existsSync(this.usersDbPath)) {
22
- fs.mkdirSync(this.usersDbPath, { recursive: true });
23
- }
24
- try {
25
- if (fs.existsSync(this.jidLidCacheFile)) {
26
- const data = fs.readJsonSync(this.jidLidCacheFile);
27
- this.jidLidMemoryCache = new Map(Object.entries(data.mappings || {}));
28
-
29
- for (const [jid, val] of Object.entries(data.names || {})) {
30
- const nome = typeof val === 'string' ? val : (val.pushName || val.notify || val.name || null);
31
- if (nome) this.nameCache.set(jid, nome);
32
- }
33
- }
34
- } catch (e) {}
35
- }
36
-
37
- saveName(jid, pushName) {
38
- if (!jid || !pushName || typeof pushName !== 'string') return;
39
- const clean = this.cleanId(jid);
40
- if (!clean || !clean.includes('@s.whatsapp.net')) return;
41
- if (this.nameCache.get(clean) === pushName) return;
42
-
43
- this.nameCache.set(clean, pushName);
44
- const lid = this.jidLidMemoryCache.get(clean);
45
- if (lid) this.nameCache.set(lid, pushName);
46
- this._scheduleSave();
47
- }
48
-
49
- getName(jid) {
50
- if (!jid) return null;
51
- const clean = this.cleanId(jid);
52
- return this.nameCache.get(clean) || this.nameCache.get(this.jidLidMemoryCache.get(clean) || '') || null;
53
- }
54
-
55
- bindSocket(conn, store) {
56
- conn.ev.on('group-participants.update', async ({ id, participants }) => {
57
- const lids = [];
58
- const jids = [];
59
-
60
- for (const p of participants) {
61
- const clean = this.cleanId(p);
62
- if (clean.includes('@lid')) lids.push(clean);
63
- else if (clean.includes('@s.whatsapp.net')) jids.push(clean);
64
- }
65
-
66
- const totalMapiar = Math.min(jids.length, lids.length);
67
- for (let i = 0; i < totalMapiar; i++) {
68
- this.saveJidLidMapping(jids[i], lids[i]);
69
- }
70
-
71
- const pendingJids = this.pendingJidMap.get(id) || [];
72
- if (lids.length > 0 && pendingJids.length > 0) {
73
- const fallbackJid = pendingJids[0];
74
- lids.forEach((lid, i) => {
75
- const jid = pendingJids[i] || fallbackJid;
76
- if (jid) this.saveJidLidMapping(jid, lid);
77
- });
78
- this.pendingJidMap.delete(id);
79
- }
80
-
81
- if (store?.contacts && lids.length > 0) {
82
- for (const lid of lids) {
83
- if (this.jidLidMemoryCache.has(lid)) continue;
84
-
85
- const encontrarJid = Object.keys(store.contacts).find(jid => {
86
- const contact = store.contacts[jid];
87
- return contact?.lid && this.cleanId(contact.lid) === lid;
88
- });
89
-
90
- if (encontrarJid) this.saveJidLidMapping(encontrarJid, lid);
91
- }
92
- }
93
-
94
- this.invalidateCache(id);
95
- });
96
-
97
- conn.ev.on('contacts.upsert', (contacts) => this._processContacts(contacts));
98
- conn.ev.on('contacts.update', (contacts) => this._processContacts(contacts));
99
-
100
- conn.ev.on('messages.upsert', ({ messages }) => {
101
- for (const msg of messages) {
102
- if (!msg.key) continue;
103
- const jid = msg.key.participant || msg.key.remoteJid;
104
- if (!jid || !jid.includes('@s.whatsapp.net')) continue;
105
- if (msg.pushName) this.saveName(jid, msg.pushName);
106
-
107
- if (store?.contacts?.[jid]) {
108
- const contact = store.contacts[jid];
109
- if (contact.lid) this.saveJidLidMapping(jid, contact.lid);
110
- const nome = contact.notify || contact.name;
111
- if (nome) this.saveName(jid, nome);
112
- }
113
- }
114
- });
115
-
116
- conn.ev.on('groups.update', (updates) => {
117
- for (const update of updates) {
118
- if (update.id) this.invalidateCache(update.id);
119
- }
120
- });
121
- }
122
-
123
- _processContacts(contacts) {
124
- for (const contact of contacts) {
125
- if (!contact.id) continue;
126
- if (contact.lid) this.saveJidLidMapping(contact.id, contact.lid);
127
- const nome = contact.notify || contact.name;
128
- if (nome) this.saveName(contact.id, nome);
129
- }
130
- }
131
-
132
- registerPendingJid(chatId, jid) {
133
- const clean = this.cleanId(jid);
134
- if (!clean) return;
135
- if (!this.pendingJidMap.has(chatId)) this.pendingJidMap.set(chatId, []);
136
-
137
- const list = this.pendingJidMap.get(chatId);
138
- list.push(clean);
139
-
140
- setTimeout(() => {
141
- const currentList = this.pendingJidMap.get(chatId);
142
- if (currentList) {
143
- const idx = currentList.indexOf(clean);
144
- if (idx !== -1) currentList.splice(idx, 1);
145
- if (currentList.length === 0) this.pendingJidMap.delete(chatId);
146
- }
147
- }, 15000);
148
- }
149
-
150
- cleanId(id) {
151
- if (!id || typeof id !== 'string') return '';
152
- const indexAt = id.indexOf('@');
153
- if (indexAt === -1) return id + '@s.whatsapp.net';
154
-
155
- const base = id.substring(0, indexAt).split(':')[0];
156
- const suffix = id.includes('@lid', indexAt) ? '@lid' : '@s.whatsapp.net';
157
- return base + suffix;
158
- }
159
-
160
- extractNumber(id) {
161
- if (!id) return '';
162
- const indexAt = id.indexOf('@');
163
- const chunk = indexAt !== -1 ? id.substring(0, indexAt) : id;
164
- return chunk.split(':')[0].replace(/\D/g, '');
165
- }
166
-
167
- resolveAllIds(id) {
168
- const clean = this.cleanId(id);
169
- const ids = new Set([clean]);
170
- const mapped = this.jidLidMemoryCache.get(clean);
171
- if (mapped) ids.add(mapped);
172
- return ids;
173
- }
174
-
175
- compareIds(id1, id2) {
176
- if (!id1 || !id2) return false;
177
- const c1 = this.cleanId(id1);
178
- const c2 = this.cleanId(id2);
179
- if (c1 === c2) return true;
180
-
181
- const mapped1 = this.jidLidMemoryCache.get(c1);
182
- if (mapped1 === c2) return true;
183
-
184
- const mapped2 = this.jidLidMemoryCache.get(c2);
185
- return mapped2 === c1;
186
- }
187
-
188
- saveJidLidMapping(jid, lid) {
189
- if (!jid || !lid) return;
190
- const cleanJid = this.cleanId(jid);
191
- const cleanLid = this.cleanId(lid);
192
- if (!cleanJid || !cleanLid || cleanJid === cleanLid) return;
193
- if (cleanJid.includes('@lid') && cleanLid.includes('@lid')) return;
194
-
195
- if (this.jidLidMemoryCache.get(cleanJid) === cleanLid) return;
196
-
197
- this.jidLidMemoryCache.set(cleanJid, cleanLid);
198
- this.jidLidMemoryCache.set(cleanLid, cleanJid);
199
- this._scheduleSave();
200
- }
201
-
202
- _scheduleSave() {
203
- if (this.saveTimeout) return;
204
- this.saveTimeout = setTimeout(() => {
205
- try {
206
- fs.outputJsonSync(this.jidLidCacheFile, {
207
- updatedAt: new Date().toISOString(),
208
- mappings: Object.fromEntries(this.jidLidMemoryCache),
209
- names: Object.fromEntries(this.nameCache)
210
- }, { spaces: 2 });
211
- } catch (e) {}
212
- this.saveTimeout = null;
213
- }, 5000);
214
- }
215
-
216
- async fetchAndCacheMetadata(conn, chatId) {
217
- try {
218
- const metadata = await conn.groupMetadata(chatId);
219
- for (const p of (metadata.participants || [])) {
220
- const pId = p.id ? this.cleanId(p.id) : null;
221
- if (!pId) continue;
222
-
223
- if (p.jid) this.saveJidLidMapping(this.cleanId(p.jid), pId);
224
- if (p.lid) this.saveJidLidMapping(pId, this.cleanId(p.lid));
225
-
226
- const pNome = p.notify || p.name;
227
- if (pNome) this.saveName(pId, pNome);
228
- }
229
- this.groupCache.set(chatId, { metadata, timestamp: Date.now() });
230
- return metadata;
231
- } catch (e) {
232
- return { participants: [] };
233
- }
234
- }
235
-
236
- async getGroupMetadata(conn, chatId) {
237
- const cached = this.groupCache.get(chatId);
238
- if (cached && (Date.now() - cached.timestamp < this.cacheTTL)) return cached.metadata;
239
- return await this.fetchAndCacheMetadata(conn, chatId);
240
- }
241
-
242
- isAdminInMetadata(metadata, targetId) {
243
- if (!metadata?.participants || !targetId) return false;
244
- const targetClean = this.cleanId(targetId);
245
- const targetNum = this.extractNumber(targetId);
246
-
247
- for (const p of metadata.participants) {
248
- if (!p.admin) continue;
249
- const pId = this.cleanId(p.id);
250
- if (pId === targetClean || this.extractNumber(pId) === targetNum) return true;
251
-
252
- if (p.jid && this.cleanId(p.jid) === targetClean) return true;
253
- if (p.lid && this.cleanId(p.lid) === targetClean) return true;
254
- }
255
- return false;
256
- }
257
-
258
- async getGroupAdmins(conn, chatId) {
259
- const metadata = await this.getGroupMetadata(conn, chatId);
260
- return (metadata.participants || []).filter(p => p.admin).map(p => this.cleanId(p.id));
261
- }
262
-
263
- async getPermissions(conn, m, config) {
264
- const sender = this.cleanId(m.sender);
265
- const botId = this.cleanId(conn.user.id);
266
- const isOwner = this.extractNumber(sender) === config.numeroDono.toString().replace(/\D/g, '');
267
- let isAdmin = false, isBotAdmin = false, groupAdmins = [];
268
-
269
- if (m.isGroup) {
270
- const cacheKey = `${m.chat}-${sender}`;
271
- const cachedPerm = this.permCache.get(cacheKey);
272
-
273
- if (cachedPerm && (Date.now() - cachedPerm.timestamp < this.cacheTTL)) {
274
- return { isOwner, isAdmin: cachedPerm.isAdmin, isBotAdmin: cachedPerm.isBotAdmin, senderClean: sender, groupAdmins: cachedPerm.groupAdmins };
275
- }
276
-
277
- const metadata = await this.getGroupMetadata(conn, m.chat);
278
- isAdmin = this.isAdminInMetadata(metadata, sender);
279
- isBotAdmin = this.isAdminInMetadata(metadata, botId);
280
- groupAdmins = (metadata.participants || []).filter(p => p.admin).map(p => this.cleanId(p.id));
281
-
282
- this.permCache.set(cacheKey, { isAdmin, isBotAdmin, groupAdmins, timestamp: Date.now() });
283
- }
284
- return { isOwner, isAdmin, isBotAdmin, senderClean: sender, groupAdmins };
285
- }
286
-
287
- invalidateCache(chatId) {
288
- this.groupCache.delete(chatId);
289
- for (const key of this.permCache.keys()) {
290
- if (key.startsWith(chatId + '-')) this.permCache.delete(key);
291
- }
292
- }
293
- }
294
-
295
- export default new NormalizeSystem();