@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,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
3
|
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import prettier from 'prettier';
|
|
6
8
|
|
|
7
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
10
|
const __dirname = path.dirname(__filename);
|
|
@@ -58,6 +60,143 @@ const resolveCategoryMeta = (key) => {
|
|
|
58
60
|
return { label, icon: '🧩' };
|
|
59
61
|
};
|
|
60
62
|
|
|
63
|
+
const pickFirstText = (...values) => {
|
|
64
|
+
for (const value of values) {
|
|
65
|
+
const text = String(value || '').trim();
|
|
66
|
+
if (text) return text;
|
|
67
|
+
}
|
|
68
|
+
return '';
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const ensureSentence = (value) => {
|
|
72
|
+
const text = String(value || '')
|
|
73
|
+
.trim()
|
|
74
|
+
.replace(/\s+/g, ' ');
|
|
75
|
+
if (!text) return '';
|
|
76
|
+
if (/[.!?]$/.test(text)) return text;
|
|
77
|
+
return `${text}.`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const parseOptionalBoolean = (value) => {
|
|
81
|
+
if (value === true) return true;
|
|
82
|
+
if (value === false) return false;
|
|
83
|
+
const normalized = String(value || '')
|
|
84
|
+
.trim()
|
|
85
|
+
.toLowerCase();
|
|
86
|
+
if (!normalized) return null;
|
|
87
|
+
if (['1', 'true', 'yes', 'sim'].includes(normalized)) return true;
|
|
88
|
+
if (['0', 'false', 'no', 'nao', 'não'].includes(normalized)) return false;
|
|
89
|
+
return null;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const normalizeUserList = (value) => unique(ensureArray(value).map((item) => String(item || '').trim()));
|
|
93
|
+
|
|
94
|
+
const normalizeExampleCommand = (value, fallback = '') => {
|
|
95
|
+
const raw = String(value || '').trim();
|
|
96
|
+
if (!raw) return String(fallback || '').trim();
|
|
97
|
+
return raw.replaceAll('<prefix>', '/');
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const normalizeUserExample = (value, { fallbackSituation = '', fallbackCommand = '', fallbackExpected = '', fallbackVariation = '' } = {}) => {
|
|
101
|
+
if (!value) return null;
|
|
102
|
+
|
|
103
|
+
if (typeof value === 'string') {
|
|
104
|
+
const commandText = normalizeExampleCommand(value, fallbackCommand);
|
|
105
|
+
if (!commandText) return null;
|
|
106
|
+
return {
|
|
107
|
+
situacao: ensureSentence(fallbackSituation || 'Exemplo real de uso do comando'),
|
|
108
|
+
comando: commandText,
|
|
109
|
+
resposta_esperada: ensureSentence(fallbackExpected || 'O bot confirma a execução'),
|
|
110
|
+
variacao: ensureSentence(fallbackVariation || ''),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
115
|
+
const situacao = pickFirstText(value.situacao, value.cenario, value.contexto, fallbackSituation);
|
|
116
|
+
const comando = normalizeExampleCommand(pickFirstText(value.comando, value.command, value.uso), fallbackCommand);
|
|
117
|
+
const respostaEsperada = pickFirstText(value.resposta_esperada, value.expected_response, value.resposta, fallbackExpected);
|
|
118
|
+
const variacao = pickFirstText(value.variacao, value.outcome_variation, fallbackVariation);
|
|
119
|
+
|
|
120
|
+
if (!comando) return null;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
situacao: ensureSentence(situacao || 'Exemplo real de uso do comando'),
|
|
124
|
+
comando,
|
|
125
|
+
resposta_esperada: ensureSentence(respostaEsperada || 'O bot confirma a execução'),
|
|
126
|
+
variacao: ensureSentence(variacao || ''),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const buildUserExperienceContract = ({ commandName = '', description = '', docsSummary = '', usageMethods = [], responses = {}, requirements = {}, premium = false, rawUserExperience = {} } = {}) => {
|
|
134
|
+
const explicitSummary = pickFirstText(rawUserExperience.resumo_usuario, rawUserExperience.summary, rawUserExperience.resumo_ia);
|
|
135
|
+
const explicitSummaryOrigin = pickFirstText(rawUserExperience.resumo_usuario_origem, rawUserExperience.summary_origin);
|
|
136
|
+
const normalizedSummaryOrigin = explicitSummaryOrigin === 'manual' || explicitSummaryOrigin === 'auto_ia_assistida' ? explicitSummaryOrigin : '';
|
|
137
|
+
const summaryBase = pickFirstText(explicitSummary, docsSummary, description, `Use /${commandName} para executar esta ação no bot`);
|
|
138
|
+
const resumoUsuario = ensureSentence(summaryBase);
|
|
139
|
+
const summaryOrigin = normalizedSummaryOrigin || (explicitSummary ? 'manual' : 'auto_ia_assistida');
|
|
140
|
+
const explicitReviewPending = parseOptionalBoolean(rawUserExperience.resumo_usuario_revisao_pendente);
|
|
141
|
+
|
|
142
|
+
const explicitWhenToUse = normalizeUserList(rawUserExperience.quando_usar || rawUserExperience.when_to_use);
|
|
143
|
+
const descriptionNoPunctuation = String(description || '')
|
|
144
|
+
.trim()
|
|
145
|
+
.replace(/[.!?]+$/, '');
|
|
146
|
+
const descriptionSentence = ensureSentence(descriptionNoPunctuation);
|
|
147
|
+
const derivedWhenToUse = unique([descriptionSentence ? `Use quando você precisa desta ação: ${descriptionSentence}` : '', requirements.group ? 'Funciona dentro de grupos.' : 'Pode ser usado no privado e em grupo.', requirements.admin ? 'Você precisa ser admin para executar.' : '', requirements.owner ? 'Esse comando é restrito ao admin principal do sistema.' : '', requirements.google_login ? 'É necessário estar logado no sistema para usar.' : '', premium ? 'Disponível para usuários Premium.' : ''].filter(Boolean));
|
|
148
|
+
const quandoUsar = explicitWhenToUse.length ? explicitWhenToUse : derivedWhenToUse.slice(0, 5);
|
|
149
|
+
|
|
150
|
+
const successResponse = ensureSentence(pickFirstText(rawUserExperience.resposta_sucesso, responses.success, responses.sucesso, 'O bot confirma que executou o comando'));
|
|
151
|
+
const usageErrorResponse = ensureSentence(pickFirstText(rawUserExperience.resposta_uso_incorreto, responses.usage_error, responses.erro_uso, 'Se o formato estiver incorreto, o bot mostra como usar corretamente'));
|
|
152
|
+
const permissionResponse = ensureSentence(pickFirstText(rawUserExperience.resposta_sem_permissao, responses.permission_error, responses.erro_permissao, premium ? 'Sem plano Premium ativo, o bot informa a restrição de acesso' : 'Sem permissão suficiente, o bot informa o motivo'));
|
|
153
|
+
|
|
154
|
+
const explicitExpectedResponses = normalizeUserList(rawUserExperience.resposta_esperada || rawUserExperience.expected_response);
|
|
155
|
+
const respostaEsperada = explicitExpectedResponses.length ? explicitExpectedResponses : unique([`Sucesso: ${successResponse}`, `Uso incorreto: ${usageErrorResponse}`, `Permissão: ${permissionResponse}`]);
|
|
156
|
+
|
|
157
|
+
const explicitExamples = ensureArray(rawUserExperience.exemplos_reais || rawUserExperience.real_examples);
|
|
158
|
+
const defaultSituation = descriptionSentence ? `Cenário comum: ${descriptionSentence}` : `Cenário comum: você quer usar /${commandName} no dia a dia.`;
|
|
159
|
+
const fallbackUsageMethods = usageMethods.length ? usageMethods : [`/${commandName}`];
|
|
160
|
+
let exemplosReais = explicitExamples
|
|
161
|
+
.map((example) =>
|
|
162
|
+
normalizeUserExample(example, {
|
|
163
|
+
fallbackSituation: defaultSituation,
|
|
164
|
+
fallbackCommand: fallbackUsageMethods[0] || `/${commandName}`,
|
|
165
|
+
fallbackExpected: successResponse,
|
|
166
|
+
fallbackVariation: usageErrorResponse,
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
.filter(Boolean);
|
|
170
|
+
|
|
171
|
+
if (!exemplosReais.length) {
|
|
172
|
+
exemplosReais = fallbackUsageMethods.slice(0, 3).map((usageMethod, index) => ({
|
|
173
|
+
situacao: ensureSentence(index === 0 ? defaultSituation : `Variação ${index + 1} de uso do comando no mesmo contexto`),
|
|
174
|
+
comando: normalizeExampleCommand(usageMethod, `/${commandName}`),
|
|
175
|
+
resposta_esperada: successResponse,
|
|
176
|
+
variacao: usageErrorResponse,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const explicitCommonErrors = normalizeUserList(rawUserExperience.erros_comuns_usuario || rawUserExperience.common_user_errors);
|
|
181
|
+
const derivedCommonErrors = unique(['Digitar o comando fora do formato esperado.', requirements.group ? 'Tentar executar fora de um grupo.' : '', requirements.admin ? 'Tentar executar sem ser admin.' : '', requirements.owner ? 'Tentar executar sem ser admin principal do sistema.' : '', requirements.google_login ? 'Tentar usar sem estar logado no sistema.' : '', premium ? 'Tentar usar sem acesso Premium ativo.' : ''].filter(Boolean));
|
|
182
|
+
const errosComunsUsuario = explicitCommonErrors.length ? explicitCommonErrors : derivedCommonErrors.slice(0, 5);
|
|
183
|
+
|
|
184
|
+
const explicitErrorSteps = normalizeUserList(rawUserExperience.passos_se_der_erro || rawUserExperience.error_steps);
|
|
185
|
+
const fallbackErrorSteps = ['Copie e teste um exemplo pronto desta página.', 'Confira se você está no local certo (grupo/privado) e com a permissão necessária.', premium ? 'Verifique se seu acesso Premium está ativo.' : '', 'Se ainda falhar, fale com o admin do sistema no privado.'].filter(Boolean);
|
|
186
|
+
const passosSeDerErro = explicitErrorSteps.length ? explicitErrorSteps : fallbackErrorSteps;
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
resumo_usuario: resumoUsuario,
|
|
190
|
+
quando_usar: quandoUsar,
|
|
191
|
+
exemplos_reais: exemplosReais,
|
|
192
|
+
resposta_esperada: respostaEsperada,
|
|
193
|
+
erros_comuns_usuario: errosComunsUsuario,
|
|
194
|
+
passos_se_der_erro: passosSeDerErro,
|
|
195
|
+
resumo_usuario_origem: summaryOrigin,
|
|
196
|
+
resumo_usuario_revisao_pendente: explicitReviewPending ?? summaryOrigin !== 'manual',
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
61
200
|
const deepMerge = (target, source) => {
|
|
62
201
|
if (!source) return target;
|
|
63
202
|
const output = { ...target };
|
|
@@ -160,6 +299,23 @@ const sanitizeCommand = ({ command: rawCommand, moduleDefaults, moduleDirName, m
|
|
|
160
299
|
const sideEffects = unique([...ensureArray(command?.efeitos_colaterais), ...ensureArray(command?.side_effects)]);
|
|
161
300
|
|
|
162
301
|
const responses = deepMerge(moduleDefaults?.responses || moduleDefaults?.respostas_padrao || {}, command?.responses || command?.respostas_padrao || {});
|
|
302
|
+
const userExperienceSeed = deepMerge(moduleDefaults?.user_experience || moduleDefaults?.experiencia_usuario || {}, command?.user_experience || command?.experiencia_usuario || {});
|
|
303
|
+
if (command?.resumo_usuario !== undefined) userExperienceSeed.resumo_usuario = command.resumo_usuario;
|
|
304
|
+
if (command?.quando_usar !== undefined) userExperienceSeed.quando_usar = command.quando_usar;
|
|
305
|
+
if (command?.exemplos_reais !== undefined) userExperienceSeed.exemplos_reais = command.exemplos_reais;
|
|
306
|
+
if (command?.resposta_esperada !== undefined) userExperienceSeed.resposta_esperada = command.resposta_esperada;
|
|
307
|
+
if (command?.erros_comuns_usuario !== undefined) userExperienceSeed.erros_comuns_usuario = command.erros_comuns_usuario;
|
|
308
|
+
if (command?.passos_se_der_erro !== undefined) userExperienceSeed.passos_se_der_erro = command.passos_se_der_erro;
|
|
309
|
+
const userExperience = buildUserExperienceContract({
|
|
310
|
+
commandName,
|
|
311
|
+
description: String(command?.description || command?.descricao || '').trim(),
|
|
312
|
+
docsSummary: String(command?.docs?.summary || '').trim(),
|
|
313
|
+
usageMethods,
|
|
314
|
+
responses,
|
|
315
|
+
requirements,
|
|
316
|
+
premium,
|
|
317
|
+
rawUserExperience: userExperienceSeed,
|
|
318
|
+
});
|
|
163
319
|
|
|
164
320
|
const observability = deepMerge(moduleDefaults?.observability || {}, command?.observability || {});
|
|
165
321
|
const privacy = deepMerge(moduleDefaults?.privacy || {}, command?.privacy || {});
|
|
@@ -183,6 +339,7 @@ const sanitizeCommand = ({ command: rawCommand, moduleDefaults, moduleDirName, m
|
|
|
183
339
|
subcomandos: unique(ensureArray(command?.subcomandos).map((item) => String(item).trim())),
|
|
184
340
|
metodos_de_uso: usageMethods,
|
|
185
341
|
mensagens_uso: normalizedUsageVariants,
|
|
342
|
+
...userExperience,
|
|
186
343
|
arguments: args,
|
|
187
344
|
responses,
|
|
188
345
|
technical: {
|
|
@@ -283,7 +440,7 @@ const buildCatalog = async () => {
|
|
|
283
440
|
|
|
284
441
|
return {
|
|
285
442
|
schema_version: '3.0.0',
|
|
286
|
-
generated_at:
|
|
443
|
+
generated_at: __timeNowIso(),
|
|
287
444
|
totals: {
|
|
288
445
|
modules: sortedModules.length,
|
|
289
446
|
categories: categories.length,
|
|
@@ -297,7 +454,14 @@ const buildCatalog = async () => {
|
|
|
297
454
|
const writeCatalog = async () => {
|
|
298
455
|
const payload = await buildCatalog();
|
|
299
456
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
300
|
-
|
|
457
|
+
const serialized = `${JSON.stringify(payload, null, 2)}\n`;
|
|
458
|
+
const prettierConfig = (await prettier.resolveConfig(outputPath)) || {};
|
|
459
|
+
const formatted = await prettier.format(serialized, {
|
|
460
|
+
...prettierConfig,
|
|
461
|
+
parser: 'json',
|
|
462
|
+
filepath: outputPath,
|
|
463
|
+
});
|
|
464
|
+
await fs.writeFile(outputPath, formatted, 'utf8');
|
|
301
465
|
|
|
302
466
|
console.log(`Catalogo de comandos atualizado: ${path.relative(repoRoot, outputPath)} (${payload.totals.commands} comandos)`);
|
|
303
467
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
3
|
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -8,7 +9,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
9
10
|
const repoRoot = path.resolve(__dirname, '..');
|
|
10
11
|
const modulesRoot = path.join(repoRoot, 'app', 'modules');
|
|
11
|
-
const nowIso =
|
|
12
|
+
const nowIso = __timeNowIso();
|
|
12
13
|
|
|
13
14
|
const normalizeBoolLabel = (value) => (value ? 'sim' : 'nao');
|
|
14
15
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
3
|
import fs from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
|
|
@@ -173,7 +174,7 @@ const renderPageHtml = (page, generatedAt, { slugSet, prefix }) => {
|
|
|
173
174
|
url: canonicalUrl,
|
|
174
175
|
isPartOf: {
|
|
175
176
|
'@type': 'WebSite',
|
|
176
|
-
name: '
|
|
177
|
+
name: 'Omnizap',
|
|
177
178
|
url: SITE_ORIGIN,
|
|
178
179
|
},
|
|
179
180
|
};
|
|
@@ -205,7 +206,7 @@ const renderPageHtml = (page, generatedAt, { slugSet, prefix }) => {
|
|
|
205
206
|
|
|
206
207
|
<meta property="og:type" content="article" />
|
|
207
208
|
<meta property="og:locale" content="pt_BR" />
|
|
208
|
-
<meta property="og:site_name" content="
|
|
209
|
+
<meta property="og:site_name" content="Omnizap" />
|
|
209
210
|
<meta property="og:title" content="${escapeHtml(page.title)}" />
|
|
210
211
|
<meta property="og:description" content="${escapeHtml(page.description)}" />
|
|
211
212
|
<meta property="og:url" content="${escapeHtml(canonicalUrl)}" />
|
|
@@ -349,7 +350,7 @@ const renderPageHtml = (page, generatedAt, { slugSet, prefix }) => {
|
|
|
349
350
|
</main>
|
|
350
351
|
|
|
351
352
|
<footer class="footer-meta">
|
|
352
|
-
© 2026 OMNIZAP
|
|
353
|
+
© 2026 OMNIZAP · SEO SATELLITE V2 · ATUALIZADO EM ${escapeHtml(generatedAt)}
|
|
353
354
|
</footer>
|
|
354
355
|
</div>
|
|
355
356
|
</body>
|
|
@@ -364,7 +365,7 @@ const run = async () => {
|
|
|
364
365
|
const rawConfig = await fs.readFile(absoluteConfigPath, 'utf8');
|
|
365
366
|
const parsedConfig = JSON.parse(rawConfig);
|
|
366
367
|
const pages = Array.isArray(parsedConfig?.pages) ? parsedConfig.pages : [];
|
|
367
|
-
const generatedAt = String(parsedConfig?.generated_at ||
|
|
368
|
+
const generatedAt = String(parsedConfig?.generated_at || __timeNowIso().slice(0, 10)).trim();
|
|
368
369
|
|
|
369
370
|
if (!pages.length) {
|
|
370
371
|
throw new Error('config sem paginas');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
3
|
|
|
3
4
|
import { execSync } from 'node:child_process';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -128,7 +129,7 @@ const run = async () => {
|
|
|
128
129
|
const baseUrl = `https://api.github.com/repos/${encodeURIComponent(repoOwner)}/${encodeURIComponent(repoName)}`;
|
|
129
130
|
|
|
130
131
|
if (action === 'start') {
|
|
131
|
-
const startDescription = description || `Deploy OmniZap ${buildId ||
|
|
132
|
+
const startDescription = description || `Deploy OmniZap ${buildId || __timeNowIso()}`;
|
|
132
133
|
const deployment = await request(`${baseUrl}/deployments`, 'POST', {
|
|
133
134
|
ref: currentRef(),
|
|
134
135
|
task: 'deploy',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import process from 'node:process';
|
|
7
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -99,13 +98,16 @@ const target = getArg('--target');
|
|
|
99
98
|
const name = getArg('--name', tag);
|
|
100
99
|
const bodyArg = getArg('--body', '');
|
|
101
100
|
const bodyFile = getArg('--body-file', '');
|
|
101
|
+
const bodyStdin = toBool(getArg('--body-stdin', 'false'), false);
|
|
102
102
|
const generateNotes = toBool(getArg('--generate-notes', 'true'), true);
|
|
103
103
|
const prerelease = toBool(getArg('--prerelease', 'false'), false);
|
|
104
104
|
const draft = toBool(getArg('--draft', 'false'), false);
|
|
105
105
|
const latestArg = getArg('--latest', '');
|
|
106
106
|
|
|
107
107
|
const parseMakeLatest = (value) => {
|
|
108
|
-
const normalized = String(value || '')
|
|
108
|
+
const normalized = String(value || '')
|
|
109
|
+
.trim()
|
|
110
|
+
.toLowerCase();
|
|
109
111
|
if (!normalized) return '';
|
|
110
112
|
if (['1', 'true', 'yes', 'on'].includes(normalized)) return 'true';
|
|
111
113
|
if (['0', 'false', 'no', 'off'].includes(normalized)) return 'false';
|
|
@@ -114,17 +116,28 @@ const parseMakeLatest = (value) => {
|
|
|
114
116
|
};
|
|
115
117
|
const makeLatest = parseMakeLatest(latestArg);
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
119
|
+
const readBodyFromStdin = async () => {
|
|
120
|
+
if (process.stdin.isTTY) return '';
|
|
121
|
+
const chunks = [];
|
|
122
|
+
for await (const chunk of process.stdin) {
|
|
123
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
124
|
+
}
|
|
125
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const resolveReleaseBody = async () => {
|
|
129
|
+
if (bodyArg) return sanitizeReleaseBody(bodyArg);
|
|
130
|
+
if (bodyStdin) {
|
|
131
|
+
const stdinBody = await readBodyFromStdin();
|
|
132
|
+
return sanitizeReleaseBody(stdinBody);
|
|
133
|
+
}
|
|
134
|
+
if (bodyFile) {
|
|
120
135
|
const resolvedBodyFile = resolveBodyFilePath(bodyFile);
|
|
121
|
-
body
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error(`Falha ao ler --body-file (${bodyFile}): ${error?.message || error}`);
|
|
136
|
+
console.error(`Parâmetro --body-file não é suportado neste script. Envie o conteúdo via stdin com --body-stdin true (arquivo: ${resolvedBodyFile}).`);
|
|
124
137
|
process.exit(1);
|
|
125
138
|
}
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
return '';
|
|
140
|
+
};
|
|
128
141
|
|
|
129
142
|
if (!tag) {
|
|
130
143
|
console.error('Parâmetro obrigatório ausente: --tag');
|
|
@@ -140,6 +153,7 @@ const headers = {
|
|
|
140
153
|
};
|
|
141
154
|
|
|
142
155
|
const request = async (url, method, payload) => {
|
|
156
|
+
// lgtm[js/file-access-to-http]
|
|
143
157
|
const response = await fetch(url, {
|
|
144
158
|
method,
|
|
145
159
|
headers,
|
|
@@ -169,6 +183,7 @@ const failFromResponse = (response, fallbackPrefix = 'GitHub API') => {
|
|
|
169
183
|
};
|
|
170
184
|
|
|
171
185
|
const run = async () => {
|
|
186
|
+
const body = await resolveReleaseBody();
|
|
172
187
|
const baseUrl = `https://api.github.com/repos/${encodeURIComponent(repoOwner)}/${encodeURIComponent(repoName)}`;
|
|
173
188
|
|
|
174
189
|
const byTag = await request(`${baseUrl}/releases/tags/${encodeURIComponent(tag)}`, 'GET');
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
ENV_FILE="${ENV_FILE:-$PROJECT_ROOT/.env}"
|
|
6
|
+
|
|
7
|
+
SESSION_ID_PATTERN='^[a-zA-Z0-9:_-]+$'
|
|
8
|
+
SESSION_ID_MAX_LENGTH=64
|
|
9
|
+
|
|
10
|
+
SESSION_ID=""
|
|
11
|
+
SESSION_PREFIX="work"
|
|
12
|
+
SESSION_WEIGHT=1
|
|
13
|
+
SET_PRIMARY=0
|
|
14
|
+
CONNECT_NOW=1
|
|
15
|
+
RESET_AUTH=0
|
|
16
|
+
CLEAR_AUTH_FILES=0
|
|
17
|
+
ALLOW_REUSE=0
|
|
18
|
+
|
|
19
|
+
log() {
|
|
20
|
+
printf '[new-whatsapp-session] %s\n' "$*"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fail() {
|
|
24
|
+
printf '[new-whatsapp-session] erro: %s\n' "$*" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
require_cmd() {
|
|
29
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
30
|
+
fail "comando ausente: $1"
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
usage() {
|
|
35
|
+
cat <<'EOF'
|
|
36
|
+
Uso:
|
|
37
|
+
bash scripts/new-whatsapp-session.sh [opcoes]
|
|
38
|
+
|
|
39
|
+
Opcoes:
|
|
40
|
+
--session <id> Define o session_id manualmente.
|
|
41
|
+
--prefix <valor> Prefixo para gerar session_id automatico (padrao: work).
|
|
42
|
+
--weight <1-1000> Peso da sessao em BAILEYS_SESSION_WEIGHTS (padrao: 1).
|
|
43
|
+
--primary Define a nova sessao como BAILEYS_PRIMARY_SESSION_ID.
|
|
44
|
+
--reuse Permite usar um session_id ja existente.
|
|
45
|
+
--reset-auth Limpa credenciais atuais da sessao no MySQL antes do QR.
|
|
46
|
+
--clear-auth-files Junto com --reset-auth, remove app/connection/auth/*.json.
|
|
47
|
+
--no-connect Apenas atualiza .env (nao abre conexao para QR agora).
|
|
48
|
+
--help Mostra esta ajuda.
|
|
49
|
+
|
|
50
|
+
Exemplos:
|
|
51
|
+
npm run new:work
|
|
52
|
+
npm run new:work -- --session suporte_2 --primary
|
|
53
|
+
bash scripts/new-whatsapp-session.sh --prefix operador --no-connect
|
|
54
|
+
EOF
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
strip_wrapping_quotes() {
|
|
58
|
+
local value="${1:-}"
|
|
59
|
+
if [[ "$value" == \"*\" && "$value" == *\" ]]; then
|
|
60
|
+
value="${value:1:${#value}-2}"
|
|
61
|
+
elif [[ "$value" == \'*\' && "$value" == *\' ]]; then
|
|
62
|
+
value="${value:1:${#value}-2}"
|
|
63
|
+
fi
|
|
64
|
+
printf '%s' "$value"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
read_env_value() {
|
|
68
|
+
local key="$1"
|
|
69
|
+
local raw=""
|
|
70
|
+
|
|
71
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
72
|
+
raw="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d'=' -f2- || true)"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
raw="${raw//$'\r'/}"
|
|
76
|
+
strip_wrapping_quotes "$raw"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
split_entries() {
|
|
80
|
+
printf '%s' "${1:-}" | tr ',;\n' '\n' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' | awk 'NF'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
validate_session_id() {
|
|
84
|
+
local value="$1"
|
|
85
|
+
[[ -n "$value" ]] || return 1
|
|
86
|
+
[[ "${#value}" -le "$SESSION_ID_MAX_LENGTH" ]] || return 1
|
|
87
|
+
[[ "$value" =~ $SESSION_ID_PATTERN ]] || return 1
|
|
88
|
+
return 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
upsert_env_value() {
|
|
92
|
+
local key="$1"
|
|
93
|
+
local value="$2"
|
|
94
|
+
local temp_file
|
|
95
|
+
temp_file="$(mktemp)"
|
|
96
|
+
|
|
97
|
+
awk -v key="$key" -v value="$value" '
|
|
98
|
+
BEGIN {
|
|
99
|
+
replaced = 0;
|
|
100
|
+
prefix = key "=";
|
|
101
|
+
}
|
|
102
|
+
index($0, prefix) == 1 {
|
|
103
|
+
if (replaced == 0) {
|
|
104
|
+
print prefix value;
|
|
105
|
+
replaced = 1;
|
|
106
|
+
}
|
|
107
|
+
next;
|
|
108
|
+
}
|
|
109
|
+
{ print; }
|
|
110
|
+
END {
|
|
111
|
+
if (replaced == 0) {
|
|
112
|
+
print prefix value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
' "$ENV_FILE" >"$temp_file"
|
|
116
|
+
|
|
117
|
+
mv "$temp_file" "$ENV_FILE"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
is_valid_weight() {
|
|
121
|
+
local value="$1"
|
|
122
|
+
[[ "$value" =~ ^[0-9]+$ ]] || return 1
|
|
123
|
+
((value >= 1 && value <= 1000)) || return 1
|
|
124
|
+
return 0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
while [[ $# -gt 0 ]]; do
|
|
128
|
+
case "$1" in
|
|
129
|
+
--session)
|
|
130
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --session"
|
|
131
|
+
SESSION_ID="$2"
|
|
132
|
+
shift 2
|
|
133
|
+
;;
|
|
134
|
+
--prefix)
|
|
135
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --prefix"
|
|
136
|
+
SESSION_PREFIX="$2"
|
|
137
|
+
shift 2
|
|
138
|
+
;;
|
|
139
|
+
--weight)
|
|
140
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --weight"
|
|
141
|
+
SESSION_WEIGHT="$2"
|
|
142
|
+
shift 2
|
|
143
|
+
;;
|
|
144
|
+
--primary)
|
|
145
|
+
SET_PRIMARY=1
|
|
146
|
+
shift
|
|
147
|
+
;;
|
|
148
|
+
--reuse)
|
|
149
|
+
ALLOW_REUSE=1
|
|
150
|
+
shift
|
|
151
|
+
;;
|
|
152
|
+
--reset-auth)
|
|
153
|
+
RESET_AUTH=1
|
|
154
|
+
shift
|
|
155
|
+
;;
|
|
156
|
+
--clear-auth-files)
|
|
157
|
+
CLEAR_AUTH_FILES=1
|
|
158
|
+
shift
|
|
159
|
+
;;
|
|
160
|
+
--no-connect)
|
|
161
|
+
CONNECT_NOW=0
|
|
162
|
+
shift
|
|
163
|
+
;;
|
|
164
|
+
-h|--help)
|
|
165
|
+
usage
|
|
166
|
+
exit 0
|
|
167
|
+
;;
|
|
168
|
+
*)
|
|
169
|
+
fail "opcao invalida: $1 (use --help)"
|
|
170
|
+
;;
|
|
171
|
+
esac
|
|
172
|
+
done
|
|
173
|
+
|
|
174
|
+
[[ -f "$ENV_FILE" ]] || fail "arquivo .env nao encontrado em: $ENV_FILE"
|
|
175
|
+
is_valid_weight "$SESSION_WEIGHT" || fail "peso invalido em --weight: $SESSION_WEIGHT (use 1..1000)"
|
|
176
|
+
|
|
177
|
+
if [[ -n "$SESSION_ID" ]]; then
|
|
178
|
+
validate_session_id "$SESSION_ID" || fail "session_id invalido: \"$SESSION_ID\""
|
|
179
|
+
else
|
|
180
|
+
validate_session_id "$SESSION_PREFIX" || fail "prefixo invalido para session_id: \"$SESSION_PREFIX\""
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
declare -A SEEN_SESSION_IDS=()
|
|
184
|
+
declare -a SESSION_IDS=()
|
|
185
|
+
|
|
186
|
+
existing_session_ids_raw="$(read_env_value BAILEYS_SESSION_IDS)"
|
|
187
|
+
legacy_session_id_raw="$(read_env_value BAILEYS_AUTH_SESSION_ID)"
|
|
188
|
+
if [[ -z "$existing_session_ids_raw" ]]; then
|
|
189
|
+
existing_session_ids_raw="$legacy_session_id_raw"
|
|
190
|
+
fi
|
|
191
|
+
if [[ -z "$existing_session_ids_raw" ]]; then
|
|
192
|
+
existing_session_ids_raw="default"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
while IFS= read -r candidate; do
|
|
196
|
+
[[ -n "$candidate" ]] || continue
|
|
197
|
+
if ! validate_session_id "$candidate"; then
|
|
198
|
+
log "ignorado session_id invalido no .env: $candidate"
|
|
199
|
+
continue
|
|
200
|
+
fi
|
|
201
|
+
if [[ -z "${SEEN_SESSION_IDS[$candidate]+x}" ]]; then
|
|
202
|
+
SEEN_SESSION_IDS["$candidate"]=1
|
|
203
|
+
SESSION_IDS+=("$candidate")
|
|
204
|
+
fi
|
|
205
|
+
done < <(split_entries "$existing_session_ids_raw")
|
|
206
|
+
|
|
207
|
+
if [[ "${#SESSION_IDS[@]}" -eq 0 ]]; then
|
|
208
|
+
SESSION_IDS=("default")
|
|
209
|
+
SEEN_SESSION_IDS["default"]=1
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
if [[ -z "$SESSION_ID" ]]; then
|
|
213
|
+
base_id="${SESSION_PREFIX}-$(date -u +%Y%m%d%H%M%S)"
|
|
214
|
+
generated_id="$base_id"
|
|
215
|
+
suffix=1
|
|
216
|
+
while [[ -n "${SEEN_SESSION_IDS[$generated_id]+x}" ]]; do
|
|
217
|
+
suffix=$((suffix + 1))
|
|
218
|
+
generated_id="${base_id}-${suffix}"
|
|
219
|
+
done
|
|
220
|
+
SESSION_ID="$generated_id"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
if [[ -n "${SEEN_SESSION_IDS[$SESSION_ID]+x}" && "$ALLOW_REUSE" != "1" ]]; then
|
|
224
|
+
fail "session_id \"$SESSION_ID\" ja existe. Use --reuse para permitir reutilizacao."
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
if [[ -z "${SEEN_SESSION_IDS[$SESSION_ID]+x}" ]]; then
|
|
228
|
+
SESSION_IDS+=("$SESSION_ID")
|
|
229
|
+
SEEN_SESSION_IDS["$SESSION_ID"]=1
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
current_primary_session_id="$(read_env_value BAILEYS_PRIMARY_SESSION_ID)"
|
|
233
|
+
if ! validate_session_id "$current_primary_session_id"; then
|
|
234
|
+
current_primary_session_id="${SESSION_IDS[0]}"
|
|
235
|
+
fi
|
|
236
|
+
if [[ -z "${SEEN_SESSION_IDS[$current_primary_session_id]+x}" ]]; then
|
|
237
|
+
current_primary_session_id="${SESSION_IDS[0]}"
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
if [[ "$SET_PRIMARY" == "1" ]]; then
|
|
241
|
+
current_primary_session_id="$SESSION_ID"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
declare -A SESSION_WEIGHTS=()
|
|
245
|
+
weights_raw="$(read_env_value BAILEYS_SESSION_WEIGHTS)"
|
|
246
|
+
while IFS= read -r raw_entry; do
|
|
247
|
+
[[ -n "$raw_entry" ]] || continue
|
|
248
|
+
separator='='
|
|
249
|
+
if [[ "$raw_entry" == *":"* && "$raw_entry" != *"="* ]]; then
|
|
250
|
+
separator=':'
|
|
251
|
+
fi
|
|
252
|
+
if [[ "$raw_entry" != *"$separator"* ]]; then
|
|
253
|
+
continue
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
raw_session="${raw_entry%%"$separator"*}"
|
|
257
|
+
raw_weight="${raw_entry#*"$separator"}"
|
|
258
|
+
if ! validate_session_id "$raw_session"; then
|
|
259
|
+
continue
|
|
260
|
+
fi
|
|
261
|
+
if is_valid_weight "$raw_weight"; then
|
|
262
|
+
SESSION_WEIGHTS["$raw_session"]="$raw_weight"
|
|
263
|
+
fi
|
|
264
|
+
done < <(split_entries "$weights_raw")
|
|
265
|
+
|
|
266
|
+
SESSION_WEIGHTS["$SESSION_ID"]="$SESSION_WEIGHT"
|
|
267
|
+
for existing_session in "${SESSION_IDS[@]}"; do
|
|
268
|
+
if [[ -z "${SESSION_WEIGHTS[$existing_session]+x}" ]]; then
|
|
269
|
+
SESSION_WEIGHTS["$existing_session"]=1
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
session_ids_value="$(IFS=,; printf '%s' "${SESSION_IDS[*]}")"
|
|
274
|
+
declare -a weight_entries=()
|
|
275
|
+
for listed_session in "${SESSION_IDS[@]}"; do
|
|
276
|
+
weight_entries+=("${listed_session}=${SESSION_WEIGHTS[$listed_session]}")
|
|
277
|
+
done
|
|
278
|
+
weights_value="$(IFS=,; printf '%s' "${weight_entries[*]}")"
|
|
279
|
+
|
|
280
|
+
upsert_env_value "BAILEYS_SESSION_IDS" "$session_ids_value"
|
|
281
|
+
upsert_env_value "BAILEYS_PRIMARY_SESSION_ID" "$current_primary_session_id"
|
|
282
|
+
upsert_env_value "BAILEYS_SESSION_WEIGHTS" "$weights_value"
|
|
283
|
+
|
|
284
|
+
legacy_auth_session_id="$(read_env_value BAILEYS_AUTH_SESSION_ID)"
|
|
285
|
+
if [[ -z "$legacy_auth_session_id" || "$SET_PRIMARY" == "1" ]]; then
|
|
286
|
+
upsert_env_value "BAILEYS_AUTH_SESSION_ID" "$current_primary_session_id"
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
if [[ "$RESET_AUTH" == "1" ]]; then
|
|
290
|
+
reset_args=(--session "$SESSION_ID")
|
|
291
|
+
if [[ "$CLEAR_AUTH_FILES" == "1" ]]; then
|
|
292
|
+
reset_args+=(--clear-auth-files)
|
|
293
|
+
fi
|
|
294
|
+
bash "$PROJECT_ROOT/scripts/clear-whatsapp-session.sh" "${reset_args[@]}"
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
log "Sessao preparada com sucesso."
|
|
298
|
+
log "session_id: $SESSION_ID"
|
|
299
|
+
log "BAILEYS_SESSION_IDS: $session_ids_value"
|
|
300
|
+
log "BAILEYS_PRIMARY_SESSION_ID: $current_primary_session_id"
|
|
301
|
+
|
|
302
|
+
if [[ "$CONNECT_NOW" == "1" ]]; then
|
|
303
|
+
require_cmd node
|
|
304
|
+
log "Inicializando schema do banco..."
|
|
305
|
+
node "$PROJECT_ROOT/database/init.js"
|
|
306
|
+
|
|
307
|
+
log "Abrindo conexao da sessao '$SESSION_ID' para leitura do QR Code..."
|
|
308
|
+
export BAILEYS_SESSION_IDS="$SESSION_ID"
|
|
309
|
+
export BAILEYS_PRIMARY_SESSION_ID="$SESSION_ID"
|
|
310
|
+
export BAILEYS_AUTH_SESSION_ID="$SESSION_ID"
|
|
311
|
+
|
|
312
|
+
log "Apos conectar no WhatsApp, use Ctrl+C para encerrar."
|
|
313
|
+
exec node "$PROJECT_ROOT/app/connection/socketController.js"
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
log "Concluido sem abrir conexao. Para abrir QR agora use:"
|
|
317
|
+
log " BAILEYS_SESSION_IDS=$SESSION_ID BAILEYS_PRIMARY_SESSION_ID=$SESSION_ID BAILEYS_AUTH_SESSION_ID=$SESSION_ID node app/connection/socketController.js"
|