@omnizap-system/omnizap 2.6.1 → 2.6.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.
- package/.env.example +78 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +6 -0
- package/app/configParts/adminIdentity.js +36 -7
- package/app/configParts/baileysConfig.js +343 -56
- package/app/configParts/groupUtils.js +226 -0
- package/app/configParts/loggerConfig.js +185 -0
- package/app/configParts/messagePersistenceService.js +307 -5
- package/app/configParts/sessionConfig.js +242 -0
- package/app/connection/baileysCompatibility.test.js +10 -1
- package/app/connection/baileysDbAuthState.js +205 -9
- package/app/connection/baileysLibsignalPatch.js +210 -0
- package/app/connection/groupOwnerWriteStateResolver.js +141 -0
- package/app/connection/socketController.js +694 -123
- package/app/connection/socketController.multiSession.test.js +128 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +96 -4
- package/app/controllers/messageProcessingPipeline.js +90 -9
- package/app/controllers/messageProcessingPipeline.test.js +202 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +452 -0
- package/app/services/multiSession/groupOwnershipRepository.js +346 -0
- package/app/services/multiSession/groupOwnershipService.js +809 -0
- package/app/services/multiSession/groupOwnershipService.test.js +317 -0
- package/app/services/multiSession/sessionRegistryService.js +239 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +391 -25
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +14 -6
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/css/payments-react.css +478 -0
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/homeReactApp.js +2 -2
- package/public/js/apps/paymentsCancelReactApp.js +45 -0
- package/public/js/apps/paymentsReactApp.js +399 -0
- package/public/js/apps/paymentsSuccessReactApp.js +148 -0
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/pagamentos-cancelado.html +21 -0
- package/public/pages/pagamentos-sucesso.html +21 -0
- package/public/pages/pagamentos.html +30 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +13 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +564 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +254 -0
- package/server/controllers/payments/paymentsController.js +731 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +228 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailAutomationRuntime.js +36 -1
- package/server/email/emailAutomationService.js +42 -1
- package/server/email/emailTemplateService.js +140 -33
- package/server/http/httpRequestUtils.js +18 -14
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +35 -3
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +50 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/payments/paymentsRouter.js +47 -0
- package/server/routes/static/staticPageRouter.js +30 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +5 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import logger from '#logger';
|
|
2
|
+
import { isGroupJid, normalizeJid } from '../config/index.js';
|
|
2
3
|
import { findById, upsert } from '../../database/index.js';
|
|
3
4
|
|
|
5
|
+
const SYSTEM_CONFIG_PREFIX = 'system:';
|
|
6
|
+
|
|
7
|
+
const normalizeGroupConfigId = (groupId) => {
|
|
8
|
+
const raw = String(groupId || '').trim();
|
|
9
|
+
if (!raw) return '';
|
|
10
|
+
return normalizeJid(raw) || raw;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const isReservedSystemConfigId = (groupId) => groupId.startsWith(SYSTEM_CONFIG_PREFIX);
|
|
14
|
+
|
|
15
|
+
const assertWritableGroupConfigId = (groupId) => {
|
|
16
|
+
if (!groupId) {
|
|
17
|
+
throw new Error('O identificador do grupo é obrigatório para persistir configurações.');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (isReservedSystemConfigId(groupId)) {
|
|
21
|
+
throw new Error(`O id ${groupId} é reservado para configurações de sistema e não pode ser salvo em group_configs.`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!isGroupJid(groupId)) {
|
|
25
|
+
throw new Error(`O id ${groupId} não representa um grupo válido para group_configs.`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
4
29
|
const groupConfigStore = {
|
|
5
30
|
/**
|
|
6
31
|
* Recupera a configuracao de um grupo especifico.
|
|
@@ -8,8 +33,17 @@ const groupConfigStore = {
|
|
|
8
33
|
* @returns {object} A configuracao do grupo, ou um objeto vazio se nao encontrado.
|
|
9
34
|
*/
|
|
10
35
|
getGroupConfig: async function (groupId) {
|
|
36
|
+
const normalizedGroupId = normalizeGroupConfigId(groupId);
|
|
37
|
+
if (!normalizedGroupId || !isGroupJid(normalizedGroupId)) {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
if (isReservedSystemConfigId(normalizedGroupId)) {
|
|
41
|
+
logger.warn('Tentativa bloqueada de leitura de configuração reservada em group_configs.', { groupId: normalizedGroupId });
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
11
45
|
try {
|
|
12
|
-
const record = await findById('group_configs',
|
|
46
|
+
const record = await findById('group_configs', normalizedGroupId);
|
|
13
47
|
if (!record || record.config === null || record.config === undefined) {
|
|
14
48
|
return {};
|
|
15
49
|
}
|
|
@@ -23,7 +57,7 @@ const groupConfigStore = {
|
|
|
23
57
|
} catch (error) {
|
|
24
58
|
logger.error('Error loading group configuration from DB:', {
|
|
25
59
|
error: error.message,
|
|
26
|
-
groupId,
|
|
60
|
+
groupId: normalizedGroupId,
|
|
27
61
|
});
|
|
28
62
|
return {};
|
|
29
63
|
}
|
|
@@ -37,18 +71,20 @@ const groupConfigStore = {
|
|
|
37
71
|
* @param {string} [newConfig.farewellMedia] - Caminho opcional para midia de despedida.
|
|
38
72
|
*/
|
|
39
73
|
updateGroupConfig: async function (groupId, newConfig) {
|
|
40
|
-
const
|
|
74
|
+
const normalizedGroupId = normalizeGroupConfigId(groupId);
|
|
75
|
+
assertWritableGroupConfigId(normalizedGroupId);
|
|
76
|
+
const currentConfig = await this.getGroupConfig(normalizedGroupId);
|
|
41
77
|
const updatedConfig = { ...currentConfig, ...newConfig };
|
|
42
78
|
try {
|
|
43
79
|
await upsert('group_configs', {
|
|
44
|
-
id:
|
|
80
|
+
id: normalizedGroupId,
|
|
45
81
|
config: JSON.stringify(updatedConfig),
|
|
46
82
|
});
|
|
47
83
|
return updatedConfig;
|
|
48
84
|
} catch (error) {
|
|
49
85
|
logger.error('Error updating group configuration in DB:', {
|
|
50
86
|
error: error.message,
|
|
51
|
-
groupId,
|
|
87
|
+
groupId: normalizedGroupId,
|
|
52
88
|
});
|
|
53
89
|
throw error;
|
|
54
90
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { TABLES, executeQuery, withTransaction } from '../../database/index.js';
|
|
2
2
|
import { isSameJidUser, normalizeJid } from '../config/index.js';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const PREMIUM_USERS_TABLE = TABLES.SYSTEM_PREMIUM_USERS;
|
|
5
|
+
const SELECT_PREMIUM_USERS_SQL = `SELECT id FROM \`${PREMIUM_USERS_TABLE}\` ORDER BY id ASC`;
|
|
6
|
+
const DELETE_ALL_PREMIUM_USERS_SQL = `DELETE FROM \`${PREMIUM_USERS_TABLE}\``;
|
|
7
|
+
const INSERT_PREMIUM_USER_SQL = `INSERT INTO \`${PREMIUM_USERS_TABLE}\` (id) VALUES (?)`;
|
|
5
8
|
|
|
6
9
|
const normalizePremiumEntry = (value) => {
|
|
7
10
|
const raw = String(value || '').trim();
|
|
@@ -23,22 +26,33 @@ const normalizeList = (list) => {
|
|
|
23
26
|
return normalizedList;
|
|
24
27
|
};
|
|
25
28
|
|
|
29
|
+
const loadPremiumUsersFromDb = async () => {
|
|
30
|
+
const rows = await executeQuery(SELECT_PREMIUM_USERS_SQL);
|
|
31
|
+
return normalizeList(rows.map((row) => row.id));
|
|
32
|
+
};
|
|
33
|
+
|
|
26
34
|
const premiumUserStore = {
|
|
27
35
|
getPremiumUsers: async function () {
|
|
28
|
-
|
|
29
|
-
return normalizeList(config.premiumUsers);
|
|
36
|
+
return loadPremiumUsersFromDb();
|
|
30
37
|
},
|
|
31
38
|
|
|
32
39
|
setPremiumUsers: async function (premiumUsers) {
|
|
33
40
|
const normalized = normalizeList(premiumUsers);
|
|
34
|
-
|
|
41
|
+
|
|
42
|
+
await withTransaction(async (connection) => {
|
|
43
|
+
await executeQuery(DELETE_ALL_PREMIUM_USERS_SQL, [], connection);
|
|
44
|
+
for (const premiumJid of normalized) {
|
|
45
|
+
await executeQuery(INSERT_PREMIUM_USER_SQL, [premiumJid], connection);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
35
49
|
return normalized;
|
|
36
50
|
},
|
|
37
51
|
|
|
38
52
|
addPremiumUsers: async function (usersToAdd) {
|
|
39
53
|
const current = await this.getPremiumUsers();
|
|
40
54
|
const updated = normalizeList([...current, ...usersToAdd]);
|
|
41
|
-
await
|
|
55
|
+
await this.setPremiumUsers(updated);
|
|
42
56
|
return updated;
|
|
43
57
|
},
|
|
44
58
|
|
|
@@ -46,7 +60,7 @@ const premiumUserStore = {
|
|
|
46
60
|
const current = await this.getPremiumUsers();
|
|
47
61
|
const normalizedTargets = normalizeList(usersToRemove);
|
|
48
62
|
const updated = current.filter((jid) => !normalizedTargets.some((target) => target === jid || isSameJidUser(target, jid)));
|
|
49
|
-
await
|
|
63
|
+
await this.setPremiumUsers(updated);
|
|
50
64
|
return updated;
|
|
51
65
|
},
|
|
52
66
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { URL } from 'node:url';
|
|
2
2
|
import { isUserAdmin, updateGroupParticipants } from '../../config/index.js';
|
|
3
|
-
import { getJidUser, isLidJid, isSameJidUser, isWhatsAppJid, normalizeJid } from '../../config/index.js';
|
|
3
|
+
import { getJidUser, isGroupJid, isLidJid, isSameJidUser, isSocketOpen, isWhatsAppJid, normalizeJid, parseEnvInt, runActiveSocketMethod, getActiveSocket } from '../../config/index.js';
|
|
4
4
|
import groupConfigStore from '../../store/groupConfigStore.js';
|
|
5
5
|
import logger from '#logger';
|
|
6
6
|
import { sendAndStore } from '../../services/messaging/messagePersistenceService.js';
|
|
7
7
|
import { extractSenderInfoFromMessage, resolveUserId } from '../../config/index.js';
|
|
8
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Base de redes conhecidas e seus domínios oficiais para permitir por categoria.
|
|
@@ -120,6 +121,13 @@ const URL_HINTS = ['https://', 'http://', 'www.'];
|
|
|
120
121
|
const STRICT_TLD_SUFFIXES = new Set(['com', 'net', 'org', 'edu', 'gov', 'mil', 'io', 'me', 'tv', 'co', 'cc', 'gg', 'gl', 'ly', 'so', 'br', 'us', 'uk', 'eu', 'de', 'fr', 'es', 'pt', 'it', 'nl', 'be', 'ch', 'at', 'se', 'no', 'fi', 'dk', 'ie', 'pl', 'cz', 'sk', 'hu', 'ro', 'bg', 'gr', 'ru', 'ua', 'tr', 'il', 'ae', 'sa', 'qa', 'eg', 'ma', 'tn', 'dz', 'za', 'ng', 'ke', 'gh', 'in', 'pk', 'bd', 'lk', 'cn', 'jp', 'kr', 'tw', 'hk', 'sg', 'my', 'th', 'vn', 'ph', 'id', 'au', 'nz', 'ca', 'mx', 'ar', 'cl', 'pe', 'uy', 'py', 'bo', 'ec', 've', 'do', 'cu', 'pa', 'cr', 'gt', 'hn', 'ni', 'sv', 'pr', 'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br', 'jus.br', 'mil.br', 'co.uk', 'org.uk', 'gov.uk', 'ac.uk', 'co.jp', 'ne.jp', 'or.jp', 'go.jp', 'ac.jp', 'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'com.mx', 'com.ar', 'com.co', 'com.pe', 'com.tr', 'com.sg', 'com.my', 'com.ph', 'co.in', 'firm.in', 'net.in', 'org.in', 'gen.in', 'ind.in', 'co.id', 'or.id', 'go.id', 'web.id', 'co.za', 'org.za', 'net.za', 'com.ng', 'com.gh', 'com.eg', 'com.sa', 'com.qa', 'com.ae', 'page.link', 'g.page']);
|
|
121
122
|
const EXTRA_TLD_SUFFIXES = new Set(['ai', 'app', 'dev', 'xyz', 'site', 'online', 'store', 'shop', 'blog', 'tech', 'cloud', 'digital', 'live', 'media', 'news', 'one', 'top', 'club', 'vip', 'fun', 'games', 'game', 'space', 'world', 'today', 'agency', 'email', 'center', 'company', 'group', 'solutions', 'systems', 'services', 'network', 'social', 'design', 'studio', 'photo', 'video', 'audio', 'music', 'art', 'wiki', 'finance', 'capital', 'money', 'loans', 'insurance', 'legal', 'law', 'health', 'care', 'clinic', 'dental', 'academy', 'school', 'college', 'university', 'education', 'training', 'support', 'chat', 'forum', 'community', 'events', 'travel', 'tours', 'hotel', 'homes', 'house', 'auto', 'cars', 'bike', 'food', 'restaurant', 'cafe', 'bar', 'pizza', 'delivery', 'fashion', 'beauty', 'style', 'fit', 'fitness', 'sports', 'download']);
|
|
122
123
|
const ANY_TLD_SUFFIXES = new Set([...STRICT_TLD_SUFFIXES, ...EXTRA_TLD_SUFFIXES]);
|
|
124
|
+
const ANTILINK_DELETE_WINDOW_MS = parseEnvInt(process.env.ANTILINK_DELETE_WINDOW_MS, 5 * 60 * 1000, 60 * 1000, 30 * 60 * 1000);
|
|
125
|
+
const ANTILINK_DELETE_MAX_MESSAGES = parseEnvInt(process.env.ANTILINK_DELETE_MAX_MESSAGES, 40, 1, 300);
|
|
126
|
+
const ANTILINK_QUERY_MAX_CANDIDATES = 20;
|
|
127
|
+
const ANTILINK_DELETE_REVALIDATION_ATTEMPTS = parseEnvInt(process.env.ANTILINK_DELETE_REVALIDATION_ATTEMPTS, 3, 1, 8);
|
|
128
|
+
const ANTILINK_DELETE_REVALIDATION_DELAY_MS = parseEnvInt(process.env.ANTILINK_DELETE_REVALIDATION_DELAY_MS, 450, 100, 5000);
|
|
129
|
+
const ANTILINK_DELETE_WINDOW_MINUTES = Math.max(1, Math.round(ANTILINK_DELETE_WINDOW_MS / (60 * 1000)));
|
|
130
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
123
131
|
|
|
124
132
|
/**
|
|
125
133
|
* Tokeniza texto por espaço/quebra de linha sem regex.
|
|
@@ -591,6 +599,317 @@ const removeParticipantWithFallback = async (sock, remoteJid, candidates = []) =
|
|
|
591
599
|
return '';
|
|
592
600
|
};
|
|
593
601
|
|
|
602
|
+
const resolveOperationalSocket = (sock) => {
|
|
603
|
+
if (isSocketOpen(sock)) return sock;
|
|
604
|
+
const activeSocket = getActiveSocket();
|
|
605
|
+
if (isSocketOpen(activeSocket)) return activeSocket;
|
|
606
|
+
return null;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const sendMessageWithFallback = async (sock, jid, content) => {
|
|
610
|
+
const operationalSocket = resolveOperationalSocket(sock);
|
|
611
|
+
if (operationalSocket) {
|
|
612
|
+
return sendAndStore(operationalSocket, jid, content);
|
|
613
|
+
}
|
|
614
|
+
return runActiveSocketMethod('sendMessage', jid, content);
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const sendDeleteWithFallback = async (sock, remoteJid, messageKey) => {
|
|
618
|
+
const operationalSocket = resolveOperationalSocket(sock);
|
|
619
|
+
if (operationalSocket && typeof operationalSocket.sendMessage === 'function') {
|
|
620
|
+
return operationalSocket.sendMessage(remoteJid, { delete: messageKey });
|
|
621
|
+
}
|
|
622
|
+
return runActiveSocketMethod('sendMessage', remoteJid, { delete: messageKey });
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const safeJsonParse = (value, fallback = null) => {
|
|
626
|
+
if (value === null || value === undefined) return fallback;
|
|
627
|
+
if (typeof value === 'object') return value;
|
|
628
|
+
if (Buffer.isBuffer(value)) {
|
|
629
|
+
return safeJsonParse(value.toString('utf8'), fallback);
|
|
630
|
+
}
|
|
631
|
+
if (typeof value !== 'string') return fallback;
|
|
632
|
+
try {
|
|
633
|
+
return JSON.parse(value);
|
|
634
|
+
} catch {
|
|
635
|
+
return fallback;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const toTimestampMs = (value) => {
|
|
640
|
+
if (value === null || value === undefined) return null;
|
|
641
|
+
if (value instanceof Date) {
|
|
642
|
+
const ms = value.getTime();
|
|
643
|
+
return Number.isFinite(ms) ? ms : null;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const numeric = Number(value);
|
|
647
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
648
|
+
if (numeric > 1e12) return numeric;
|
|
649
|
+
if (numeric > 1e10) return numeric;
|
|
650
|
+
if (numeric > 1e9) return numeric * 1000;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const parsed = Date.parse(String(value));
|
|
654
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const normalizeMessageId = (value) => {
|
|
658
|
+
if (value === null || value === undefined) return '';
|
|
659
|
+
const normalized = String(value).trim();
|
|
660
|
+
if (!normalized || normalized.length > 255) return '';
|
|
661
|
+
return normalized;
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const buildInClause = (items = []) => items.map(() => '?').join(', ');
|
|
665
|
+
|
|
666
|
+
const isMissingCanonicalSenderColumnError = (error) => {
|
|
667
|
+
const code = String(error?.code || '')
|
|
668
|
+
.trim()
|
|
669
|
+
.toUpperCase();
|
|
670
|
+
if (code === 'ER_BAD_FIELD_ERROR') return true;
|
|
671
|
+
const errno = Number(error?.errno || 0);
|
|
672
|
+
if (errno === 1054) return true;
|
|
673
|
+
const message = String(error?.message || '').toLowerCase();
|
|
674
|
+
return message.includes('canonical_sender_id') && (message.includes('unknown column') || message.includes("doesn't exist"));
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
const isSenderInCandidates = (senderJid, senderCandidates = []) => {
|
|
678
|
+
const normalizedSender = normalizeOptionalJid(senderJid);
|
|
679
|
+
if (!normalizedSender) return false;
|
|
680
|
+
|
|
681
|
+
for (const candidate of senderCandidates) {
|
|
682
|
+
const normalizedCandidate = normalizeOptionalJid(candidate);
|
|
683
|
+
if (!normalizedCandidate) continue;
|
|
684
|
+
if (normalizedCandidate === normalizedSender) return true;
|
|
685
|
+
if (isSameUserSafe(normalizedCandidate, normalizedSender)) return true;
|
|
686
|
+
}
|
|
687
|
+
return false;
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
const normalizeAddressingMode = (value) => {
|
|
691
|
+
const normalized = String(value || '')
|
|
692
|
+
.trim()
|
|
693
|
+
.toLowerCase();
|
|
694
|
+
if (normalized === 'lid' || normalized === 'pn') return normalized;
|
|
695
|
+
return '';
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
const buildDeleteMessageKey = ({ sourceKey = {}, remoteJid, messageId, senderCandidates = [], fallbackParticipant = '' }) => {
|
|
699
|
+
const normalizedRemoteJid = normalizeOptionalJid(sourceKey?.remoteJid || remoteJid);
|
|
700
|
+
const normalizedGroupJid = normalizeOptionalJid(remoteJid);
|
|
701
|
+
if (!normalizedRemoteJid || !normalizedGroupJid || normalizedRemoteJid !== normalizedGroupJid) return null;
|
|
702
|
+
|
|
703
|
+
const normalizedMessageId = normalizeMessageId(sourceKey?.id || messageId);
|
|
704
|
+
if (!normalizedMessageId) return null;
|
|
705
|
+
if (sourceKey?.fromMe === true) return null;
|
|
706
|
+
|
|
707
|
+
const keyParticipant = normalizeOptionalJid(sourceKey?.participant || sourceKey?.participantAlt || fallbackParticipant);
|
|
708
|
+
if (!keyParticipant || !isSenderInCandidates(keyParticipant, senderCandidates)) return null;
|
|
709
|
+
|
|
710
|
+
const deleteKey = {
|
|
711
|
+
remoteJid: normalizedRemoteJid,
|
|
712
|
+
id: normalizedMessageId,
|
|
713
|
+
fromMe: false,
|
|
714
|
+
participant: keyParticipant,
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const participantAlt = normalizeOptionalJid(sourceKey?.participantAlt);
|
|
718
|
+
if (participantAlt && participantAlt !== keyParticipant && isSenderInCandidates(participantAlt, senderCandidates)) {
|
|
719
|
+
deleteKey.participantAlt = participantAlt;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const addressingMode = normalizeAddressingMode(sourceKey?.addressingMode);
|
|
723
|
+
if (addressingMode) {
|
|
724
|
+
deleteKey.addressingMode = addressingMode;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return deleteKey;
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
const fetchRecentSenderMessages = async ({ remoteJid, senderCandidates = [], minimumTimestampMs, limit }) => {
|
|
731
|
+
const normalizedCandidates = uniqueNormalizedJids(senderCandidates).slice(0, ANTILINK_QUERY_MAX_CANDIDATES);
|
|
732
|
+
if (!normalizedCandidates.length) return [];
|
|
733
|
+
|
|
734
|
+
const inClause = buildInClause(normalizedCandidates);
|
|
735
|
+
const safeLimit = Math.max(1, Math.min(Number(limit) || ANTILINK_DELETE_MAX_MESSAGES, ANTILINK_DELETE_MAX_MESSAGES));
|
|
736
|
+
const queryParams = [remoteJid, new Date(minimumTimestampMs), ...normalizedCandidates, ...normalizedCandidates, safeLimit];
|
|
737
|
+
const fullQuery = `SELECT message_id, chat_id, sender_id, canonical_sender_id, raw_message, timestamp
|
|
738
|
+
FROM ${TABLES.MESSAGES}
|
|
739
|
+
WHERE chat_id = ?
|
|
740
|
+
AND timestamp IS NOT NULL
|
|
741
|
+
AND timestamp >= ?
|
|
742
|
+
AND (canonical_sender_id IN (${inClause}) OR sender_id IN (${inClause}))
|
|
743
|
+
ORDER BY timestamp DESC
|
|
744
|
+
LIMIT ?`;
|
|
745
|
+
|
|
746
|
+
try {
|
|
747
|
+
return await executeQuery(fullQuery, queryParams);
|
|
748
|
+
} catch (error) {
|
|
749
|
+
if (!isMissingCanonicalSenderColumnError(error)) {
|
|
750
|
+
throw error;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const fallbackParams = [remoteJid, new Date(minimumTimestampMs), ...normalizedCandidates, safeLimit];
|
|
754
|
+
const fallbackQuery = `SELECT message_id, chat_id, sender_id, NULL AS canonical_sender_id, raw_message, timestamp
|
|
755
|
+
FROM ${TABLES.MESSAGES}
|
|
756
|
+
WHERE chat_id = ?
|
|
757
|
+
AND timestamp IS NOT NULL
|
|
758
|
+
AND timestamp >= ?
|
|
759
|
+
AND sender_id IN (${inClause})
|
|
760
|
+
ORDER BY timestamp DESC
|
|
761
|
+
LIMIT ?`;
|
|
762
|
+
|
|
763
|
+
return executeQuery(fallbackQuery, fallbackParams);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
const collectRecentDeleteKeysForSender = async ({ messageInfo, remoteJid, senderCandidates = [] }) => {
|
|
768
|
+
const normalizedRemoteJid = normalizeOptionalJid(remoteJid);
|
|
769
|
+
if (!normalizedRemoteJid || !isGroupJid(normalizedRemoteJid)) return [];
|
|
770
|
+
|
|
771
|
+
const normalizedCandidates = uniqueNormalizedJids(senderCandidates).slice(0, ANTILINK_QUERY_MAX_CANDIDATES);
|
|
772
|
+
if (!normalizedCandidates.length) return [];
|
|
773
|
+
const preferredParticipant = normalizedCandidates.find((candidate) => isWhatsAppJid(candidate)) || normalizedCandidates[0] || '';
|
|
774
|
+
|
|
775
|
+
const minimumTimestampMs = Date.now() - ANTILINK_DELETE_WINDOW_MS;
|
|
776
|
+
const keysById = new Map();
|
|
777
|
+
|
|
778
|
+
const currentMessageKey = buildDeleteMessageKey({
|
|
779
|
+
sourceKey: messageInfo?.key || {},
|
|
780
|
+
remoteJid: normalizedRemoteJid,
|
|
781
|
+
senderCandidates: normalizedCandidates,
|
|
782
|
+
fallbackParticipant: preferredParticipant,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
if (currentMessageKey) {
|
|
786
|
+
keysById.set(currentMessageKey.id, currentMessageKey);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
let recentRows = [];
|
|
790
|
+
try {
|
|
791
|
+
recentRows = await fetchRecentSenderMessages({
|
|
792
|
+
remoteJid: normalizedRemoteJid,
|
|
793
|
+
senderCandidates: normalizedCandidates,
|
|
794
|
+
minimumTimestampMs,
|
|
795
|
+
limit: ANTILINK_DELETE_MAX_MESSAGES,
|
|
796
|
+
});
|
|
797
|
+
} catch (error) {
|
|
798
|
+
logger.warn('Falha ao buscar mensagens recentes para limpeza de antilink.', {
|
|
799
|
+
action: 'antilink_recent_fetch_error',
|
|
800
|
+
groupId: normalizedRemoteJid,
|
|
801
|
+
senderCandidates: normalizedCandidates,
|
|
802
|
+
error: error?.message,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
for (const row of recentRows) {
|
|
807
|
+
if (keysById.size >= ANTILINK_DELETE_MAX_MESSAGES) break;
|
|
808
|
+
|
|
809
|
+
const rowTimestampMs = toTimestampMs(row?.timestamp);
|
|
810
|
+
if (!rowTimestampMs || rowTimestampMs < minimumTimestampMs) continue;
|
|
811
|
+
|
|
812
|
+
const rawMessage = safeJsonParse(row?.raw_message, null);
|
|
813
|
+
const candidateKey = rawMessage?.key && typeof rawMessage.key === 'object' ? rawMessage.key : {};
|
|
814
|
+
const fallbackParticipant = normalizeOptionalJid(row?.canonical_sender_id || row?.sender_id || preferredParticipant);
|
|
815
|
+
const deleteKey = buildDeleteMessageKey({
|
|
816
|
+
sourceKey: candidateKey,
|
|
817
|
+
remoteJid: normalizedRemoteJid,
|
|
818
|
+
messageId: row?.message_id,
|
|
819
|
+
senderCandidates: normalizedCandidates,
|
|
820
|
+
fallbackParticipant,
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
if (!deleteKey) continue;
|
|
824
|
+
if (keysById.has(deleteKey.id)) continue;
|
|
825
|
+
keysById.set(deleteKey.id, deleteKey);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return Array.from(keysById.values());
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
const purgeRecentMessagesFromRemovedSender = async ({ sock, messageInfo, remoteJid, senderCandidates = [] }) => {
|
|
832
|
+
const totalRounds = Math.max(1, ANTILINK_DELETE_REVALIDATION_ATTEMPTS);
|
|
833
|
+
const seenMessageIds = new Set();
|
|
834
|
+
const deletedMessageIds = new Set();
|
|
835
|
+
let failedAttempts = 0;
|
|
836
|
+
let roundsWithDeletes = 0;
|
|
837
|
+
|
|
838
|
+
for (let round = 1; round <= totalRounds; round += 1) {
|
|
839
|
+
const deleteKeys = await collectRecentDeleteKeysForSender({
|
|
840
|
+
messageInfo,
|
|
841
|
+
remoteJid,
|
|
842
|
+
senderCandidates,
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
const pendingKeys = [];
|
|
846
|
+
for (const deleteKey of deleteKeys) {
|
|
847
|
+
const messageId = normalizeMessageId(deleteKey?.id);
|
|
848
|
+
if (!messageId) continue;
|
|
849
|
+
seenMessageIds.add(messageId);
|
|
850
|
+
if (deletedMessageIds.has(messageId)) continue;
|
|
851
|
+
pendingKeys.push(deleteKey);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
let deletedInRound = 0;
|
|
855
|
+
for (const deleteKey of pendingKeys) {
|
|
856
|
+
try {
|
|
857
|
+
await sendDeleteWithFallback(sock, remoteJid, deleteKey);
|
|
858
|
+
const messageId = normalizeMessageId(deleteKey?.id);
|
|
859
|
+
if (messageId) {
|
|
860
|
+
deletedMessageIds.add(messageId);
|
|
861
|
+
deletedInRound += 1;
|
|
862
|
+
}
|
|
863
|
+
} catch (error) {
|
|
864
|
+
failedAttempts += 1;
|
|
865
|
+
logger.debug('Falha ao apagar mensagem durante limpeza do antilink.', {
|
|
866
|
+
action: 'antilink_delete_message_failed',
|
|
867
|
+
groupId: remoteJid,
|
|
868
|
+
messageId: deleteKey?.id,
|
|
869
|
+
participant: deleteKey?.participant || null,
|
|
870
|
+
round,
|
|
871
|
+
totalRounds,
|
|
872
|
+
error: error?.message,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (deletedInRound > 0) {
|
|
878
|
+
roundsWithDeletes += 1;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (round < totalRounds) {
|
|
882
|
+
await wait(ANTILINK_DELETE_REVALIDATION_DELAY_MS);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return {
|
|
887
|
+
requested: seenMessageIds.size,
|
|
888
|
+
deleted: deletedMessageIds.size,
|
|
889
|
+
failed: failedAttempts,
|
|
890
|
+
rounds: totalRounds,
|
|
891
|
+
roundsWithDeletes,
|
|
892
|
+
};
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Limpa mensagens recentes (janela de segurança) de um participante alvo.
|
|
897
|
+
* Pode ser reutilizado por outros fluxos de moderação (ex.: comando ban).
|
|
898
|
+
* @param {Object} params
|
|
899
|
+
* @param {import('@whiskeysockets/baileys').WASocket} params.sock
|
|
900
|
+
* @param {Object|null|undefined} [params.messageInfo]
|
|
901
|
+
* @param {string} params.remoteJid
|
|
902
|
+
* @param {string[]} params.senderCandidates
|
|
903
|
+
* @returns {Promise<{requested:number, deleted:number, failed:number}>}
|
|
904
|
+
*/
|
|
905
|
+
export const purgeRecentMessagesForSenderCandidates = async ({ sock, messageInfo, remoteJid, senderCandidates = [] }) =>
|
|
906
|
+
purgeRecentMessagesFromRemovedSender({
|
|
907
|
+
sock,
|
|
908
|
+
messageInfo,
|
|
909
|
+
remoteJid,
|
|
910
|
+
senderCandidates,
|
|
911
|
+
});
|
|
912
|
+
|
|
594
913
|
/**
|
|
595
914
|
* Aplica a regra de antilink do grupo. Retorna true quando removeu e deve pular o restante.
|
|
596
915
|
* @param {Object} params
|
|
@@ -604,7 +923,10 @@ const removeParticipantWithFallback = async (sock, remoteJid, candidates = []) =
|
|
|
604
923
|
* @returns {Promise<boolean>}
|
|
605
924
|
*/
|
|
606
925
|
export const handleAntiLink = async ({ sock, messageInfo, extractedText, remoteJid, senderJid, senderIdentity, botJid }) => {
|
|
607
|
-
const
|
|
926
|
+
const normalizedRemoteJid = normalizeOptionalJid(remoteJid);
|
|
927
|
+
if (!normalizedRemoteJid || !isGroupJid(normalizedRemoteJid)) return false;
|
|
928
|
+
|
|
929
|
+
const groupConfig = await groupConfigStore.getGroupConfig(normalizedRemoteJid);
|
|
608
930
|
if (!groupConfig || !groupConfig.antilinkEnabled) return false;
|
|
609
931
|
|
|
610
932
|
const allowedDomains = getAllowedDomains(groupConfig.antilinkAllowedNetworks || [], groupConfig.antilinkAllowedDomains || []);
|
|
@@ -618,7 +940,7 @@ export const handleAntiLink = async ({ sock, messageInfo, extractedText, remoteJ
|
|
|
618
940
|
});
|
|
619
941
|
if (!senderContext.primarySenderId && senderContext.senderCandidates.length === 0) return false;
|
|
620
942
|
|
|
621
|
-
let isAdmin = await isUserAdmin(
|
|
943
|
+
let isAdmin = await isUserAdmin(normalizedRemoteJid, {
|
|
622
944
|
id: senderContext.primarySenderId || null,
|
|
623
945
|
jid: senderContext.senderInfo?.jid || senderContext.primarySenderId || null,
|
|
624
946
|
lid: senderContext.senderInfo?.lid || null,
|
|
@@ -628,7 +950,7 @@ export const handleAntiLink = async ({ sock, messageInfo, extractedText, remoteJ
|
|
|
628
950
|
});
|
|
629
951
|
|
|
630
952
|
if (!isAdmin && senderContext.primarySenderId) {
|
|
631
|
-
isAdmin = await isUserAdmin(
|
|
953
|
+
isAdmin = await isUserAdmin(normalizedRemoteJid, senderContext.primarySenderId);
|
|
632
954
|
}
|
|
633
955
|
|
|
634
956
|
const senderIsBot = isSenderBot(botJid, senderContext.senderCandidates);
|
|
@@ -637,59 +959,103 @@ export const handleAntiLink = async ({ sock, messageInfo, extractedText, remoteJ
|
|
|
637
959
|
if (senderContext.removalCandidates.length === 0) {
|
|
638
960
|
logger.warn('Antilink detectou link, mas não encontrou ID válido para remoção.', {
|
|
639
961
|
action: 'antilink_no_removal_candidate',
|
|
640
|
-
groupId:
|
|
962
|
+
groupId: normalizedRemoteJid,
|
|
641
963
|
senderCandidates: senderContext.senderCandidates,
|
|
642
964
|
});
|
|
643
965
|
return false;
|
|
644
966
|
}
|
|
645
967
|
|
|
968
|
+
let removedParticipantId = '';
|
|
646
969
|
try {
|
|
647
|
-
|
|
970
|
+
removedParticipantId = await removeParticipantWithFallback(sock, normalizedRemoteJid, senderContext.removalCandidates);
|
|
648
971
|
if (!removedParticipantId) {
|
|
649
972
|
throw new Error('Nenhum candidato de participante pôde ser removido.');
|
|
650
973
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
974
|
+
} catch (error) {
|
|
975
|
+
logger.error(`Falha ao remover usuário com antilink: ${error.message}`, {
|
|
976
|
+
action: 'antilink_error',
|
|
977
|
+
groupId: normalizedRemoteJid,
|
|
978
|
+
userId: senderContext.primarySenderId,
|
|
979
|
+
senderCandidates: senderContext.senderCandidates,
|
|
980
|
+
error: error.stack,
|
|
656
981
|
});
|
|
657
|
-
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
658
984
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
985
|
+
const deletionCandidates = uniqueNormalizedJids([removedParticipantId, ...senderContext.senderCandidates]);
|
|
986
|
+
let purgeResult = {
|
|
987
|
+
requested: 0,
|
|
988
|
+
deleted: 0,
|
|
989
|
+
failed: 0,
|
|
990
|
+
rounds: 0,
|
|
991
|
+
roundsWithDeletes: 0,
|
|
992
|
+
};
|
|
993
|
+
try {
|
|
994
|
+
purgeResult = await purgeRecentMessagesFromRemovedSender({
|
|
995
|
+
sock,
|
|
996
|
+
messageInfo,
|
|
997
|
+
remoteJid: normalizedRemoteJid,
|
|
998
|
+
senderCandidates: deletionCandidates,
|
|
999
|
+
});
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
logger.warn('Falha ao limpar mensagens recentes após remoção por antilink.', {
|
|
1002
|
+
action: 'antilink_recent_delete_error',
|
|
1003
|
+
groupId: normalizedRemoteJid,
|
|
662
1004
|
userId: removedParticipantId || senderContext.primarySenderId,
|
|
663
1005
|
senderCandidates: senderContext.senderCandidates,
|
|
1006
|
+
error: error?.message,
|
|
664
1007
|
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const senderMention = senderContext.mentionJid || removedParticipantId || senderContext.primarySenderId;
|
|
1011
|
+
const senderUser = getJidUser(senderMention);
|
|
1012
|
+
const recentDeleteLine = purgeResult.deleted > 0 ? `\n🧹 ${purgeResult.deleted} mensagem(ns) dos últimos ${ANTILINK_DELETE_WINDOW_MINUTES} minuto(s) foram apagadas.` : '';
|
|
665
1013
|
|
|
666
|
-
|
|
1014
|
+
try {
|
|
1015
|
+
await sendMessageWithFallback(sock, normalizedRemoteJid, {
|
|
1016
|
+
text: `🚫 @${senderUser || 'usuario'} foi removido por enviar um link.${recentDeleteLine}`,
|
|
1017
|
+
mentions: senderMention ? [senderMention] : [],
|
|
1018
|
+
});
|
|
667
1019
|
} catch (error) {
|
|
668
|
-
logger.
|
|
669
|
-
action: '
|
|
670
|
-
groupId:
|
|
671
|
-
userId: senderContext.primarySenderId,
|
|
1020
|
+
logger.warn('Falha ao enviar aviso de remoção por antilink.', {
|
|
1021
|
+
action: 'antilink_remove_notice_error',
|
|
1022
|
+
groupId: normalizedRemoteJid,
|
|
1023
|
+
userId: removedParticipantId || senderContext.primarySenderId,
|
|
672
1024
|
senderCandidates: senderContext.senderCandidates,
|
|
673
|
-
error: error
|
|
1025
|
+
error: error?.message,
|
|
674
1026
|
});
|
|
675
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
logger.info(`Usuário ${removedParticipantId || senderContext.primarySenderId} removido do grupo ${normalizedRemoteJid} por enviar link.`, {
|
|
1030
|
+
action: 'antilink_remove',
|
|
1031
|
+
groupId: normalizedRemoteJid,
|
|
1032
|
+
userId: removedParticipantId || senderContext.primarySenderId,
|
|
1033
|
+
senderCandidates: senderContext.senderCandidates,
|
|
1034
|
+
deletedRecentMessages: purgeResult.deleted,
|
|
1035
|
+
failedRecentMessageDeletes: purgeResult.failed,
|
|
1036
|
+
requestedRecentMessageDeletes: purgeResult.requested,
|
|
1037
|
+
deleteRevalidationRounds: purgeResult.rounds,
|
|
1038
|
+
deleteRevalidationRoundsWithDeletes: purgeResult.roundsWithDeletes,
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
return true;
|
|
676
1042
|
} else if (isAdmin && !senderIsBot) {
|
|
677
1043
|
try {
|
|
678
1044
|
const senderMention = senderContext.mentionJid || senderContext.primarySenderId;
|
|
679
1045
|
const senderUser = getJidUser(senderMention);
|
|
680
|
-
await
|
|
1046
|
+
await sendMessageWithFallback(sock, normalizedRemoteJid, {
|
|
681
1047
|
text: `ⓘ @${senderUser || 'admin'} (admin) enviou um link.`,
|
|
682
1048
|
mentions: senderMention ? [senderMention] : [],
|
|
683
1049
|
});
|
|
684
|
-
logger.info(`Admin ${senderContext.primarySenderId} enviou um link no grupo ${
|
|
1050
|
+
logger.info(`Admin ${senderContext.primarySenderId} enviou um link no grupo ${normalizedRemoteJid} (aviso enviado).`, {
|
|
685
1051
|
action: 'antilink_admin_link_detected',
|
|
686
|
-
groupId:
|
|
1052
|
+
groupId: normalizedRemoteJid,
|
|
687
1053
|
userId: senderContext.primarySenderId,
|
|
688
1054
|
});
|
|
689
1055
|
} catch (error) {
|
|
690
1056
|
logger.error(`Falha ao enviar aviso de link de admin: ${error.message}`, {
|
|
691
1057
|
action: 'antilink_admin_warning_error',
|
|
692
|
-
groupId:
|
|
1058
|
+
groupId: normalizedRemoteJid,
|
|
693
1059
|
userId: senderContext.primarySenderId,
|
|
694
1060
|
error: error.stack,
|
|
695
1061
|
});
|