@omnizap-system/omnizap 2.6.1 → 2.6.2
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 +54 -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 +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +226 -55
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +143 -3
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +625 -124
- package/app/connection/socketController.multiSession.test.js +108 -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 +80 -2
- package/app/controllers/messageProcessingPipeline.js +88 -9
- package/app/controllers/messageProcessingPipeline.test.js +200 -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 +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -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 +352 -16
- 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 +12 -5
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- 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/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 +10 -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 +317 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +267 -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 +254 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +3 -2
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +2 -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,37 +959,51 @@ 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
|
|
|
646
968
|
try {
|
|
647
|
-
const removedParticipantId = await removeParticipantWithFallback(sock,
|
|
969
|
+
const removedParticipantId = await removeParticipantWithFallback(sock, normalizedRemoteJid, senderContext.removalCandidates);
|
|
648
970
|
if (!removedParticipantId) {
|
|
649
971
|
throw new Error('Nenhum candidato de participante pôde ser removido.');
|
|
650
972
|
}
|
|
973
|
+
|
|
974
|
+
const deletionCandidates = uniqueNormalizedJids([removedParticipantId, ...senderContext.senderCandidates]);
|
|
975
|
+
const purgeResult = await purgeRecentMessagesFromRemovedSender({
|
|
976
|
+
sock,
|
|
977
|
+
messageInfo,
|
|
978
|
+
remoteJid: normalizedRemoteJid,
|
|
979
|
+
senderCandidates: deletionCandidates,
|
|
980
|
+
});
|
|
981
|
+
|
|
651
982
|
const senderMention = senderContext.mentionJid || removedParticipantId || senderContext.primarySenderId;
|
|
652
983
|
const senderUser = getJidUser(senderMention);
|
|
653
|
-
|
|
654
|
-
|
|
984
|
+
const recentDeleteLine = purgeResult.deleted > 0 ? `\n🧹 ${purgeResult.deleted} mensagem(ns) dos últimos ${ANTILINK_DELETE_WINDOW_MINUTES} minuto(s) foram apagadas.` : '';
|
|
985
|
+
await sendMessageWithFallback(sock, normalizedRemoteJid, {
|
|
986
|
+
text: `🚫 @${senderUser || 'usuario'} foi removido por enviar um link.${recentDeleteLine}`,
|
|
655
987
|
mentions: senderMention ? [senderMention] : [],
|
|
656
988
|
});
|
|
657
|
-
await sendAndStore(sock, remoteJid, { delete: messageInfo.key });
|
|
658
989
|
|
|
659
|
-
logger.info(`Usuário ${removedParticipantId || senderContext.primarySenderId} removido do grupo ${
|
|
990
|
+
logger.info(`Usuário ${removedParticipantId || senderContext.primarySenderId} removido do grupo ${normalizedRemoteJid} por enviar link.`, {
|
|
660
991
|
action: 'antilink_remove',
|
|
661
|
-
groupId:
|
|
992
|
+
groupId: normalizedRemoteJid,
|
|
662
993
|
userId: removedParticipantId || senderContext.primarySenderId,
|
|
663
994
|
senderCandidates: senderContext.senderCandidates,
|
|
995
|
+
deletedRecentMessages: purgeResult.deleted,
|
|
996
|
+
failedRecentMessageDeletes: purgeResult.failed,
|
|
997
|
+
requestedRecentMessageDeletes: purgeResult.requested,
|
|
998
|
+
deleteRevalidationRounds: purgeResult.rounds,
|
|
999
|
+
deleteRevalidationRoundsWithDeletes: purgeResult.roundsWithDeletes,
|
|
664
1000
|
});
|
|
665
1001
|
|
|
666
1002
|
return true;
|
|
667
1003
|
} catch (error) {
|
|
668
1004
|
logger.error(`Falha ao remover usuário com antilink: ${error.message}`, {
|
|
669
1005
|
action: 'antilink_error',
|
|
670
|
-
groupId:
|
|
1006
|
+
groupId: normalizedRemoteJid,
|
|
671
1007
|
userId: senderContext.primarySenderId,
|
|
672
1008
|
senderCandidates: senderContext.senderCandidates,
|
|
673
1009
|
error: error.stack,
|
|
@@ -677,19 +1013,19 @@ export const handleAntiLink = async ({ sock, messageInfo, extractedText, remoteJ
|
|
|
677
1013
|
try {
|
|
678
1014
|
const senderMention = senderContext.mentionJid || senderContext.primarySenderId;
|
|
679
1015
|
const senderUser = getJidUser(senderMention);
|
|
680
|
-
await
|
|
1016
|
+
await sendMessageWithFallback(sock, normalizedRemoteJid, {
|
|
681
1017
|
text: `ⓘ @${senderUser || 'admin'} (admin) enviou um link.`,
|
|
682
1018
|
mentions: senderMention ? [senderMention] : [],
|
|
683
1019
|
});
|
|
684
|
-
logger.info(`Admin ${senderContext.primarySenderId} enviou um link no grupo ${
|
|
1020
|
+
logger.info(`Admin ${senderContext.primarySenderId} enviou um link no grupo ${normalizedRemoteJid} (aviso enviado).`, {
|
|
685
1021
|
action: 'antilink_admin_link_detected',
|
|
686
|
-
groupId:
|
|
1022
|
+
groupId: normalizedRemoteJid,
|
|
687
1023
|
userId: senderContext.primarySenderId,
|
|
688
1024
|
});
|
|
689
1025
|
} catch (error) {
|
|
690
1026
|
logger.error(`Falha ao enviar aviso de link de admin: ${error.message}`, {
|
|
691
1027
|
action: 'antilink_admin_warning_error',
|
|
692
|
-
groupId:
|
|
1028
|
+
groupId: normalizedRemoteJid,
|
|
693
1029
|
userId: senderContext.primarySenderId,
|
|
694
1030
|
error: error.stack,
|
|
695
1031
|
});
|