@kaikybrofc/omnizap-system 2.1.8
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/.env.example +534 -0
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/RELEASE-v2.1.2.md +83 -0
- package/app/config/adminIdentity.js +87 -0
- package/app/config/baileysConfig.js +693 -0
- package/app/config/groupUtils.js +388 -0
- package/app/connection/socketController.js +992 -0
- package/app/controllers/messageController.js +354 -0
- package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
- package/app/modules/adminModule/groupEventHandlers.js +355 -0
- package/app/modules/aiModule/catCommand.js +1006 -0
- package/app/modules/broadcastModule/noticeCommand.js +416 -0
- package/app/modules/gameModule/diceCommand.js +67 -0
- package/app/modules/menuModule/common.js +311 -0
- package/app/modules/menuModule/menus.js +59 -0
- package/app/modules/playModule/playCommand.js +1615 -0
- package/app/modules/quoteModule/quoteCommand.js +851 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
- package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
- package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
- package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
- package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
- package/app/modules/statsModule/globalRankingCommand.js +65 -0
- package/app/modules/statsModule/noMessageCommand.js +288 -0
- package/app/modules/statsModule/rankingCommand.js +60 -0
- package/app/modules/statsModule/rankingCommon.js +889 -0
- package/app/modules/stickerModule/addStickerMetadata.js +239 -0
- package/app/modules/stickerModule/convertToWebp.js +390 -0
- package/app/modules/stickerModule/stickerCommand.js +454 -0
- package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
- package/app/modules/stickerModule/stickerTextCommand.js +657 -0
- package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
- package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
- package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
- package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
- package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
- package/app/modules/stickerPackModule/stickerPackService.js +788 -0
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
- package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
- package/app/modules/systemMetricsModule/pingCommand.js +421 -0
- package/app/modules/tiktokModule/tiktokCommand.js +798 -0
- package/app/modules/userModule/userCommand.js +1217 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
- package/app/observability/metrics.js +734 -0
- package/app/services/captchaService.js +492 -0
- package/app/services/dbWriteQueue.js +572 -0
- package/app/services/groupMetadataService.js +279 -0
- package/app/services/lidMapService.js +663 -0
- package/app/services/messagePersistenceService.js +56 -0
- package/app/services/newsBroadcastService.js +351 -0
- package/app/services/pokeApiService.js +398 -0
- package/app/services/queueUtils.js +57 -0
- package/app/services/socketState.js +7 -0
- package/app/store/aiPromptStore.js +38 -0
- package/app/store/groupConfigStore.js +58 -0
- package/app/store/premiumUserStore.js +36 -0
- package/app/utils/antiLink/antiLinkModule.js +804 -0
- package/app/utils/http/getImageBufferModule.js +18 -0
- package/app/utils/json/jsonSanitizer.js +113 -0
- package/app/utils/json/jsonSanitizer.test.js +40 -0
- package/app/utils/logger/loggerModule.js +262 -0
- package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
- package/database/index.js +2052 -0
- package/database/init.js +516 -0
- package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
- package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
- package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
- package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
- package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
- package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
- package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
- package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
- package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
- package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
- package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
- package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
- package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
- package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
- package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
- package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
- package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
- package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
- package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
- package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
- package/docker-compose.yml +103 -0
- package/ecosystem.prod.config.cjs +35 -0
- package/eslint.config.js +61 -0
- package/index.js +437 -0
- package/ml/clip_classifier/Dockerfile +16 -0
- package/ml/clip_classifier/README.md +120 -0
- package/ml/clip_classifier/adaptive_scoring.py +40 -0
- package/ml/clip_classifier/classifier.py +654 -0
- package/ml/clip_classifier/embedding_store.py +481 -0
- package/ml/clip_classifier/env_loader.py +15 -0
- package/ml/clip_classifier/llm_label_expander.py +144 -0
- package/ml/clip_classifier/main.py +213 -0
- package/ml/clip_classifier/requirements.txt +10 -0
- package/ml/clip_classifier/similarity_engine.py +74 -0
- package/observability/alert-rules.yml +60 -0
- package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
- package/observability/grafana/dashboards/omnizap-overview.json +170 -0
- package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
- package/observability/loki-config.yml +38 -0
- package/observability/mysql-exporter.cnf +5 -0
- package/observability/mysql-setup.sql +46 -0
- package/observability/prometheus.yml +32 -0
- package/observability/promtail-config.yml +84 -0
- package/package.json +109 -0
- package/public/api-docs/index.html +144 -0
- package/public/css/github-project-panel.css +297 -0
- package/public/css/stickers-admin.css +1272 -0
- package/public/css/styles.css +671 -0
- package/public/index.html +1311 -0
- package/public/js/apps/apiDocsApp.js +310 -0
- package/public/js/apps/createPackApp.js +2069 -0
- package/public/js/apps/homeApp.js +396 -0
- package/public/js/apps/stickersAdminApp.js +1744 -0
- package/public/js/apps/stickersApp.js +4830 -0
- package/public/js/catalog.js +1019 -0
- package/public/js/github-panel/components/CommitList.js +34 -0
- package/public/js/github-panel/components/ErrorState.js +16 -0
- package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
- package/public/js/github-panel/components/ReleaseList.js +38 -0
- package/public/js/github-panel/components/SkeletonPanel.js +22 -0
- package/public/js/github-panel/components/StatCard.js +15 -0
- package/public/js/github-panel/index.js +15 -0
- package/public/js/github-panel/useGithubRepoData.js +154 -0
- package/public/js/github-panel/vendor/react.js +11 -0
- package/public/js/runtime/react-runtime.js +19 -0
- package/public/licenca/index.html +106 -0
- package/public/stickers/admin/index.html +23 -0
- package/public/stickers/create/index.html +47 -0
- package/public/stickers/index.html +48 -0
- package/public/termos-de-uso/index.html +125 -0
- package/scripts/cache-bust.mjs +107 -0
- package/scripts/deploy.sh +458 -0
- package/scripts/github-deploy-notify.mjs +174 -0
- package/scripts/release.sh +129 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* eslint-disable no-undef */
|
|
3
|
+
/* eslint-disable no-useless-escape */
|
|
4
|
+
import { fetchLatestBaileysVersion, downloadContentFromMessage, jidNormalizedUser, jidEncode, jidDecode, areJidsSameUser, normalizeMessageContent, isJidMetaAI, isPnUser, isLidUser, isJidBroadcast, isJidGroup, isJidStatusBroadcast, isJidNewsletter, isHostedPnUser, isHostedLidUser, isJidBot, SERVER_JID, PSA_WID, STORIES_JID, META_AI_JID } from '@whiskeysockets/baileys';
|
|
5
|
+
|
|
6
|
+
import logger from '../utils/logger/loggerModule.js';
|
|
7
|
+
import { createWriteStream } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { pipeline } from 'node:stream/promises';
|
|
10
|
+
import { Readable } from 'node:stream';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_BAILEYS_VERSION = [7, 0, 0];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Constantes de JID expostas pelo Baileys para facilitar comparações.
|
|
16
|
+
* @type {{SERVER_JID: string, PSA_WID: string, STORIES_JID: string, META_AI_JID: string}}
|
|
17
|
+
*/
|
|
18
|
+
export const JID_CONSTANTS = {
|
|
19
|
+
SERVER_JID,
|
|
20
|
+
PSA_WID,
|
|
21
|
+
STORIES_JID,
|
|
22
|
+
META_AI_JID,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const decodeJidParts = (() => {
|
|
26
|
+
let lastJid = null;
|
|
27
|
+
let lastDecoded = null;
|
|
28
|
+
|
|
29
|
+
return (jid) => {
|
|
30
|
+
if (!jid) return null;
|
|
31
|
+
if (jid === lastJid) return lastDecoded;
|
|
32
|
+
const decoded = jidDecode(jid) || null;
|
|
33
|
+
lastJid = jid;
|
|
34
|
+
lastDecoded = decoded;
|
|
35
|
+
return decoded;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Tipos de mensagem conhecidos do Baileys
|
|
41
|
+
* Mapeamento de chaves do proto.Message para tipos normalizados
|
|
42
|
+
*/
|
|
43
|
+
export const MEDIA_TYPE_MAPPING = {
|
|
44
|
+
conversation: 'text',
|
|
45
|
+
extendedTextMessage: 'text',
|
|
46
|
+
imageMessage: 'image',
|
|
47
|
+
videoMessage: 'video',
|
|
48
|
+
audioMessage: 'audio',
|
|
49
|
+
documentMessage: 'document',
|
|
50
|
+
documentWithCaptionMessage: 'document',
|
|
51
|
+
stickerMessage: 'sticker',
|
|
52
|
+
contactMessage: 'contact',
|
|
53
|
+
contactsArrayMessage: 'contacts',
|
|
54
|
+
locationMessage: 'location',
|
|
55
|
+
liveLocationMessage: 'liveLocation',
|
|
56
|
+
buttonsMessage: 'buttons',
|
|
57
|
+
buttonsResponseMessage: 'buttonsResponse',
|
|
58
|
+
templateMessage: 'template',
|
|
59
|
+
templateButtonReplyMessage: 'buttonReply',
|
|
60
|
+
listMessage: 'list',
|
|
61
|
+
listResponseMessage: 'listResponse',
|
|
62
|
+
ephemeralMessage: 'ephemeral',
|
|
63
|
+
reactionMessage: 'reaction',
|
|
64
|
+
pollCreationMessage: 'poll',
|
|
65
|
+
pollUpdateMessage: 'pollUpdate',
|
|
66
|
+
pollResultSnapshotMessage: 'pollResult',
|
|
67
|
+
invoiceMessage: 'invoice',
|
|
68
|
+
sendPaymentMessage: 'payment',
|
|
69
|
+
requestPaymentMessage: 'paymentRequest',
|
|
70
|
+
cancelPaymentRequestMessage: 'paymentCancel',
|
|
71
|
+
declinePaymentRequestMessage: 'paymentDecline',
|
|
72
|
+
groupInviteMessage: 'groupInvite',
|
|
73
|
+
productMessage: 'product',
|
|
74
|
+
orderMessage: 'order',
|
|
75
|
+
viewOnceMessage: 'viewOnce',
|
|
76
|
+
viewOnceMessageV2: 'viewOnceV2',
|
|
77
|
+
interactiveMessage: 'interactive',
|
|
78
|
+
interactiveResponseMessage: 'interactiveResponse',
|
|
79
|
+
newsletterAdminInviteMessage: 'newsletterInvite',
|
|
80
|
+
eventMessage: 'event',
|
|
81
|
+
requestPhoneNumberMessage: 'requestPhoneNumber',
|
|
82
|
+
call: 'call',
|
|
83
|
+
messageHistoryBundle: 'messageHistoryBundle',
|
|
84
|
+
messageHistoryNotice: 'messageHistoryNotice',
|
|
85
|
+
albumMessage: 'album',
|
|
86
|
+
stickerPackMessage: 'stickerPack',
|
|
87
|
+
highlyStructuredMessage: 'structured',
|
|
88
|
+
fastRatchetKeySenderKeyDistributionMessage: 'keyDistribution',
|
|
89
|
+
deviceSentMessage: 'deviceSent',
|
|
90
|
+
messageContextInfo: 'contextInfo',
|
|
91
|
+
botInvokeMessage: 'botInvoke',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Tipos de midia que contem conteudo binario/arquivo
|
|
96
|
+
*/
|
|
97
|
+
export const BINARY_MEDIA_TYPES = new Set(['image', 'video', 'videoNote', 'audio', 'voice', 'document', 'sticker']);
|
|
98
|
+
|
|
99
|
+
const normalizeMessage = (message) => normalizeMessageContent(message) || message;
|
|
100
|
+
|
|
101
|
+
const hasNonEmptyMediaKey = (mediaKey) => {
|
|
102
|
+
if (!mediaKey) return false;
|
|
103
|
+
|
|
104
|
+
if (typeof mediaKey === 'string') {
|
|
105
|
+
return mediaKey.trim().length > 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Buffer.isBuffer(mediaKey) || mediaKey instanceof Uint8Array) {
|
|
109
|
+
return mediaKey.length > 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(mediaKey)) {
|
|
113
|
+
return mediaKey.length > 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (typeof mediaKey === 'object') {
|
|
117
|
+
if (typeof mediaKey.byteLength === 'number') {
|
|
118
|
+
return mediaKey.byteLength > 0;
|
|
119
|
+
}
|
|
120
|
+
return Object.keys(mediaKey).length > 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Boolean(mediaKey);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const buildMediaEntry = (mediaType, messageKey, value, isQuoted, overrides = {}) => ({
|
|
127
|
+
mediaType,
|
|
128
|
+
mediaKey: value,
|
|
129
|
+
messageKey,
|
|
130
|
+
isQuoted,
|
|
131
|
+
isBinary: BINARY_MEDIA_TYPES.has(mediaType),
|
|
132
|
+
hasUrl: !!value.url,
|
|
133
|
+
hasDirectPath: !!value.directPath,
|
|
134
|
+
hasMediaKey: !!value.mediaKey,
|
|
135
|
+
hasFileEncSha256: !!value.fileEncSha256,
|
|
136
|
+
mimetype: value.mimetype || null,
|
|
137
|
+
fileLength: value.fileLength || null,
|
|
138
|
+
fileName: value.fileName || null,
|
|
139
|
+
caption: value.caption || null,
|
|
140
|
+
...overrides,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const collectMediaFromMessage = (message, { includeQuoted = true } = {}) => {
|
|
144
|
+
if (!message || !message.message) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const messageContent = message.message;
|
|
149
|
+
let allMedia = detectAllMediaTypes(messageContent, false);
|
|
150
|
+
|
|
151
|
+
if (includeQuoted) {
|
|
152
|
+
const quotedMessage = messageContent?.extendedTextMessage?.contextInfo?.quotedMessage;
|
|
153
|
+
if (quotedMessage) {
|
|
154
|
+
allMedia = allMedia.concat(detectAllMediaTypes(quotedMessage, true));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return allMedia;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const filterMedia = (media, { includeAllTypes = false, includeUnknown = false } = {}) => {
|
|
162
|
+
let filtered = media;
|
|
163
|
+
|
|
164
|
+
if (!includeAllTypes) {
|
|
165
|
+
filtered = filtered.filter((item) => item.isBinary);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!includeUnknown) {
|
|
169
|
+
filtered = filtered.filter((item) => !item.isUnknownType);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return filtered;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const findExpiration = (root) => {
|
|
176
|
+
if (!root || typeof root !== 'object') return null;
|
|
177
|
+
|
|
178
|
+
const stack = [root];
|
|
179
|
+
const visited = new WeakSet();
|
|
180
|
+
|
|
181
|
+
while (stack.length > 0) {
|
|
182
|
+
const current = stack.pop();
|
|
183
|
+
if (!current || typeof current !== 'object') continue;
|
|
184
|
+
if (visited.has(current)) continue;
|
|
185
|
+
visited.add(current);
|
|
186
|
+
|
|
187
|
+
const expiration = current.contextInfo?.expiration;
|
|
188
|
+
if (typeof expiration === 'number') return expiration;
|
|
189
|
+
|
|
190
|
+
for (const value of Object.values(current)) {
|
|
191
|
+
if (value && typeof value === 'object') {
|
|
192
|
+
stack.push(value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const getMediaExtension = (type) => {
|
|
201
|
+
if (type === 'image') return 'jpeg';
|
|
202
|
+
if (type === 'video') return 'mp4';
|
|
203
|
+
if (type === 'audio') return 'mp3';
|
|
204
|
+
return 'bin';
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const isBadDecryptError = (error) => {
|
|
208
|
+
if (!error || typeof error !== 'object') return false;
|
|
209
|
+
if (error.code === 'ERR_OSSL_BAD_DECRYPT') return true;
|
|
210
|
+
|
|
211
|
+
const message = String(error.message || '').toLowerCase();
|
|
212
|
+
const reason = String(error.reason || '').toLowerCase();
|
|
213
|
+
const opensslStack = Array.isArray(error.opensslErrorStack) ? error.opensslErrorStack.join(' ').toLowerCase() : '';
|
|
214
|
+
|
|
215
|
+
return message.includes('bad decrypt') || reason.includes('bad decrypt') || opensslStack.includes('bad decrypt');
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Converte a versão do Baileys (string ou array) para o formato `[major, minor, patch]`.
|
|
220
|
+
* @param {string|number[]|null|undefined} rawVersion - Valor bruto informado na variável de ambiente.
|
|
221
|
+
* @returns {number[]|null} Retorna a versão normalizada ou `null` se inválida.
|
|
222
|
+
*/
|
|
223
|
+
function parseBaileysVersion(rawVersion) {
|
|
224
|
+
if (!rawVersion) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const cleaned = String(rawVersion).replace(/[\[\]\s]/g, '');
|
|
229
|
+
const parts = cleaned
|
|
230
|
+
.split(/[.,]/)
|
|
231
|
+
.filter(Boolean)
|
|
232
|
+
.map((value) => Number(value));
|
|
233
|
+
|
|
234
|
+
if (parts.length < 3 || parts.some((value) => Number.isNaN(value))) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return parts.slice(0, 3);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Codifica um usuário no formato JID aceito pelo WhatsApp.
|
|
243
|
+
* @param {string|number|null|undefined} user - Identificador do usuário.
|
|
244
|
+
* @param {string} [server='c.us'] - Domínio do servidor JID.
|
|
245
|
+
* @param {number} [device] - ID do dispositivo quando aplicável.
|
|
246
|
+
* @returns {string|null} JID codificado ou `null` para entrada inválida.
|
|
247
|
+
*/
|
|
248
|
+
export function encodeJid(user, server = 'c.us', device) {
|
|
249
|
+
if (user === null || user === undefined) return null;
|
|
250
|
+
return jidEncode(user, server, device);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Decodifica um JID em partes (`user`, `server`, `device`) usando cache simples.
|
|
255
|
+
* @param {string} jid - JID completo.
|
|
256
|
+
* @returns {{user?: string, server?: string, domainType?: number, device?: number}|null} Partes do JID ou `null`.
|
|
257
|
+
*/
|
|
258
|
+
export function decodeJid(jid) {
|
|
259
|
+
if (!jid) return null;
|
|
260
|
+
return decodeJidParts(jid);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Normaliza um JID para o formato canônico.
|
|
265
|
+
* @param {string} jid - JID de entrada.
|
|
266
|
+
* @returns {string} JID normalizado ou string vazia quando ausente.
|
|
267
|
+
*/
|
|
268
|
+
export function normalizeJid(jid) {
|
|
269
|
+
if (!jid) return '';
|
|
270
|
+
return jidNormalizedUser(jid);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Extrai o identificador do usuário de um JID.
|
|
275
|
+
* @param {string} jid - JID completo.
|
|
276
|
+
* @returns {string|null} Usuário extraído ou `null`.
|
|
277
|
+
*/
|
|
278
|
+
export function getJidUser(jid) {
|
|
279
|
+
return decodeJidParts(jid)?.user || null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Extrai o servidor de um JID.
|
|
284
|
+
* @param {string} jid - JID completo.
|
|
285
|
+
* @returns {string|null} Servidor extraído ou `null`.
|
|
286
|
+
*/
|
|
287
|
+
export function getJidServer(jid) {
|
|
288
|
+
return decodeJidParts(jid)?.server || null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Verifica se dois JIDs pertencem ao mesmo usuário.
|
|
293
|
+
* @param {string} jid1 - Primeiro JID.
|
|
294
|
+
* @param {string} jid2 - Segundo JID.
|
|
295
|
+
* @returns {boolean} `true` quando representam o mesmo usuário.
|
|
296
|
+
*/
|
|
297
|
+
export function isSameJidUser(jid1, jid2) {
|
|
298
|
+
return areJidsSameUser(jid1, jid2);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Verifica se o JID representa um usuário (PN/LID, hospedado ou não).
|
|
303
|
+
* @param {string} jid - JID a validar.
|
|
304
|
+
* @returns {boolean} `true` quando for JID de usuário.
|
|
305
|
+
*/
|
|
306
|
+
export function isUserJid(jid) {
|
|
307
|
+
return Boolean(jid && (isPnUser(jid) || isHostedPnUser(jid) || isLidUser(jid) || isHostedLidUser(jid)));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Verifica se o JID é de grupo.
|
|
312
|
+
* @param {string} jid - JID a validar.
|
|
313
|
+
* @returns {boolean} `true` quando for grupo.
|
|
314
|
+
*/
|
|
315
|
+
export function isGroupJid(jid) {
|
|
316
|
+
return Boolean(jid && isJidGroup(jid));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Verifica se o JID é de broadcast.
|
|
321
|
+
* @param {string} jid - JID a validar.
|
|
322
|
+
* @returns {boolean} `true` quando for broadcast.
|
|
323
|
+
*/
|
|
324
|
+
export function isBroadcastJid(jid) {
|
|
325
|
+
return Boolean(jid && isJidBroadcast(jid));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Verifica se o JID é do status broadcast.
|
|
330
|
+
* @param {string} jid - JID a validar.
|
|
331
|
+
* @returns {boolean} `true` quando for status.
|
|
332
|
+
*/
|
|
333
|
+
export function isStatusJid(jid) {
|
|
334
|
+
return Boolean(jid && isJidStatusBroadcast(jid));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Verifica se o JID é de newsletter/canal.
|
|
339
|
+
* @param {string} jid - JID a validar.
|
|
340
|
+
* @returns {boolean} `true` quando for newsletter.
|
|
341
|
+
*/
|
|
342
|
+
export function isNewsletterJid(jid) {
|
|
343
|
+
return Boolean(jid && isJidNewsletter(jid));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Verifica se o JID pertence à Meta AI.
|
|
348
|
+
* @param {string} jid - JID a validar.
|
|
349
|
+
* @returns {boolean} `true` quando for Meta AI.
|
|
350
|
+
*/
|
|
351
|
+
export function isMetaAiJid(jid) {
|
|
352
|
+
return Boolean(jid && isJidMetaAI(jid));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Verifica se o JID pertence a um bot.
|
|
357
|
+
* @param {string} jid - JID a validar.
|
|
358
|
+
* @returns {boolean} `true` quando for bot.
|
|
359
|
+
*/
|
|
360
|
+
export function isBotJid(jid) {
|
|
361
|
+
return Boolean(jid && isJidBot(jid));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Resolve o JID do bot a partir do `sock.user.id`.
|
|
366
|
+
* @param {string} sockUserId - ID bruto retornado pelo socket.
|
|
367
|
+
* @returns {string|null} JID normalizado do bot ou `null`.
|
|
368
|
+
*/
|
|
369
|
+
export function resolveBotJid(sockUserId) {
|
|
370
|
+
const normalized = normalizeJid(sockUserId);
|
|
371
|
+
if (normalized) return normalized;
|
|
372
|
+
if (!sockUserId || typeof sockUserId !== 'string') return null;
|
|
373
|
+
const rawUser = sockUserId.split(':')[0];
|
|
374
|
+
return encodeJid(rawUser, 's.whatsapp.net');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Resolve a versão do Baileys com prioridade para `BAILEYS_VERSION`.
|
|
379
|
+
* Se a variável não for válida, tenta buscar a recomendada e aplica fallback local.
|
|
380
|
+
* @returns {Promise<number[]>} Versão no formato `[major, minor, patch]`.
|
|
381
|
+
*/
|
|
382
|
+
export async function resolveBaileysVersion() {
|
|
383
|
+
const envVersion = parseBaileysVersion(process.env.BAILEYS_VERSION);
|
|
384
|
+
if (envVersion) {
|
|
385
|
+
return envVersion;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (process.env.BAILEYS_VERSION) {
|
|
389
|
+
logger.warn('Valor invalido em BAILEYS_VERSION; usando versao recomendada.', {
|
|
390
|
+
provided: process.env.BAILEYS_VERSION,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
const { version } = await fetchLatestBaileysVersion();
|
|
396
|
+
if (Array.isArray(version) && version.length >= 3) {
|
|
397
|
+
return version;
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
logger.warn('Falha ao buscar a versao recomendada do Baileys; usando fallback.', {
|
|
401
|
+
error: error.message,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return DEFAULT_BAILEYS_VERSION;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Baixa a foto de perfil associada à mensagem recebida.
|
|
410
|
+
* @param {import('@whiskeysockets/baileys').WASocket} sock - Instância conectada do socket.
|
|
411
|
+
* @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg - Mensagem usada para resolver o JID.
|
|
412
|
+
* @returns {Promise<Buffer|null>} Buffer da imagem ou `null` se indisponível.
|
|
413
|
+
*/
|
|
414
|
+
export async function getProfilePicBuffer(sock, msg) {
|
|
415
|
+
const rawJid = msg?.key?.participant || msg?.key?.remoteJid;
|
|
416
|
+
const jid = jidNormalizedUser(rawJid);
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const url = await sock.profilePictureUrl(jid, 'image');
|
|
420
|
+
if (!url) return null;
|
|
421
|
+
|
|
422
|
+
const response = await fetch(url);
|
|
423
|
+
if (!response.ok) return null;
|
|
424
|
+
|
|
425
|
+
const data = await response.arrayBuffer();
|
|
426
|
+
return Buffer.from(data);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Extrai o valor de expiração de uma mensagem do WhatsApp, ou retorna 24 horas (em segundos) por padrão.
|
|
434
|
+
* @param {{message?: object}|null|undefined} sock - Estrutura contendo a propriedade `message`.
|
|
435
|
+
* @returns {number} Tempo de expiração em segundos.
|
|
436
|
+
*/
|
|
437
|
+
export function getExpiration(sock) {
|
|
438
|
+
const DEFAULT_EXPIRATION_SECONDS = 24 * 60 * 60;
|
|
439
|
+
|
|
440
|
+
if (!sock || typeof sock !== 'object' || !sock.message) {
|
|
441
|
+
return DEFAULT_EXPIRATION_SECONDS;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const normalizedMessage = normalizeMessage(sock.message);
|
|
445
|
+
const expiration = findExpiration(normalizedMessage);
|
|
446
|
+
|
|
447
|
+
return typeof expiration === 'number' ? expiration : DEFAULT_EXPIRATION_SECONDS;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Extrai o conteúdo de texto de uma mensagem do WhatsApp.
|
|
452
|
+
* @param {{message?: object}} messageInfo - Objeto que contém o payload da mensagem.
|
|
453
|
+
* @returns {string} Conteúdo textual extraído ou descrição do tipo de mensagem.
|
|
454
|
+
*/
|
|
455
|
+
export const extractMessageContent = ({ message }) => {
|
|
456
|
+
if (!message) return 'Mensagem vazia';
|
|
457
|
+
|
|
458
|
+
const normalizedMessage = normalizeMessage(message);
|
|
459
|
+
if (!normalizedMessage) return 'Mensagem vazia';
|
|
460
|
+
|
|
461
|
+
const text = normalizedMessage.conversation?.trim() || normalizedMessage.extendedTextMessage?.text;
|
|
462
|
+
|
|
463
|
+
if (text) return text;
|
|
464
|
+
|
|
465
|
+
const handlers = [
|
|
466
|
+
[normalizedMessage.imageMessage, (m) => m.caption || '[Imagem]'],
|
|
467
|
+
[normalizedMessage.videoMessage, (m) => m.caption || '[Vídeo]'],
|
|
468
|
+
[normalizedMessage.documentMessage, (m) => m.fileName || '[Documento]'],
|
|
469
|
+
[normalizedMessage.audioMessage, (m) => (m.ptt ? '[Áudio] (voz)' : '[Áudio]')],
|
|
470
|
+
[normalizedMessage.stickerMessage, () => '[Figurinha]'],
|
|
471
|
+
[normalizedMessage.locationMessage, (m) => `[Localização] Lat: ${m.degreesLatitude}, Long: ${m.degreesLongitude}`],
|
|
472
|
+
[normalizedMessage.contactMessage, (m) => `[Contato] ${m.displayName}`],
|
|
473
|
+
[normalizedMessage.contactsArrayMessage, (m) => `[Contatos] ${m.contacts.map((c) => c.displayName).join(', ')}`],
|
|
474
|
+
[normalizedMessage.listMessage, (m) => m.description || '[Mensagem de Lista]'],
|
|
475
|
+
[normalizedMessage.listResponseMessage, (m) => `[Lista] ${m.singleSelectReply?.selectedRowId || m.title || ''}`.trim()],
|
|
476
|
+
[normalizedMessage.buttonsMessage, (m) => m.contentText || '[Mensagem de Botões]'],
|
|
477
|
+
[normalizedMessage.buttonsResponseMessage, (m) => `[Botão] ${m.selectedDisplayText || m.selectedButtonId || ''}`.trim()],
|
|
478
|
+
[normalizedMessage.templateButtonReplyMessage, (m) => `[Resposta de Botão] ${m.selectedDisplayText || ''}`.trim()],
|
|
479
|
+
[normalizedMessage.interactiveResponseMessage, (m) => `[Interativo] ${m.body?.text || m.nativeFlowResponseMessage?.name || ''}`.trim()],
|
|
480
|
+
[normalizedMessage.productMessage, (m) => m.product?.title || '[Mensagem de Produto]'],
|
|
481
|
+
[normalizedMessage.reactionMessage, (m) => `[Reação] ${m.text || ''}`.trim()],
|
|
482
|
+
[normalizedMessage.pollCreationMessage, (m) => `[Enquete] ${m.name}`],
|
|
483
|
+
[normalizedMessage.pollResultSnapshotMessage, (m) => `[Resultado de Enquete] ${m.name || ''}`.trim()],
|
|
484
|
+
[normalizedMessage.requestPhoneNumberMessage, () => '[Solicitação de telefone]'],
|
|
485
|
+
[normalizedMessage.groupInviteMessage, (m) => `[Convite de grupo] ${m.groupName || ''}`.trim()],
|
|
486
|
+
[normalizedMessage.eventMessage, (m) => `[Evento] ${m.name || ''}`.trim()],
|
|
487
|
+
[normalizedMessage.newsletterAdminInviteMessage, () => '[Convite de newsletter]'],
|
|
488
|
+
[normalizedMessage.albumMessage, () => '[Álbum]'],
|
|
489
|
+
[normalizedMessage.stickerPackMessage, () => '[Pacote de figurinhas]'],
|
|
490
|
+
[normalizedMessage.messageHistoryBundle, () => '[Histórico de mensagens]'],
|
|
491
|
+
[normalizedMessage.messageHistoryNotice, () => '[Aviso de histórico de mensagens]'],
|
|
492
|
+
[normalizedMessage.call, () => '[Chamada]'],
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
for (const [msg, fn] of handlers) {
|
|
496
|
+
if (msg) return fn(msg);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return 'Tipo de mensagem não suportado ou sem conteúdo.';
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Faz o download de mídia a partir de uma mensagem do Baileys.
|
|
504
|
+
* @param {import('@whiskeysockets/baileys').WAProto.IMessage} message - Objeto da mídia a ser baixada.
|
|
505
|
+
* @param {string} type - Tipo de mídia (ex.: `image`, `video`, `audio`, `document`).
|
|
506
|
+
* @param {string} outputPath - Diretório onde o arquivo será salvo.
|
|
507
|
+
* @returns {Promise<string|null>} Caminho do arquivo salvo ou `null` em caso de falha.
|
|
508
|
+
*/
|
|
509
|
+
export const downloadMediaMessage = async (message, type, outputPath) => {
|
|
510
|
+
if (!message || typeof message !== 'object') {
|
|
511
|
+
logger.warn('Skipping media download: invalid message payload.', { type });
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (!hasNonEmptyMediaKey(message.mediaKey)) {
|
|
516
|
+
logger.warn('Skipping media download: missing or empty media key.', {
|
|
517
|
+
type,
|
|
518
|
+
hasUrl: Boolean(message.url),
|
|
519
|
+
hasDirectPath: Boolean(message.directPath),
|
|
520
|
+
});
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (!message.url && !message.directPath) {
|
|
525
|
+
logger.warn('Skipping media download: media URL/directPath not found.', { type });
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
const stream = await downloadContentFromMessage(message, type);
|
|
531
|
+
|
|
532
|
+
const fileId = message.key?.id || Date.now();
|
|
533
|
+
const extension = getMediaExtension(type);
|
|
534
|
+
const fileName = `${Date.now()}-${fileId}.${extension}`;
|
|
535
|
+
const filePath = path.join(outputPath, fileName);
|
|
536
|
+
|
|
537
|
+
await pipeline(Readable.from(stream), createWriteStream(filePath));
|
|
538
|
+
logger.info(`Media downloaded successfully to ${filePath}`);
|
|
539
|
+
return filePath;
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (error?.message?.includes('Cannot derive from empty media key')) {
|
|
542
|
+
logger.warn('Skipping media download: invalid media key received from source.', { type });
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (isBadDecryptError(error)) {
|
|
547
|
+
logger.warn('Skipping media download: failed to decrypt media payload from source.', {
|
|
548
|
+
type,
|
|
549
|
+
code: error.code,
|
|
550
|
+
reason: error.reason || null,
|
|
551
|
+
});
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
logger.error(`Error downloading media: ${error.message}`, error);
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Detecta dinamicamente todos os tipos de midia em um objeto de mensagem
|
|
562
|
+
* @param {object} messageContent - Conteudo da mensagem
|
|
563
|
+
* @param {boolean} isQuoted - Se e de uma mensagem citada
|
|
564
|
+
* @returns {Array} Array de objetos com detalhes da midia encontrada
|
|
565
|
+
*/
|
|
566
|
+
export function detectAllMediaTypes(messageContent, isQuoted = false) {
|
|
567
|
+
if (!messageContent || typeof messageContent !== 'object') {
|
|
568
|
+
return [];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const normalizedMessage = normalizeMessage(messageContent);
|
|
572
|
+
if (!normalizedMessage || typeof normalizedMessage !== 'object') {
|
|
573
|
+
return [];
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const mediaFound = [];
|
|
577
|
+
|
|
578
|
+
for (const [key, value] of Object.entries(normalizedMessage)) {
|
|
579
|
+
if (!value || typeof value !== 'object') continue;
|
|
580
|
+
|
|
581
|
+
let mediaType = MEDIA_TYPE_MAPPING[key];
|
|
582
|
+
if (key === 'audioMessage' && value.ptt) {
|
|
583
|
+
mediaType = 'voice';
|
|
584
|
+
} else if (key === 'videoMessage' && value.ptv) {
|
|
585
|
+
mediaType = 'videoNote';
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (mediaType) {
|
|
589
|
+
mediaFound.push(buildMediaEntry(mediaType, key, value, isQuoted));
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (key.toLowerCase().includes('message')) {
|
|
594
|
+
const inferredType = key.replace(/Message$/, '').toLowerCase();
|
|
595
|
+
mediaFound.push(
|
|
596
|
+
buildMediaEntry(inferredType, key, value, isQuoted, {
|
|
597
|
+
isBinary: false,
|
|
598
|
+
isUnknownType: true,
|
|
599
|
+
}),
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return mediaFound;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Extrai detalhes da midia da mensagem de forma dinamica
|
|
609
|
+
* @param {object} message - O objeto da mensagem
|
|
610
|
+
* @param {object} options - Opcoes de configuracao
|
|
611
|
+
* @param {boolean} options.includeAllTypes - Se deve incluir todos os tipos, nao apenas binarios
|
|
612
|
+
* @param {boolean} options.includeQuoted - Se deve incluir midia de mensagens citadas
|
|
613
|
+
* @param {boolean} options.includeUnknown - Se deve incluir tipos desconhecidos
|
|
614
|
+
* @returns {{mediaType: string, mediaKey: object, details: object}|null} - Detalhes da midia ou null se nao encontrada
|
|
615
|
+
*/
|
|
616
|
+
export function extractMediaDetails(message, options = {}) {
|
|
617
|
+
const { includeAllTypes = false, includeQuoted = true, includeUnknown = false } = options;
|
|
618
|
+
|
|
619
|
+
const allMedia = collectMediaFromMessage(message, { includeQuoted });
|
|
620
|
+
const filteredMedia = filterMedia(allMedia, { includeAllTypes, includeUnknown });
|
|
621
|
+
|
|
622
|
+
if (filteredMedia.length > 0) {
|
|
623
|
+
const primaryMedia = filteredMedia[0];
|
|
624
|
+
return {
|
|
625
|
+
mediaType: primaryMedia.mediaType,
|
|
626
|
+
mediaKey: primaryMedia.mediaKey,
|
|
627
|
+
isQuoted: primaryMedia.isQuoted,
|
|
628
|
+
details: {
|
|
629
|
+
messageKey: primaryMedia.messageKey,
|
|
630
|
+
isBinary: primaryMedia.isBinary,
|
|
631
|
+
isUnknownType: primaryMedia.isUnknownType,
|
|
632
|
+
hasUrl: primaryMedia.hasUrl,
|
|
633
|
+
hasDirectPath: primaryMedia.hasDirectPath,
|
|
634
|
+
hasMediaKey: primaryMedia.hasMediaKey,
|
|
635
|
+
hasFileEncSha256: primaryMedia.hasFileEncSha256,
|
|
636
|
+
mimetype: primaryMedia.mimetype,
|
|
637
|
+
fileLength: primaryMedia.fileLength,
|
|
638
|
+
fileName: primaryMedia.fileName,
|
|
639
|
+
caption: primaryMedia.caption,
|
|
640
|
+
allMediaFound: allMedia.length > 1 ? allMedia : null,
|
|
641
|
+
},
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Extrai todos os tipos de midia de uma mensagem
|
|
650
|
+
* @param {object} message - O objeto da mensagem
|
|
651
|
+
* @param {object} options - Opcoes de configuracao
|
|
652
|
+
* @returns {Array} Array com todos os tipos de midia encontrados
|
|
653
|
+
*/
|
|
654
|
+
export function extractAllMediaDetails(message, options = {}) {
|
|
655
|
+
const { includeAllTypes = true, includeQuoted = true, includeUnknown = true } = options;
|
|
656
|
+
|
|
657
|
+
const allMedia = collectMediaFromMessage(message, { includeQuoted });
|
|
658
|
+
return filterMedia(allMedia, { includeAllTypes, includeUnknown });
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Verifica se uma mensagem contem midia
|
|
663
|
+
* @param {object} message - O objeto da mensagem
|
|
664
|
+
* @param {string} specificType - Tipo especifico para verificar (opcional)
|
|
665
|
+
* @returns {boolean} True se contem midia
|
|
666
|
+
*/
|
|
667
|
+
export function hasMedia(message, specificType = null) {
|
|
668
|
+
const allMedia = collectMediaFromMessage(message, { includeQuoted: true });
|
|
669
|
+
const filtered = filterMedia(allMedia, { includeAllTypes: true, includeUnknown: true });
|
|
670
|
+
|
|
671
|
+
if (!filtered.length) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (specificType) {
|
|
676
|
+
return filtered.some((media) => media.mediaType === specificType);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Obtem informacoes sobre os tipos de midia suportados
|
|
684
|
+
* @returns {object} Informacoes sobre tipos de midia
|
|
685
|
+
*/
|
|
686
|
+
export function getMediaTypeInfo() {
|
|
687
|
+
return {
|
|
688
|
+
knownTypes: Object.values(MEDIA_TYPE_MAPPING),
|
|
689
|
+
binaryTypes: Array.from(BINARY_MEDIA_TYPES),
|
|
690
|
+
typeMapping: { ...MEDIA_TYPE_MAPPING },
|
|
691
|
+
totalKnownTypes: Object.keys(MEDIA_TYPE_MAPPING).length,
|
|
692
|
+
};
|
|
693
|
+
}
|