@omnizap-system/omnizap 2.6.0 → 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 +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- 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 +660 -158
- 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 +93 -13
- 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 +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- 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 +135 -27
- 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 +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- 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/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- 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/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- 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/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- 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/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-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/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- 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 +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/js/apps/apiDocsApp.js +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- 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/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import assert from 'node:assert/strict';
|
|
2
3
|
import { after, afterEach, beforeEach, test } from 'node:test';
|
|
3
4
|
|
|
@@ -16,6 +17,7 @@ const ENV_OVERRIDES = {
|
|
|
16
17
|
DB_MONITOR_ENABLED: 'false',
|
|
17
18
|
METRICS_ENABLED: 'false',
|
|
18
19
|
ADMIN_AI_HELP_SCHEDULER_ENABLED: 'false',
|
|
20
|
+
WHATSAPP_ADMIN_JID: OWNER_JID,
|
|
19
21
|
USER_ADMIN: OWNER_PHONE,
|
|
20
22
|
};
|
|
21
23
|
|
|
@@ -26,7 +28,7 @@ for (const [key, value] of Object.entries(ENV_OVERRIDES)) {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const originalArgv1 = process.argv[1];
|
|
29
|
-
process.argv[1] = '
|
|
31
|
+
process.argv[1] = new URL('../../../database/init.js', import.meta.url).pathname;
|
|
30
32
|
|
|
31
33
|
let pool;
|
|
32
34
|
let handleAdminCommand;
|
|
@@ -55,27 +57,113 @@ const normalizeSql = (sql) =>
|
|
|
55
57
|
const createDbHarness = () => {
|
|
56
58
|
const groupConfigRows = new Map();
|
|
57
59
|
const groupMetadataRows = new Map();
|
|
60
|
+
const premiumUserRows = new Set();
|
|
61
|
+
const groupUserWarningsRows = [];
|
|
62
|
+
let warningAutoIncrement = 1;
|
|
58
63
|
|
|
59
64
|
const execute = async (sql, params = []) => {
|
|
60
65
|
const normalized = normalizeSql(sql);
|
|
66
|
+
const normalizedNoTicks = normalized.replaceAll('`', '');
|
|
61
67
|
|
|
62
|
-
if (
|
|
68
|
+
if (normalizedNoTicks.startsWith('select * from groups_metadata where id = ?')) {
|
|
63
69
|
const row = groupMetadataRows.get(params[0]);
|
|
64
70
|
return [[row].filter(Boolean), []];
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
if (
|
|
73
|
+
if (normalizedNoTicks.startsWith('select * from group_configs where id = ?')) {
|
|
68
74
|
const row = groupConfigRows.get(params[0]);
|
|
69
75
|
return [[row].filter(Boolean), []];
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
if (
|
|
78
|
+
if (normalizedNoTicks.startsWith('insert into group_configs')) {
|
|
73
79
|
const [id, config] = params;
|
|
74
80
|
groupConfigRows.set(id, { id, config: String(config) });
|
|
75
81
|
return [{ affectedRows: 1 }, []];
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
if (
|
|
84
|
+
if (normalizedNoTicks.startsWith('select id from system_premium_users')) {
|
|
85
|
+
const rows = Array.from(premiumUserRows.values())
|
|
86
|
+
.sort((left, right) => String(left).localeCompare(String(right)))
|
|
87
|
+
.map((id) => ({ id }));
|
|
88
|
+
return [rows, []];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (normalizedNoTicks.startsWith('delete from system_premium_users')) {
|
|
92
|
+
premiumUserRows.clear();
|
|
93
|
+
return [{ affectedRows: 1 }, []];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (normalizedNoTicks.startsWith('insert into system_premium_users')) {
|
|
97
|
+
const [id] = params;
|
|
98
|
+
premiumUserRows.add(String(id || ''));
|
|
99
|
+
return [{ affectedRows: 1 }, []];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (normalizedNoTicks.startsWith('insert into group_user_warnings')) {
|
|
103
|
+
const [groupId, participantJid, warnedByJid, reason] = params;
|
|
104
|
+
groupUserWarningsRows.push({
|
|
105
|
+
id: warningAutoIncrement++,
|
|
106
|
+
group_id: String(groupId || ''),
|
|
107
|
+
participant_jid: String(participantJid || '').toLowerCase(),
|
|
108
|
+
warned_by_jid: warnedByJid ? String(warnedByJid).toLowerCase() : null,
|
|
109
|
+
reason: reason ? String(reason) : null,
|
|
110
|
+
created_at: new Date(__timeNowMs()).toISOString(),
|
|
111
|
+
});
|
|
112
|
+
return [{ affectedRows: 1 }, []];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (normalizedNoTicks.startsWith('select count(*) as total from group_user_warnings')) {
|
|
116
|
+
const [groupId, participantJid] = params;
|
|
117
|
+
const filtered = groupUserWarningsRows.filter((row) => row.group_id === String(groupId || '') && row.participant_jid === String(participantJid || '').toLowerCase());
|
|
118
|
+
return [[{ total: filtered.length }], []];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (normalizedNoTicks.startsWith('select id, group_id, participant_jid, warned_by_jid, reason, created_at from group_user_warnings')) {
|
|
122
|
+
const [groupId, participantJid, limit] = params;
|
|
123
|
+
const safeLimit = Number.parseInt(String(limit || 0), 10);
|
|
124
|
+
const filtered = groupUserWarningsRows
|
|
125
|
+
.filter((row) => row.group_id === String(groupId || '') && row.participant_jid === String(participantJid || '').toLowerCase())
|
|
126
|
+
.sort((left, right) => right.id - left.id)
|
|
127
|
+
.slice(0, Number.isFinite(safeLimit) && safeLimit > 0 ? safeLimit : 20)
|
|
128
|
+
.map((row) => ({ ...row }));
|
|
129
|
+
return [filtered, []];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (normalizedNoTicks.startsWith('delete from group_user_warnings') && normalizedNoTicks.includes('order by id desc limit ?')) {
|
|
133
|
+
const [groupId, participantJid, limit] = params;
|
|
134
|
+
const safeGroupId = String(groupId || '');
|
|
135
|
+
const safeParticipantJid = String(participantJid || '').toLowerCase();
|
|
136
|
+
const safeLimit = Number.parseInt(String(limit || 0), 10);
|
|
137
|
+
const rowsToDelete = groupUserWarningsRows
|
|
138
|
+
.filter((row) => row.group_id === safeGroupId && row.participant_jid === safeParticipantJid)
|
|
139
|
+
.sort((left, right) => right.id - left.id)
|
|
140
|
+
.slice(0, Number.isFinite(safeLimit) && safeLimit > 0 ? safeLimit : 1)
|
|
141
|
+
.map((row) => row.id);
|
|
142
|
+
|
|
143
|
+
if (rowsToDelete.length > 0) {
|
|
144
|
+
for (let index = groupUserWarningsRows.length - 1; index >= 0; index -= 1) {
|
|
145
|
+
if (!rowsToDelete.includes(groupUserWarningsRows[index].id)) continue;
|
|
146
|
+
groupUserWarningsRows.splice(index, 1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return [{ affectedRows: rowsToDelete.length }, []];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (normalizedNoTicks.startsWith('delete from group_user_warnings')) {
|
|
154
|
+
const [groupId, participantJid] = params;
|
|
155
|
+
const safeGroupId = String(groupId || '');
|
|
156
|
+
const safeParticipantJid = String(participantJid || '').toLowerCase();
|
|
157
|
+
let removed = 0;
|
|
158
|
+
for (let index = groupUserWarningsRows.length - 1; index >= 0; index -= 1) {
|
|
159
|
+
if (groupUserWarningsRows[index].group_id !== safeGroupId || groupUserWarningsRows[index].participant_jid !== safeParticipantJid) continue;
|
|
160
|
+
groupUserWarningsRows.splice(index, 1);
|
|
161
|
+
removed += 1;
|
|
162
|
+
}
|
|
163
|
+
return [{ affectedRows: removed }, []];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (normalizedNoTicks.includes('from lid_map') || normalizedNoTicks.includes('into lid_map') || normalizedNoTicks.startsWith('update messages')) {
|
|
79
167
|
return [[], []];
|
|
80
168
|
}
|
|
81
169
|
|
|
@@ -101,11 +189,19 @@ const createDbHarness = () => {
|
|
|
101
189
|
return row ? JSON.parse(row.config) : {};
|
|
102
190
|
};
|
|
103
191
|
|
|
192
|
+
const setPremiumUsers = (premiumUsers) => {
|
|
193
|
+
premiumUserRows.clear();
|
|
194
|
+
for (const premiumUser of premiumUsers || []) {
|
|
195
|
+
premiumUserRows.add(String(premiumUser || ''));
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
104
199
|
return {
|
|
105
200
|
execute,
|
|
106
201
|
setGroupParticipants,
|
|
107
202
|
setGroupConfig,
|
|
108
203
|
getGroupConfig,
|
|
204
|
+
setPremiumUsers,
|
|
109
205
|
};
|
|
110
206
|
};
|
|
111
207
|
|
|
@@ -123,7 +219,7 @@ const createSockStub = () => {
|
|
|
123
219
|
return {
|
|
124
220
|
key: { remoteJid: jid },
|
|
125
221
|
message: content,
|
|
126
|
-
messageTimestamp: Math.floor(
|
|
222
|
+
messageTimestamp: Math.floor(__timeNowMs() / 1000),
|
|
127
223
|
};
|
|
128
224
|
},
|
|
129
225
|
groupParticipantsUpdate: async (groupId, participants, action) => {
|
|
@@ -134,10 +230,26 @@ const createSockStub = () => {
|
|
|
134
230
|
};
|
|
135
231
|
};
|
|
136
232
|
|
|
137
|
-
const buildMessageInfo = (participant = OWNER_JID) =>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
233
|
+
const buildMessageInfo = (participant = OWNER_JID, { mentionedJid = [], replyParticipant = '' } = {}) => {
|
|
234
|
+
const contextInfo = {};
|
|
235
|
+
if (Array.isArray(mentionedJid) && mentionedJid.length > 0) {
|
|
236
|
+
contextInfo.mentionedJid = mentionedJid;
|
|
237
|
+
}
|
|
238
|
+
if (replyParticipant) {
|
|
239
|
+
contextInfo.participant = replyParticipant;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
key: { participant },
|
|
244
|
+
message: Object.keys(contextInfo).length
|
|
245
|
+
? {
|
|
246
|
+
extendedTextMessage: {
|
|
247
|
+
contextInfo,
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
: {},
|
|
251
|
+
};
|
|
252
|
+
};
|
|
141
253
|
|
|
142
254
|
const runAdminCommand = async ({ command, args = [], text = args.join(' '), sock, senderJid = OWNER_JID, remoteJid = GROUP_JID, isGroupMessage = true, messageInfo, botJid = BOT_JID }) =>
|
|
143
255
|
handleAdminCommand({
|
|
@@ -181,6 +293,13 @@ after(() => {
|
|
|
181
293
|
test('isAdminCommand reconhece comandos válidos', () => {
|
|
182
294
|
assert.equal(isAdminCommand('nsfw'), true);
|
|
183
295
|
assert.equal(isAdminCommand('banir'), true);
|
|
296
|
+
assert.equal(isAdminCommand('warn'), true);
|
|
297
|
+
assert.equal(isAdminCommand('warnings'), true);
|
|
298
|
+
assert.equal(isAdminCommand('clearwarn'), true);
|
|
299
|
+
assert.equal(isAdminCommand('warnlimit'), true);
|
|
300
|
+
assert.equal(isAdminCommand('stickerallowance'), true);
|
|
301
|
+
assert.equal(isAdminCommand('noticiasfiltro'), true);
|
|
302
|
+
assert.equal(isAdminCommand('grupoaudit'), true);
|
|
184
303
|
assert.equal(isAdminCommand('comando-inexistente'), false);
|
|
185
304
|
});
|
|
186
305
|
|
|
@@ -288,6 +407,167 @@ test('ban bloqueia tentativa de remover o próprio bot', async () => {
|
|
|
288
407
|
assert.equal(messages[0].content.text, 'Operação cancelada: o bot não pode remover a própria conta.');
|
|
289
408
|
});
|
|
290
409
|
|
|
410
|
+
test('warn registra advertência e warnings lista histórico', async () => {
|
|
411
|
+
const { sock, messages } = createSockStub();
|
|
412
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
413
|
+
|
|
414
|
+
await runAdminCommand({
|
|
415
|
+
command: 'warn',
|
|
416
|
+
args: ['@alvo', 'spam', 'repetitivo'],
|
|
417
|
+
sock,
|
|
418
|
+
messageInfo: buildMessageInfo(OWNER_JID, { mentionedJid: [TARGET_JID] }),
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
assert.match(messages[messages.length - 1].content.text, /Advertência registrada/i);
|
|
422
|
+
assert.match(messages[messages.length - 1].content.text, /spam repetitivo/i);
|
|
423
|
+
|
|
424
|
+
await runAdminCommand({
|
|
425
|
+
command: 'warnings',
|
|
426
|
+
args: [],
|
|
427
|
+
sock,
|
|
428
|
+
messageInfo: buildMessageInfo(OWNER_JID, { replyParticipant: TARGET_JID }),
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
assert.match(messages[messages.length - 1].content.text, /Histórico de advertências/i);
|
|
432
|
+
assert.match(messages[messages.length - 1].content.text, /Total neste grupo: \*1\*/i);
|
|
433
|
+
assert.match(messages[messages.length - 1].content.text, /spam repetitivo/i);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('warn aplica auto-ban no limite padrão de 3 advertências', async () => {
|
|
437
|
+
const { sock, messages, participantUpdates } = createSockStub();
|
|
438
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
439
|
+
|
|
440
|
+
await runAdminCommand({
|
|
441
|
+
command: 'warn',
|
|
442
|
+
args: [TARGET_JID, 'motivo-1'],
|
|
443
|
+
sock,
|
|
444
|
+
});
|
|
445
|
+
await runAdminCommand({
|
|
446
|
+
command: 'warn',
|
|
447
|
+
args: [TARGET_JID, 'motivo-2'],
|
|
448
|
+
sock,
|
|
449
|
+
});
|
|
450
|
+
await runAdminCommand({
|
|
451
|
+
command: 'warn',
|
|
452
|
+
args: [TARGET_JID, 'motivo-3'],
|
|
453
|
+
sock,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
assert.equal(participantUpdates.length, 1);
|
|
457
|
+
assert.deepEqual(participantUpdates[0], {
|
|
458
|
+
groupId: GROUP_JID,
|
|
459
|
+
participants: [TARGET_JID],
|
|
460
|
+
action: 'remove',
|
|
461
|
+
});
|
|
462
|
+
assert.match(messages[messages.length - 1].content.text, /Auto-ban configurado para: \*3\*/i);
|
|
463
|
+
assert.match(messages[messages.length - 1].content.text, /Limite atingido/i);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('warnlimit permite ajustar limite por grupo e resetar para padrão', async () => {
|
|
467
|
+
const { sock, messages, participantUpdates } = createSockStub();
|
|
468
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
469
|
+
|
|
470
|
+
await runAdminCommand({
|
|
471
|
+
command: 'warnlimit',
|
|
472
|
+
args: ['2'],
|
|
473
|
+
sock,
|
|
474
|
+
});
|
|
475
|
+
assert.equal(dbHarness.getGroupConfig(GROUP_JID).warnAutoBanThreshold, 2);
|
|
476
|
+
|
|
477
|
+
await runAdminCommand({
|
|
478
|
+
command: 'warn',
|
|
479
|
+
args: [TARGET_JID, 'motivo-1'],
|
|
480
|
+
sock,
|
|
481
|
+
});
|
|
482
|
+
await runAdminCommand({
|
|
483
|
+
command: 'warn',
|
|
484
|
+
args: [TARGET_JID, 'motivo-2'],
|
|
485
|
+
sock,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
assert.equal(participantUpdates.length, 1);
|
|
489
|
+
assert.deepEqual(participantUpdates[0], {
|
|
490
|
+
groupId: GROUP_JID,
|
|
491
|
+
participants: [TARGET_JID],
|
|
492
|
+
action: 'remove',
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
await runAdminCommand({
|
|
496
|
+
command: 'warnlimit',
|
|
497
|
+
args: ['status'],
|
|
498
|
+
sock,
|
|
499
|
+
});
|
|
500
|
+
assert.match(messages[messages.length - 1].content.text, /Limite atual de auto-ban/i);
|
|
501
|
+
assert.match(messages[messages.length - 1].content.text, /\*2\*/);
|
|
502
|
+
|
|
503
|
+
await runAdminCommand({
|
|
504
|
+
command: 'warnlimit',
|
|
505
|
+
args: ['reset'],
|
|
506
|
+
sock,
|
|
507
|
+
});
|
|
508
|
+
assert.equal(dbHarness.getGroupConfig(GROUP_JID).warnAutoBanThreshold, null);
|
|
509
|
+
assert.match(messages[messages.length - 1].content.text, /padrão: \*3\*/i);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test('clearwarn remove parcialmente e depois remove todas as advertências', async () => {
|
|
513
|
+
const { sock, messages } = createSockStub();
|
|
514
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
515
|
+
|
|
516
|
+
await runAdminCommand({
|
|
517
|
+
command: 'warn',
|
|
518
|
+
args: [TARGET_JID, 'motivo-1'],
|
|
519
|
+
sock,
|
|
520
|
+
});
|
|
521
|
+
await runAdminCommand({
|
|
522
|
+
command: 'warn',
|
|
523
|
+
args: [TARGET_JID, 'motivo-2'],
|
|
524
|
+
sock,
|
|
525
|
+
});
|
|
526
|
+
await runAdminCommand({
|
|
527
|
+
command: 'warn',
|
|
528
|
+
args: [TARGET_JID, 'motivo-3'],
|
|
529
|
+
sock,
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
await runAdminCommand({
|
|
533
|
+
command: 'clearwarn',
|
|
534
|
+
args: [TARGET_JID, '2'],
|
|
535
|
+
sock,
|
|
536
|
+
});
|
|
537
|
+
assert.match(messages[messages.length - 1].content.text, /removi \*2 advertência\(s\)\*/i);
|
|
538
|
+
assert.match(messages[messages.length - 1].content.text, /Advertências restantes neste grupo: \*1\*/i);
|
|
539
|
+
|
|
540
|
+
await runAdminCommand({
|
|
541
|
+
command: 'clearwarn',
|
|
542
|
+
args: [TARGET_JID, 'all'],
|
|
543
|
+
sock,
|
|
544
|
+
});
|
|
545
|
+
assert.match(messages[messages.length - 1].content.text, /todas as advertências \(1\)/i);
|
|
546
|
+
assert.match(messages[messages.length - 1].content.text, /Advertências restantes neste grupo: \*0\*/i);
|
|
547
|
+
|
|
548
|
+
await runAdminCommand({
|
|
549
|
+
command: 'warnings',
|
|
550
|
+
args: [TARGET_JID],
|
|
551
|
+
sock,
|
|
552
|
+
});
|
|
553
|
+
assert.match(messages[messages.length - 1].content.text, /não possui advertências/i);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test('clearwarn retorna uso ao receber quantidade inválida', async () => {
|
|
557
|
+
const { sock, messages } = createSockStub();
|
|
558
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
559
|
+
|
|
560
|
+
await runAdminCommand({
|
|
561
|
+
command: 'clearwarn',
|
|
562
|
+
args: [TARGET_JID, 'zero'],
|
|
563
|
+
sock,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
assert.equal(messages.length, 1);
|
|
567
|
+
assert.match(messages[0].content.text, /Formato de uso/i);
|
|
568
|
+
assert.match(messages[0].content.text, /clearwarn/i);
|
|
569
|
+
});
|
|
570
|
+
|
|
291
571
|
test('premium exige admin principal e lista usuários quando autorizado', async () => {
|
|
292
572
|
const texts = getAdminTextConfig();
|
|
293
573
|
const denied = createSockStub();
|
|
@@ -305,7 +585,7 @@ test('premium exige admin principal e lista usuários quando autorizado', async
|
|
|
305
585
|
assert.equal(denied.messages.length, 1);
|
|
306
586
|
assert.equal(denied.messages[0].content.text, texts.owner_only_command_message);
|
|
307
587
|
|
|
308
|
-
dbHarness.
|
|
588
|
+
dbHarness.setPremiumUsers([TARGET_JID]);
|
|
309
589
|
const allowed = createSockStub();
|
|
310
590
|
|
|
311
591
|
await runAdminCommand({
|
|
@@ -348,3 +628,90 @@ test('prefix atualiza, consulta status e reseta para padrão', async () => {
|
|
|
348
628
|
});
|
|
349
629
|
assert.equal(dbHarness.getGroupConfig(GROUP_JID).commandPrefix, null);
|
|
350
630
|
});
|
|
631
|
+
|
|
632
|
+
test('stickerallowance atualiza e consulta limite por janela', async () => {
|
|
633
|
+
const { sock, messages } = createSockStub();
|
|
634
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
635
|
+
|
|
636
|
+
await runAdminCommand({
|
|
637
|
+
command: 'stickerallowance',
|
|
638
|
+
args: ['4'],
|
|
639
|
+
sock,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const updatedConfig = dbHarness.getGroupConfig(GROUP_JID);
|
|
643
|
+
assert.equal(updatedConfig.stickerFocusMessageAllowance, 4);
|
|
644
|
+
assert.equal(updatedConfig.stickerFocusMessageAllowanceCount, 4);
|
|
645
|
+
|
|
646
|
+
await runAdminCommand({
|
|
647
|
+
command: 'stickerallowance',
|
|
648
|
+
args: ['status'],
|
|
649
|
+
sock,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
assert.match(messages[messages.length - 1].content.text, /Limite atual de mensagens por usuário/i);
|
|
653
|
+
assert.match(messages[messages.length - 1].content.text, /\*4\*/);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test('noticiasfiltro aplica source/tag e trending no config do grupo', async () => {
|
|
657
|
+
const { sock } = createSockStub();
|
|
658
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
659
|
+
|
|
660
|
+
await runAdminCommand({
|
|
661
|
+
command: 'noticiasfiltro',
|
|
662
|
+
args: ['source', 'add', 'ann,mal'],
|
|
663
|
+
sock,
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
await runAdminCommand({
|
|
667
|
+
command: 'noticiasfiltro',
|
|
668
|
+
args: ['tag', 'add', 'shounen'],
|
|
669
|
+
sock,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
await runAdminCommand({
|
|
673
|
+
command: 'noticiasfiltro',
|
|
674
|
+
args: ['trending', 'on'],
|
|
675
|
+
sock,
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const updatedConfig = dbHarness.getGroupConfig(GROUP_JID);
|
|
679
|
+
assert.deepEqual(updatedConfig.newsSourceIds, ['ann', 'mal']);
|
|
680
|
+
assert.deepEqual(updatedConfig.newsEntitySlugs, ['shounen']);
|
|
681
|
+
assert.equal(updatedConfig.newsOnlyTrending, true);
|
|
682
|
+
assert.equal(updatedConfig.newsFilters.onlyTrending, true);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test('grupoaudit retorna resumo consolidado do grupo', async () => {
|
|
686
|
+
const { sock, messages } = createSockStub();
|
|
687
|
+
dbHarness.setGroupParticipants(GROUP_JID, [{ id: OWNER_JID, admin: 'admin' }]);
|
|
688
|
+
dbHarness.setGroupConfig(GROUP_JID, {
|
|
689
|
+
commandPrefix: '!',
|
|
690
|
+
nsfwEnabled: true,
|
|
691
|
+
autoStickerEnabled: false,
|
|
692
|
+
stickerFocusEnabled: true,
|
|
693
|
+
stickerFocusMessageCooldownMinutes: 30,
|
|
694
|
+
stickerFocusMessageAllowance: 3,
|
|
695
|
+
captchaEnabled: true,
|
|
696
|
+
autoApproveRequestsEnabled: false,
|
|
697
|
+
antilinkEnabled: true,
|
|
698
|
+
antilinkAllowedNetworks: ['youtube'],
|
|
699
|
+
antilinkAllowedDomains: ['example.com'],
|
|
700
|
+
newsEnabled: true,
|
|
701
|
+
newsSentIds: ['n1', 'n2'],
|
|
702
|
+
newsLastSentAt: '2026-03-18T00:00:00.000Z',
|
|
703
|
+
welcomeMessageEnabled: true,
|
|
704
|
+
farewellMessageEnabled: false,
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
await runAdminCommand({
|
|
708
|
+
command: 'grupoaudit',
|
|
709
|
+
args: [],
|
|
710
|
+
sock,
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
assert.equal(messages.length, 1);
|
|
714
|
+
assert.match(messages[0].content.text, /Auditoria do Grupo/i);
|
|
715
|
+
assert.match(messages[0].content.text, /Notícias enviadas: \*2\*/i);
|
|
716
|
+
assert.match(messages[0].content.text, /Antilink: \*ativado\*/i);
|
|
717
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
|
+
|
|
3
|
+
const MAX_REASON_CHARS = 500;
|
|
4
|
+
const DEFAULT_LIST_LIMIT = 20;
|
|
5
|
+
|
|
6
|
+
const normalizeGroupId = (value) => {
|
|
7
|
+
const normalized = String(value || '')
|
|
8
|
+
.trim()
|
|
9
|
+
.slice(0, 255);
|
|
10
|
+
return normalized || null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const normalizeParticipantJid = (value) => {
|
|
14
|
+
const normalized = String(value || '')
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.slice(0, 255);
|
|
18
|
+
return normalized || null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const normalizeReason = (value) => {
|
|
22
|
+
const normalized = String(value || '')
|
|
23
|
+
.replace(/\s+/g, ' ')
|
|
24
|
+
.trim()
|
|
25
|
+
.slice(0, MAX_REASON_CHARS);
|
|
26
|
+
return normalized || null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const normalizeWarnByJid = (value) => {
|
|
30
|
+
const normalized = String(value || '')
|
|
31
|
+
.trim()
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.slice(0, 255);
|
|
34
|
+
return normalized || null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const toPositiveInt = (value, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) => {
|
|
38
|
+
const numeric = Number.parseInt(String(value ?? ''), 10);
|
|
39
|
+
if (!Number.isFinite(numeric) || numeric < min) return fallback;
|
|
40
|
+
return Math.max(min, Math.min(max, numeric));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const addGroupWarning = async ({ groupId, participantJid, warnedByJid, reason = null } = {}) => {
|
|
44
|
+
const safeGroupId = normalizeGroupId(groupId);
|
|
45
|
+
const safeParticipantJid = normalizeParticipantJid(participantJid);
|
|
46
|
+
const safeWarnedByJid = normalizeWarnByJid(warnedByJid);
|
|
47
|
+
|
|
48
|
+
if (!safeGroupId || !safeParticipantJid) {
|
|
49
|
+
throw new Error('group_warning_invalid_target');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await executeQuery(
|
|
53
|
+
`INSERT INTO ${TABLES.GROUP_USER_WARNINGS}
|
|
54
|
+
(group_id, participant_jid, warned_by_jid, reason)
|
|
55
|
+
VALUES (?, ?, ?, ?)`,
|
|
56
|
+
[safeGroupId, safeParticipantJid, safeWarnedByJid, normalizeReason(reason)],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const countGroupWarnings = async ({ groupId, participantJid } = {}) => {
|
|
63
|
+
const safeGroupId = normalizeGroupId(groupId);
|
|
64
|
+
const safeParticipantJid = normalizeParticipantJid(participantJid);
|
|
65
|
+
|
|
66
|
+
if (!safeGroupId || !safeParticipantJid) return 0;
|
|
67
|
+
|
|
68
|
+
const rows = await executeQuery(
|
|
69
|
+
`SELECT COUNT(*) AS total
|
|
70
|
+
FROM ${TABLES.GROUP_USER_WARNINGS}
|
|
71
|
+
WHERE group_id = ? AND participant_jid = ?`,
|
|
72
|
+
[safeGroupId, safeParticipantJid],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const total = Number(rows?.[0]?.total || 0);
|
|
76
|
+
return Number.isFinite(total) ? Math.max(0, Math.floor(total)) : 0;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const listGroupWarnings = async ({ groupId, participantJid, limit = DEFAULT_LIST_LIMIT } = {}) => {
|
|
80
|
+
const safeGroupId = normalizeGroupId(groupId);
|
|
81
|
+
const safeParticipantJid = normalizeParticipantJid(participantJid);
|
|
82
|
+
const safeLimit = toPositiveInt(limit, DEFAULT_LIST_LIMIT, 1, 100);
|
|
83
|
+
|
|
84
|
+
if (!safeGroupId || !safeParticipantJid) return [];
|
|
85
|
+
|
|
86
|
+
const rows = await executeQuery(
|
|
87
|
+
`SELECT id, group_id, participant_jid, warned_by_jid, reason, created_at
|
|
88
|
+
FROM ${TABLES.GROUP_USER_WARNINGS}
|
|
89
|
+
WHERE group_id = ? AND participant_jid = ?
|
|
90
|
+
ORDER BY id DESC
|
|
91
|
+
LIMIT ?`,
|
|
92
|
+
[safeGroupId, safeParticipantJid, safeLimit],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
96
|
+
id: Number(row?.id || 0),
|
|
97
|
+
groupId: normalizeGroupId(row?.group_id),
|
|
98
|
+
participantJid: normalizeParticipantJid(row?.participant_jid),
|
|
99
|
+
warnedByJid: normalizeWarnByJid(row?.warned_by_jid),
|
|
100
|
+
reason: normalizeReason(row?.reason),
|
|
101
|
+
createdAt: row?.created_at || null,
|
|
102
|
+
}));
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const clearGroupWarnings = async ({ groupId, participantJid, clearAll = false, limit = 1 } = {}) => {
|
|
106
|
+
const safeGroupId = normalizeGroupId(groupId);
|
|
107
|
+
const safeParticipantJid = normalizeParticipantJid(participantJid);
|
|
108
|
+
const safeLimit = toPositiveInt(limit, 1, 1, 500);
|
|
109
|
+
|
|
110
|
+
if (!safeGroupId || !safeParticipantJid) {
|
|
111
|
+
return {
|
|
112
|
+
removedCount: 0,
|
|
113
|
+
remainingCount: 0,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const beforeCount = await countGroupWarnings({
|
|
118
|
+
groupId: safeGroupId,
|
|
119
|
+
participantJid: safeParticipantJid,
|
|
120
|
+
});
|
|
121
|
+
if (beforeCount <= 0) {
|
|
122
|
+
return {
|
|
123
|
+
removedCount: 0,
|
|
124
|
+
remainingCount: 0,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (clearAll) {
|
|
129
|
+
await executeQuery(
|
|
130
|
+
`DELETE FROM ${TABLES.GROUP_USER_WARNINGS}
|
|
131
|
+
WHERE group_id = ? AND participant_jid = ?`,
|
|
132
|
+
[safeGroupId, safeParticipantJid],
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
await executeQuery(
|
|
136
|
+
`DELETE FROM ${TABLES.GROUP_USER_WARNINGS}
|
|
137
|
+
WHERE group_id = ? AND participant_jid = ?
|
|
138
|
+
ORDER BY id DESC
|
|
139
|
+
LIMIT ?`,
|
|
140
|
+
[safeGroupId, safeParticipantJid, safeLimit],
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const remainingCount = await countGroupWarnings({
|
|
145
|
+
groupId: safeGroupId,
|
|
146
|
+
participantJid: safeParticipantJid,
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
removedCount: Math.max(0, beforeCount - remainingCount),
|
|
150
|
+
remainingCount,
|
|
151
|
+
};
|
|
152
|
+
};
|