@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.
Files changed (261) hide show
  1. package/.env.example +58 -13
  2. package/.github/workflows/ci.yml +5 -5
  3. package/.github/workflows/codeql.yml +1 -1
  4. package/.github/workflows/db-migration-check.yml +2 -2
  5. package/.github/workflows/dependency-review.yml +1 -1
  6. package/.github/workflows/deploy.yml +2 -2
  7. package/.github/workflows/release.yml +2 -2
  8. package/.github/workflows/security-attest-provenance.yml +2 -2
  9. package/.github/workflows/security-gitleaks.yml +13 -4
  10. package/.github/workflows/security-runner-hardening.yml +2 -2
  11. package/.github/workflows/security-scorecard.yml +1 -1
  12. package/.github/workflows/security-zap-baseline.yml +1 -1
  13. package/.github/workflows/security-zap-full-scan.yml +2 -1
  14. package/.github/workflows/security-zizmor.yml +1 -1
  15. package/.github/workflows/wiki-sync.yml +1 -1
  16. package/.gitleaksignore +9 -0
  17. package/CODE_OF_CONDUCT.md +2 -2
  18. package/GEMINI.md +64 -0
  19. package/README.md +52 -82
  20. package/SECURITY.md +1 -1
  21. package/app/config/index.js +2 -0
  22. package/app/configParts/adminIdentity.js +5 -5
  23. package/app/configParts/baileysConfig.js +230 -58
  24. package/app/configParts/groupUtils.js +5 -0
  25. package/app/configParts/messagePersistenceService.js +145 -4
  26. package/app/configParts/sessionConfig.js +157 -0
  27. package/app/connection/baileysCompatibility.test.js +1 -1
  28. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  29. package/app/connection/socketController.js +660 -158
  30. package/app/connection/socketController.multiSession.test.js +108 -0
  31. package/app/controllers/messageController.js +1 -1
  32. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  33. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  34. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  35. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  36. package/app/controllers/messageProcessingPipeline.js +93 -13
  37. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  38. package/app/modules/adminModule/AGENT.md +1 -1
  39. package/app/modules/adminModule/commandConfig.json +3318 -1347
  40. package/app/modules/adminModule/groupCommandHandlers.js +858 -15
  41. package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
  42. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  43. package/app/modules/aiModule/AGENT.md +47 -30
  44. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  45. package/app/modules/aiModule/catCommand.js +135 -27
  46. package/app/modules/aiModule/commandConfig.json +114 -28
  47. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  48. package/app/modules/gameModule/AGENT.md +1 -1
  49. package/app/modules/gameModule/commandConfig.json +29 -0
  50. package/app/modules/menuModule/AGENT.md +1 -1
  51. package/app/modules/menuModule/commandConfig.json +45 -10
  52. package/app/modules/menuModule/menuCatalogService.js +190 -0
  53. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  54. package/app/modules/menuModule/menuDynamicService.js +511 -0
  55. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  56. package/app/modules/menuModule/menus.js +36 -5
  57. package/app/modules/playModule/AGENT.md +10 -5
  58. package/app/modules/playModule/commandConfig.json +140 -12
  59. package/app/modules/playModule/playCommand.js +1 -1417
  60. package/app/modules/playModule/playCommandConstants.js +80 -0
  61. package/app/modules/playModule/playCommandCore.js +361 -0
  62. package/app/modules/playModule/playCommandHandlers.js +41 -0
  63. package/app/modules/playModule/playCommandMediaClient.js +1872 -0
  64. package/app/modules/playModule/playConfigRuntime.js +245 -4
  65. package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
  66. package/app/modules/quoteModule/AGENT.md +1 -1
  67. package/app/modules/quoteModule/commandConfig.json +29 -0
  68. package/app/modules/quoteModule/quoteCommand.js +3 -2
  69. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  70. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  71. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
  72. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
  73. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
  74. package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
  75. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
  76. package/app/modules/statsModule/AGENT.md +1 -1
  77. package/app/modules/statsModule/commandConfig.json +58 -0
  78. package/app/modules/statsModule/rankingCommon.js +5 -4
  79. package/app/modules/stickerModule/AGENT.md +1 -1
  80. package/app/modules/stickerModule/addStickerMetadata.js +4 -3
  81. package/app/modules/stickerModule/commandConfig.json +145 -0
  82. package/app/modules/stickerModule/stickerCommand.js +1 -1
  83. package/app/modules/stickerPackModule/AGENT.md +1 -1
  84. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  85. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  86. package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
  87. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
  88. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
  89. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
  90. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
  91. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
  92. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
  93. package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
  94. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
  95. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  96. package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
  97. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
  98. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  99. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  100. package/app/modules/systemMetricsModule/pingCommand.js +6 -5
  101. package/app/modules/tiktokModule/AGENT.md +1 -1
  102. package/app/modules/tiktokModule/commandConfig.json +29 -0
  103. package/app/modules/tiktokModule/tiktokCommand.js +2 -1
  104. package/app/modules/userModule/AGENT.md +1 -1
  105. package/app/modules/userModule/commandConfig.json +29 -0
  106. package/app/modules/userModule/userCommand.js +72 -23
  107. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  108. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  109. package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
  110. package/app/observability/metrics.js +136 -0
  111. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  112. package/app/services/ai/conversationRouterService.js +4 -3
  113. package/app/services/ai/geminiService.js +132 -7
  114. package/app/services/ai/geminiService.test.js +59 -2
  115. package/app/services/ai/globalModuleAiHelpService.js +3 -2
  116. package/app/services/ai/messageCommandExecutionService.js +2 -1
  117. package/app/services/ai/moduleAiHelpCoreService.js +45 -14
  118. package/app/services/ai/moduleToolExecutorService.js +3 -2
  119. package/app/services/ai/moduleToolRegistryService.js +2 -1
  120. package/app/services/ai/toolCandidateSelectorService.js +6 -5
  121. package/app/services/auth/googleWebLinkService.js +3 -2
  122. package/app/services/auth/whatsappLoginLinkService.js +3 -2
  123. package/app/services/external/pokeApiService.js +4 -3
  124. package/app/services/group/groupMetadataService.js +24 -1
  125. package/app/services/infra/dbWriteQueue.js +57 -26
  126. package/app/services/infra/featureFlagService.js +2 -1
  127. package/app/services/messaging/captchaService.js +3 -2
  128. package/app/services/messaging/newsBroadcastService.js +846 -29
  129. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  130. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  131. package/app/services/multiSession/groupOwnershipService.js +890 -0
  132. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  133. package/app/services/multiSession/sessionRegistryService.js +293 -0
  134. package/app/services/sticker/stickerFocusService.js +11 -10
  135. package/app/store/aiPromptStore.js +36 -19
  136. package/app/store/conversationSessionStore.js +7 -6
  137. package/app/store/groupConfigStore.js +41 -5
  138. package/app/store/premiumUserStore.js +21 -7
  139. package/app/utils/antiLink/antiLinkModule.js +352 -16
  140. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  141. package/app/workers/aiLearningWorker.js +6 -5
  142. package/app/workers/commandConfigEnrichmentWorker.js +4 -3
  143. package/database/index.js +14 -8
  144. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  145. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  146. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  147. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  148. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  149. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  150. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  151. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  152. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  153. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  154. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  155. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  156. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  157. package/database/schema.sql +102 -1
  158. package/docker-compose.yml +4 -1
  159. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  160. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
  161. package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
  162. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  163. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
  164. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  165. package/docs/security/omnizap-static-security-headers.conf +25 -0
  166. package/docs/wiki/Home.md +1 -1
  167. package/ecosystem.prod.config.cjs +32 -12
  168. package/index.js +57 -23
  169. package/observability/alert-rules.yml +20 -0
  170. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  171. package/observability/mysql-setup.sql +4 -4
  172. package/observability/system-admin-observability.md +26 -0
  173. package/package.json +20 -6
  174. package/public/apple-touch-icon.png +0 -0
  175. package/public/comandos/commands-catalog.json +2853 -3326
  176. package/public/favicon-16x16.png +0 -0
  177. package/public/favicon-32x32.png +0 -0
  178. package/public/favicon.ico +0 -0
  179. package/public/js/apps/apiDocsApp.js +3 -2
  180. package/public/js/apps/commandsReactApp.js +280 -99
  181. package/public/js/apps/createPackApp.js +11 -10
  182. package/public/js/apps/homeReactApp.js +181 -130
  183. package/public/js/apps/loginReactApp.js +1 -1
  184. package/public/js/apps/stickersApp.js +263 -110
  185. package/public/js/apps/termsReactApp.js +73 -24
  186. package/public/js/apps/userApp.js +4 -3
  187. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  188. package/public/js/apps/userReactApp.js +355 -280
  189. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  190. package/public/pages/api-docs.html +1 -1
  191. package/public/pages/aup.html +2 -2
  192. package/public/pages/dpa.html +3 -3
  193. package/public/pages/licenca.html +4 -4
  194. package/public/pages/login.html +1 -1
  195. package/public/pages/notice-and-takedown.html +2 -2
  196. package/public/pages/politica-de-privacidade.html +6 -6
  197. package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
  198. package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
  199. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
  200. package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
  201. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
  202. package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
  203. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
  204. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
  205. package/public/pages/stickers-admin.html +1 -1
  206. package/public/pages/stickers-create.html +1 -1
  207. package/public/pages/stickers.html +6 -6
  208. package/public/pages/suboperadores.html +2 -2
  209. package/public/pages/termos-de-uso-texto-integral.html +6 -6
  210. package/public/pages/termos-de-uso.html +3 -3
  211. package/public/pages/user-password-reset.html +4 -5
  212. package/public/pages/user-systemadm.html +9 -463
  213. package/public/pages/user.html +2 -2
  214. package/scripts/clear-whatsapp-session.sh +123 -0
  215. package/scripts/core-ai-mode.mjs +163 -0
  216. package/scripts/deploy.sh +11 -1
  217. package/scripts/email-broadcast-terms-update.mjs +2 -1
  218. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  219. package/scripts/generate-commands-catalog.mjs +166 -2
  220. package/scripts/generate-module-agents.mjs +2 -1
  221. package/scripts/generate-seo-satellite-pages.mjs +5 -4
  222. package/scripts/github-deploy-notify.mjs +2 -1
  223. package/scripts/github-release-notify.mjs +25 -10
  224. package/scripts/new-whatsapp-session.sh +317 -0
  225. package/scripts/release.sh +2 -19
  226. package/scripts/security-smoketest.mjs +6 -5
  227. package/scripts/security-web-surface-check.mjs +218 -0
  228. package/scripts/sticker-catalog-loadtest.mjs +5 -4
  229. package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
  230. package/server/auth/jwt/webJwtService.js +1 -1
  231. package/server/auth/stickerCatalogAuthContext.js +2 -1
  232. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
  233. package/server/auth/userPassword/userPasswordAuthService.js +2 -1
  234. package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
  235. package/server/auth/webAccount/webAccountHandlers.js +9 -10
  236. package/server/controllers/admin/adminPanelHandlers.js +267 -16
  237. package/server/controllers/admin/systemAdminController.js +267 -0
  238. package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
  239. package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
  240. package/server/controllers/sticker/stickerCatalogController.js +23 -36
  241. package/server/controllers/system/contactController.js +9 -17
  242. package/server/controllers/system/githubController.js +3 -2
  243. package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
  244. package/server/controllers/system/systemController.js +254 -1
  245. package/server/controllers/system/systemMetricsController.js +2 -1
  246. package/server/controllers/userController.js +6 -0
  247. package/server/email/emailTemplateService.js +5 -3
  248. package/server/http/httpServer.js +11 -6
  249. package/server/middleware/rateLimit.js +2 -1
  250. package/server/middleware/securityHeaders.js +20 -1
  251. package/server/routes/admin/systemAdminRouter.js +6 -0
  252. package/server/routes/indexRouter.js +30 -6
  253. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  254. package/server/routes/static/staticPageRouter.js +27 -1
  255. package/server/utils/publicContact.js +31 -0
  256. package/utils/time/timeModule.js +135 -0
  257. package/utils/time/timeModule.test.js +65 -0
  258. package/utils/whatsapp/contactEnv.js +39 -0
  259. package/vite.config.mjs +7 -1
  260. package/public/assets/images/brand-icon-192.png +0 -0
  261. 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: new Date().toISOString(),
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
- await fs.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
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 = new Date().toISOString();
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: 'OmniZap System',
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="OmniZap System" />
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 SYSTEM · SEO SATELLITE V2 · ATUALIZADO EM ${escapeHtml(generatedAt)}
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 || new Date().toISOString().slice(0, 10)).trim();
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 || new Date().toISOString()}`;
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 || '').trim().toLowerCase();
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
- let body = bodyArg;
118
- if (!body && bodyFile) {
119
- try {
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 = fs.readFileSync(resolvedBodyFile, 'utf8');
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
- body = sanitizeReleaseBody(body);
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"