@omnizap-system/omnizap 2.5.12

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 (425) hide show
  1. package/.clusterfuzzlite/Dockerfile +10 -0
  2. package/.env.example +907 -0
  3. package/.github/codeql/codeql-config.yml +10 -0
  4. package/.github/dependabot.yml +35 -0
  5. package/.github/workflows/ci.yml +73 -0
  6. package/.github/workflows/codeql.yml +106 -0
  7. package/.github/workflows/db-migration-check.yml +98 -0
  8. package/.github/workflows/dependency-review.yml +22 -0
  9. package/.github/workflows/deploy.yml +95 -0
  10. package/.github/workflows/release.yml +106 -0
  11. package/.github/workflows/security-attest-provenance.yml +51 -0
  12. package/.github/workflows/security-gitleaks.yml +34 -0
  13. package/.github/workflows/security-runner-hardening.yml +31 -0
  14. package/.github/workflows/security-scorecard.yml +44 -0
  15. package/.github/workflows/security-zap-baseline.yml +44 -0
  16. package/.github/workflows/security-zap-full-scan.yml +43 -0
  17. package/.github/workflows/security-zizmor.yml +36 -0
  18. package/.github/workflows/wiki-sync.yml +44 -0
  19. package/.gitleaks.toml +15 -0
  20. package/.prettierrc +34 -0
  21. package/CODE_OF_CONDUCT.md +114 -0
  22. package/LICENSE +56 -0
  23. package/README.md +110 -0
  24. package/SECURITY.md +110 -0
  25. package/app/config/index.js +4 -0
  26. package/app/configParts/adminIdentity.js +92 -0
  27. package/app/configParts/baileysConfig.js +1818 -0
  28. package/app/configParts/groupUtils.js +692 -0
  29. package/app/configParts/loggerConfig.js +394 -0
  30. package/app/configParts/messagePersistenceService.js +305 -0
  31. package/app/connection/baileysCompatibility.test.js +40 -0
  32. package/app/connection/baileysDbAuthState.js +344 -0
  33. package/app/connection/socketController.js +2243 -0
  34. package/app/controllers/messageController.js +7 -0
  35. package/app/controllers/messagePipeline/commandMiddleware.js +146 -0
  36. package/app/controllers/messagePipeline/conversationMiddleware.js +183 -0
  37. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +522 -0
  38. package/app/controllers/messagePipeline/postProcessingMiddleware.js +41 -0
  39. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +166 -0
  40. package/app/controllers/messageProcessingPipeline.js +699 -0
  41. package/app/modules/adminModule/AGENT.md +4056 -0
  42. package/app/modules/adminModule/adminAiHelpService.js +56 -0
  43. package/app/modules/adminModule/adminConfigRuntime.js +177 -0
  44. package/app/modules/adminModule/commandConfig.json +7122 -0
  45. package/app/modules/adminModule/groupCommandHandlers.js +1823 -0
  46. package/app/modules/adminModule/groupCommandHandlers.test.js +350 -0
  47. package/app/modules/adminModule/groupEventHandlers.js +399 -0
  48. package/app/modules/aiModule/AGENT.md +547 -0
  49. package/app/modules/aiModule/aiAiHelpService.js +14 -0
  50. package/app/modules/aiModule/aiConfigRuntime.js +135 -0
  51. package/app/modules/aiModule/catCommand.js +967 -0
  52. package/app/modules/aiModule/commandConfig.json +981 -0
  53. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  54. package/app/modules/gameModule/AGENT.md +196 -0
  55. package/app/modules/gameModule/commandConfig.json +366 -0
  56. package/app/modules/gameModule/diceCommand.js +42 -0
  57. package/app/modules/gameModule/gameAiHelpService.js +14 -0
  58. package/app/modules/gameModule/gameConfigRuntime.js +68 -0
  59. package/app/modules/menuModule/AGENT.md +205 -0
  60. package/app/modules/menuModule/commandConfig.json +366 -0
  61. package/app/modules/menuModule/common.js +316 -0
  62. package/app/modules/menuModule/menuAiHelpService.js +14 -0
  63. package/app/modules/menuModule/menuConfigRuntime.js +68 -0
  64. package/app/modules/menuModule/menus.js +66 -0
  65. package/app/modules/playModule/AGENT.md +321 -0
  66. package/app/modules/playModule/commandConfig.json +584 -0
  67. package/app/modules/playModule/playAiHelpService.js +14 -0
  68. package/app/modules/playModule/playCommand.js +1417 -0
  69. package/app/modules/playModule/playConfigRuntime.js +68 -0
  70. package/app/modules/quoteModule/AGENT.md +199 -0
  71. package/app/modules/quoteModule/commandConfig.json +366 -0
  72. package/app/modules/quoteModule/quoteAiHelpService.js +14 -0
  73. package/app/modules/quoteModule/quoteCommand.js +842 -0
  74. package/app/modules/quoteModule/quoteConfigRuntime.js +68 -0
  75. package/app/modules/rpgPokemonModule/AGENT.md +229 -0
  76. package/app/modules/rpgPokemonModule/commandConfig.json +386 -0
  77. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +795 -0
  78. package/app/modules/rpgPokemonModule/rpgBattleService.js +2110 -0
  79. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +770 -0
  80. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  81. package/app/modules/rpgPokemonModule/rpgPokemonAiHelpService.js +14 -0
  82. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +174 -0
  83. package/app/modules/rpgPokemonModule/rpgPokemonConfigRuntime.js +68 -0
  84. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  85. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  86. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  87. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  88. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1847 -0
  89. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6839 -0
  90. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  91. package/app/modules/statsModule/AGENT.md +320 -0
  92. package/app/modules/statsModule/commandConfig.json +540 -0
  93. package/app/modules/statsModule/globalRankingCommand.js +64 -0
  94. package/app/modules/statsModule/rankingCommand.js +41 -0
  95. package/app/modules/statsModule/rankingCommon.js +1305 -0
  96. package/app/modules/statsModule/statsAiHelpService.js +14 -0
  97. package/app/modules/statsModule/statsConfigRuntime.js +68 -0
  98. package/app/modules/stickerModule/AGENT.md +692 -0
  99. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  100. package/app/modules/stickerModule/commandConfig.json +1216 -0
  101. package/app/modules/stickerModule/convertToWebp.js +367 -0
  102. package/app/modules/stickerModule/stickerAiHelpService.js +14 -0
  103. package/app/modules/stickerModule/stickerCommand.js +446 -0
  104. package/app/modules/stickerModule/stickerConfigRuntime.js +68 -0
  105. package/app/modules/stickerModule/stickerConvertCommand.js +159 -0
  106. package/app/modules/stickerModule/stickerTextCommand.js +653 -0
  107. package/app/modules/stickerPackModule/AGENT.md +215 -0
  108. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  109. package/app/modules/stickerPackModule/autoPackCollectorService.js +357 -0
  110. package/app/modules/stickerPackModule/commandConfig.json +387 -0
  111. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +227 -0
  112. package/app/modules/stickerPackModule/domainEvents.js +52 -0
  113. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +429 -0
  114. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +75 -0
  115. package/app/modules/stickerPackModule/semanticThemeClusterService.js +544 -0
  116. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +400 -0
  117. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  118. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +175 -0
  119. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +3702 -0
  120. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +559 -0
  121. package/app/modules/stickerPackModule/stickerClassificationService.js +557 -0
  122. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +249 -0
  123. package/app/modules/stickerPackModule/stickerDomainEventBus.js +65 -0
  124. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +208 -0
  125. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +99 -0
  126. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  127. package/app/modules/stickerPackModule/stickerPackAiHelpService.js +14 -0
  128. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1148 -0
  129. package/app/modules/stickerPackModule/stickerPackConfigRuntime.js +68 -0
  130. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +152 -0
  131. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  132. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +101 -0
  133. package/app/modules/stickerPackModule/stickerPackItemRepository.js +432 -0
  134. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +313 -0
  135. package/app/modules/stickerPackModule/stickerPackMessageService.js +268 -0
  136. package/app/modules/stickerPackModule/stickerPackRepository.js +450 -0
  137. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +179 -0
  138. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +271 -0
  139. package/app/modules/stickerPackModule/stickerPackService.js +733 -0
  140. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +32 -0
  141. package/app/modules/stickerPackModule/stickerPackUtils.js +107 -0
  142. package/app/modules/stickerPackModule/stickerStorageService.js +559 -0
  143. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +242 -0
  144. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +242 -0
  145. package/app/modules/systemMetricsModule/AGENT.md +193 -0
  146. package/app/modules/systemMetricsModule/commandConfig.json +344 -0
  147. package/app/modules/systemMetricsModule/pingCommand.js +399 -0
  148. package/app/modules/systemMetricsModule/systemMetricsAiHelpService.js +14 -0
  149. package/app/modules/systemMetricsModule/systemMetricsConfigRuntime.js +68 -0
  150. package/app/modules/tiktokModule/AGENT.md +196 -0
  151. package/app/modules/tiktokModule/commandConfig.json +366 -0
  152. package/app/modules/tiktokModule/tiktokAiHelpService.js +14 -0
  153. package/app/modules/tiktokModule/tiktokCommand.js +716 -0
  154. package/app/modules/tiktokModule/tiktokConfigRuntime.js +68 -0
  155. package/app/modules/userModule/AGENT.md +200 -0
  156. package/app/modules/userModule/commandConfig.json +386 -0
  157. package/app/modules/userModule/userAiHelpService.js +14 -0
  158. package/app/modules/userModule/userCommand.js +1155 -0
  159. package/app/modules/userModule/userConfigRuntime.js +68 -0
  160. package/app/modules/waifuPicsModule/AGENT.md +431 -0
  161. package/app/modules/waifuPicsModule/commandConfig.json +780 -0
  162. package/app/modules/waifuPicsModule/waifuPicsAiHelpService.js +14 -0
  163. package/app/modules/waifuPicsModule/waifuPicsCommand.js +586 -0
  164. package/app/modules/waifuPicsModule/waifuPicsConfigRuntime.js +68 -0
  165. package/app/observability/metrics.js +766 -0
  166. package/app/services/ai/aiHelpResponseCacheRepository.js +280 -0
  167. package/app/services/ai/aiLearningRepository.js +400 -0
  168. package/app/services/ai/commandConfigEnrichmentRepository.js +769 -0
  169. package/app/services/ai/commandConfigEnrichmentService.js +452 -0
  170. package/app/services/ai/commandConfigValidationService.js +443 -0
  171. package/app/services/ai/commandToolBuilderService.js +192 -0
  172. package/app/services/ai/conversationRouterService.js +516 -0
  173. package/app/services/ai/geminiService.js +115 -0
  174. package/app/services/ai/geminiService.test.js +87 -0
  175. package/app/services/ai/globalModuleAiHelpService.js +1412 -0
  176. package/app/services/ai/globalToolCallingService.js +203 -0
  177. package/app/services/ai/messageCommandExecutionService.js +391 -0
  178. package/app/services/ai/moduleAiHelpCoreService.js +1099 -0
  179. package/app/services/ai/moduleAiHelpWrapperFactory.js +65 -0
  180. package/app/services/ai/moduleCommandConfigRuntimeService.js +113 -0
  181. package/app/services/ai/moduleToolExecutorService.js +464 -0
  182. package/app/services/ai/moduleToolRegistryService.js +178 -0
  183. package/app/services/ai/toolCandidateSelectorService.js +781 -0
  184. package/app/services/auth/googleWebLinkService.js +80 -0
  185. package/app/services/auth/whatsappLoginLinkService.js +230 -0
  186. package/app/services/external/pokeApiService.js +398 -0
  187. package/app/services/group/groupMetadataService.js +311 -0
  188. package/app/services/infra/dbWriteQueue.js +874 -0
  189. package/app/services/infra/featureFlagService.js +131 -0
  190. package/app/services/infra/queueUtils.js +55 -0
  191. package/app/services/messaging/captchaService.js +491 -0
  192. package/app/services/messaging/messagePersistenceService.js +1 -0
  193. package/app/services/messaging/newsBroadcastService.js +347 -0
  194. package/app/services/sticker/stickerFocusService.js +347 -0
  195. package/app/services/sticker/stickerFocusService.test.js +43 -0
  196. package/app/store/aiPromptStore.js +38 -0
  197. package/app/store/conversationSessionStore.js +131 -0
  198. package/app/store/groupConfigStore.js +58 -0
  199. package/app/store/premiumUserStore.js +54 -0
  200. package/app/utils/antiLink/antiLinkModule.js +700 -0
  201. package/app/utils/http/getImageBufferModule.js +18 -0
  202. package/app/utils/json/jsonSanitizer.js +113 -0
  203. package/app/utils/json/jsonSanitizer.test.js +40 -0
  204. package/app/utils/systemMetrics/systemMetricsModule.js +88 -0
  205. package/app/workers/aiLearningWorker.js +605 -0
  206. package/app/workers/commandConfigEnrichmentWorker.js +242 -0
  207. package/database/index.js +2075 -0
  208. package/database/init.js +151 -0
  209. package/database/migrations/.gitkeep +0 -0
  210. package/database/migrations/20260307_d0_hardening_down.sql +64 -0
  211. package/database/migrations/20260307_d0_hardening_up.sql +79 -0
  212. package/database/migrations/20260307_d1_terms_acceptance_down.sql +11 -0
  213. package/database/migrations/20260307_d1_terms_acceptance_up.sql +37 -0
  214. package/database/migrations/20260307_d2_auth_hardening_down.sql +75 -0
  215. package/database/migrations/20260307_d2_auth_hardening_up.sql +100 -0
  216. package/database/migrations/20260314_d7_canonical_sender_down.sql +53 -0
  217. package/database/migrations/20260314_d7_canonical_sender_up.sql +114 -0
  218. package/database/migrations/20260406_d30_security_analytics_down.sql +95 -0
  219. package/database/migrations/20260406_d30_security_analytics_up.sql +292 -0
  220. package/database/migrations/20260407_d31_web_google_session_token_hardening_down.sql +2 -0
  221. package/database/migrations/20260407_d31_web_google_session_token_hardening_up.sql +17 -0
  222. package/database/migrations/20260408_d32_ai_help_response_cache_down.sql +1 -0
  223. package/database/migrations/20260408_d32_ai_help_response_cache_up.sql +22 -0
  224. package/database/migrations/20260409_d33_ai_learning_tables_down.sql +4 -0
  225. package/database/migrations/20260409_d33_ai_learning_tables_up.sql +52 -0
  226. package/database/migrations/20260410_d34_command_config_enrichment_down.sql +3 -0
  227. package/database/migrations/20260410_d34_command_config_enrichment_up.sql +48 -0
  228. package/database/schema.sql +1186 -0
  229. package/docker-compose.yml +104 -0
  230. package/docs/audits/stickerCatalogController-out-of-scope.md +103 -0
  231. package/docs/audits/stickerCatalogController-symbols.md +58 -0
  232. package/docs/compliance/acceptable-use-policy-2026-03-07.md +35 -0
  233. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +80 -0
  234. package/docs/compliance/monthly-compliance-checklist-2026-03-07.md +88 -0
  235. package/docs/compliance/notice-and-takedown-policy-2026-03-07.md +34 -0
  236. package/docs/compliance/privacy-policy-2026-03-07.md +75 -0
  237. package/docs/compliance/subprocessors-inventory-2026-03-07.md +16 -0
  238. package/docs/database/production-db-evolution-runbook-2026q1.md +365 -0
  239. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +86 -0
  240. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +77 -0
  241. package/docs/security/network-hardening-runbook-2026-03-07.md +137 -0
  242. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +238 -0
  243. package/docs/seo/satellite-page-template.md +116 -0
  244. package/docs/seo/satellite-pages-phase1.json +364 -0
  245. package/docs/wiki/Home.md +120 -0
  246. package/docs/wiki/pair-extraordinaire-2026-03-08.md +3 -0
  247. package/docs/wiki/recent-changes-2026-03-08.md +47 -0
  248. package/ecosystem.prod.config.cjs +135 -0
  249. package/eslint.config.js +89 -0
  250. package/index.js +488 -0
  251. package/ml/clip_classifier/Dockerfile +18 -0
  252. package/ml/clip_classifier/README.md +118 -0
  253. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  254. package/ml/clip_classifier/classifier.py +654 -0
  255. package/ml/clip_classifier/embedding_store.py +481 -0
  256. package/ml/clip_classifier/env_loader.py +15 -0
  257. package/ml/clip_classifier/llm_label_expander.py +144 -0
  258. package/ml/clip_classifier/main.py +213 -0
  259. package/ml/clip_classifier/requirements.txt +10 -0
  260. package/ml/clip_classifier/similarity_engine.py +74 -0
  261. package/new-logo.png +0 -0
  262. package/observability/alert-rules.yml +60 -0
  263. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  264. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  265. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  266. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  267. package/observability/loki-config.yml +38 -0
  268. package/observability/mysql-setup.sql +46 -0
  269. package/observability/prometheus.yml +35 -0
  270. package/observability/promtail-config.yml +84 -0
  271. package/observability/sticker-catalog-slo.md +83 -0
  272. package/observability/sticker-scale-hardening-rollout.md +128 -0
  273. package/package.json +144 -0
  274. package/public/apple-touch-icon.png +0 -0
  275. package/public/assets/css/commands-react.input.css +71 -0
  276. package/public/assets/css/create-pack-react.input.css +31 -0
  277. package/public/assets/css/home-react.input.css +106 -0
  278. package/public/assets/css/login-react.input.css +58 -0
  279. package/public/assets/css/stickers-react.input.css +18 -0
  280. package/public/assets/css/terms-react.input.css +115 -0
  281. package/public/assets/css/user-react.input.css +57 -0
  282. package/public/assets/images/brand-icon-192.png +0 -0
  283. package/public/assets/images/brand-logo-128.webp +0 -0
  284. package/public/assets/images/hero-banner-1280.jpg +0 -0
  285. package/public/comandos/commands-catalog.json +4517 -0
  286. package/public/css/api-docs.css +161 -0
  287. package/public/css/stickers-admin.css +1288 -0
  288. package/public/css/styles.css +679 -0
  289. package/public/css/systemadm/admin.css +474 -0
  290. package/public/css/systemadm/base.css +73 -0
  291. package/public/css/systemadm/components.css +662 -0
  292. package/public/css/systemadm/layout.css +229 -0
  293. package/public/css/systemadm/tokens.css +56 -0
  294. package/public/favicon-16x16.png +0 -0
  295. package/public/favicon-32x32.png +0 -0
  296. package/public/favicon.ico +0 -0
  297. package/public/js/apps/apiDocsApp.js +235 -0
  298. package/public/js/apps/commandsReactApp.js +528 -0
  299. package/public/js/apps/createPackApp.js +1646 -0
  300. package/public/js/apps/homeReactApp.js +942 -0
  301. package/public/js/apps/loginReactApp.js +496 -0
  302. package/public/js/apps/stickersAdminApp.js +1753 -0
  303. package/public/js/apps/stickersApp.js +3797 -0
  304. package/public/js/apps/termsReactApp.js +528 -0
  305. package/public/js/apps/userApp.js +2540 -0
  306. package/public/js/apps/userProfile/actions.js +66 -0
  307. package/public/js/apps/userReactApp.js +547 -0
  308. package/public/js/catalog.js +950 -0
  309. package/public/pages/api-docs.html +40 -0
  310. package/public/pages/aup.html +158 -0
  311. package/public/pages/comandos.html +41 -0
  312. package/public/pages/dpa.html +227 -0
  313. package/public/pages/home.html +45 -0
  314. package/public/pages/licenca.html +182 -0
  315. package/public/pages/login.html +40 -0
  316. package/public/pages/notice-and-takedown.html +234 -0
  317. package/public/pages/politica-de-privacidade.html +251 -0
  318. package/public/pages/seo-bot-whatsapp-para-grupo.html +350 -0
  319. package/public/pages/seo-bot-whatsapp-sem-programar.html +350 -0
  320. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +350 -0
  321. package/public/pages/seo-como-criar-comandos-whatsapp.html +350 -0
  322. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +350 -0
  323. package/public/pages/seo-como-moderar-grupo-whatsapp.html +350 -0
  324. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +350 -0
  325. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +350 -0
  326. package/public/pages/stickers-admin.html +31 -0
  327. package/public/pages/stickers-create.html +41 -0
  328. package/public/pages/stickers.html +45 -0
  329. package/public/pages/suboperadores.html +237 -0
  330. package/public/pages/termos-de-uso-texto-integral.html +241 -0
  331. package/public/pages/termos-de-uso.html +41 -0
  332. package/public/pages/user-password-reset.html +32 -0
  333. package/public/pages/user-systemadm.html +508 -0
  334. package/public/pages/user.html +39 -0
  335. package/public/robots.txt +9 -0
  336. package/public/site.webmanifest +24 -0
  337. package/public/sitemap.xml +98 -0
  338. package/schemas/command-config.schema.json +582 -0
  339. package/scripts/baileys-compat-smoke.mjs +12 -0
  340. package/scripts/cache-bust.mjs +142 -0
  341. package/scripts/deploy.sh +916 -0
  342. package/scripts/email-broadcast-terms-update.mjs +170 -0
  343. package/scripts/enrich-command-discovery-fields.mjs +286 -0
  344. package/scripts/generate-command-config-schema.mjs +273 -0
  345. package/scripts/generate-commands-catalog.mjs +308 -0
  346. package/scripts/generate-module-agents.mjs +631 -0
  347. package/scripts/generate-seo-satellite-pages.mjs +400 -0
  348. package/scripts/github-deploy-notify.mjs +174 -0
  349. package/scripts/github-release-notify.mjs +219 -0
  350. package/scripts/release.sh +599 -0
  351. package/scripts/run-codeql-local.sh +116 -0
  352. package/scripts/run-prettier-all.mjs +25 -0
  353. package/scripts/security-smoketest.mjs +581 -0
  354. package/scripts/sticker-catalog-loadtest.mjs +210 -0
  355. package/scripts/sticker-worker-task.mjs +119 -0
  356. package/scripts/sync-readme-snapshot.mjs +133 -0
  357. package/scripts/validate-command-config-schema.mjs +130 -0
  358. package/scripts/validate-command-configs.mjs +15 -0
  359. package/scripts/wiki-sync.sh +191 -0
  360. package/server/auth/googleWebAuth/googleWebAuthRuntime.js +62 -0
  361. package/server/auth/googleWebAuth/googleWebAuthService.js +807 -0
  362. package/server/auth/jwt/webJwtService.js +147 -0
  363. package/server/auth/stickerCatalogAuthContext.js +165 -0
  364. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +189 -0
  365. package/server/auth/userPassword/index.js +14 -0
  366. package/server/auth/userPassword/userPasswordAuthService.js +422 -0
  367. package/server/auth/userPassword/userPasswordCrypto.js +199 -0
  368. package/server/auth/userPassword/userPasswordCrypto.test.js +76 -0
  369. package/server/auth/userPassword/userPasswordRecoveryService.js +728 -0
  370. package/server/auth/validation/authSchemas.js +236 -0
  371. package/server/auth/webAccount/webAccountHandlers.js +1434 -0
  372. package/server/controllers/admin/adminBanService.js +138 -0
  373. package/server/controllers/admin/adminPanelHandlers.js +2083 -0
  374. package/server/controllers/admin/stickerCatalogAdminContext.js +17 -0
  375. package/server/controllers/admin/systemAdminController.js +201 -0
  376. package/server/controllers/email/emailAutomationController.js +239 -0
  377. package/server/controllers/metricsController.js +21 -0
  378. package/server/controllers/seo/stickerCatalogSeoContext.js +514 -0
  379. package/server/controllers/sticker/nonCatalogHandlers.js +303 -0
  380. package/server/controllers/sticker/stickerCatalogController.js +4700 -0
  381. package/server/controllers/system/contactController.js +115 -0
  382. package/server/controllers/system/githubController.js +137 -0
  383. package/server/controllers/system/stickerCatalogSystemContext.js +758 -0
  384. package/server/controllers/system/storageController.js +154 -0
  385. package/server/controllers/system/systemController.js +135 -0
  386. package/server/controllers/system/systemMetricsController.js +156 -0
  387. package/server/controllers/system/visitController.js +90 -0
  388. package/server/controllers/userController.js +145 -0
  389. package/server/email/emailAutomationRuntime.js +225 -0
  390. package/server/email/emailAutomationService.js +125 -0
  391. package/server/email/emailOutboxRepository.js +282 -0
  392. package/server/email/emailTemplateService.js +480 -0
  393. package/server/email/emailTransportService.js +156 -0
  394. package/server/http/clientIp.js +95 -0
  395. package/server/http/httpRequestUtils.js +262 -0
  396. package/server/http/httpRequestUtils.test.js +80 -0
  397. package/server/http/httpServer.js +180 -0
  398. package/server/http/requestContext.js +20 -0
  399. package/server/http/siteRoutingUtils.js +87 -0
  400. package/server/index.js +1 -0
  401. package/server/middleware/cachePolicy.js +26 -0
  402. package/server/middleware/cachePolicyHelpers.js +1 -0
  403. package/server/middleware/endpointRateLimit.js +181 -0
  404. package/server/middleware/rateLimit.js +70 -0
  405. package/server/middleware/requireAdminAuth.js +48 -0
  406. package/server/middleware/securityHeaders.js +97 -0
  407. package/server/routes/admin/systemAdminRouter.js +64 -0
  408. package/server/routes/email/emailAutomationRouter.js +46 -0
  409. package/server/routes/health/healthRouter.js +41 -0
  410. package/server/routes/indexRouter.js +234 -0
  411. package/server/routes/metrics/metricsRouter.js +58 -0
  412. package/server/routes/static/staticPageRouter.js +134 -0
  413. package/server/routes/sticker/catalogHandlers/catalogAdminHttp.js +105 -0
  414. package/server/routes/sticker/catalogHandlers/catalogAuthHttp.js +77 -0
  415. package/server/routes/sticker/catalogHandlers/catalogPublicHttp.js +120 -0
  416. package/server/routes/sticker/catalogHandlers/catalogUploadHttp.js +83 -0
  417. package/server/routes/sticker/catalogRouter.js +77 -0
  418. package/server/routes/sticker/stickerApiRouter.js +84 -0
  419. package/server/routes/sticker/stickerDataRouter.js +145 -0
  420. package/server/routes/sticker/stickerSiteRouter.js +43 -0
  421. package/server/routes/user/userApiPaths.js +66 -0
  422. package/server/routes/user/userRouter.js +65 -0
  423. package/server/utils/safePath.js +26 -0
  424. package/utils/logger/loggerModule.js +35 -0
  425. package/vite.config.mjs +38 -0
@@ -0,0 +1,2243 @@
1
+ import makeWASocket, { DisconnectReason, Browsers, getAggregateVotesInPollMessage, areJidsSameUser, WAMessageStatus, WAMessageStubType, delayCancellable, getStatusFromReceiptType, promiseTimeout } from '@whiskeysockets/baileys';
2
+
3
+ import NodeCache from 'node-cache';
4
+ import { parseEnvBool, parseEnvCsv, parseEnvInt, resolveBaileysVersion, resolveAddressingModeFromMessageKey, normalizeAddressingMode, normalizePnToJid, normalizeWAPresence, baileysConnectionLogger as logger, baileysSocketLogger } from '../config/index.js';
5
+
6
+ import { Boom } from '@hapi/boom';
7
+ import qrcode from 'qrcode-terminal';
8
+ import path from 'node:path';
9
+
10
+ import { handleMessages } from '../controllers/messageController.js';
11
+ import { syncNewsBroadcastService } from '../services/messaging/newsBroadcastService.js';
12
+ import { setActiveSocket as storeActiveSocket, runActiveSocketMethod, isSocketOpen } from '../config/index.js';
13
+ import { recordError, recordMessagesUpsert } from '../observability/metrics.js';
14
+ import { resolveCaptchaByReaction } from '../services/messaging/captchaService.js';
15
+
16
+ import { handleGroupUpdate as handleGroupParticipantsEvent, handleGroupJoinRequest } from '../modules/adminModule/groupEventHandlers.js';
17
+
18
+ import { dbConfig, executeQuery, findBy, findById, pool, remove } from '../../database/index.js';
19
+ import { extractSenderInfoFromMessage, primeLidCache, resolveUserIdCached, isLidUserId, isWhatsAppUserId } from '../config/index.js';
20
+ import { queueBaileysEventInsert, queueChatUpdate, queueLidUpdate, queueMessageInsert } from '../services/infra/dbWriteQueue.js';
21
+ import { buildGroupMetadataFromGroup, buildGroupMetadataFromUpdate, upsertGroupMetadata, parseParticipantsFromDb } from '../services/group/groupMetadataService.js';
22
+ import { buildMessageData } from '../configParts/messagePersistenceService.js';
23
+ import { useDbAuthState } from './baileysDbAuthState.js';
24
+
25
+ import { fileURLToPath } from 'node:url';
26
+
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = path.dirname(__filename);
29
+
30
+ /**
31
+ * Indica se o ambiente de execução é de produção.
32
+ * @type {boolean}
33
+ */
34
+ const IS_PRODUCTION =
35
+ String(process.env.NODE_ENV || '')
36
+ .trim()
37
+ .toLowerCase() === 'production';
38
+ /**
39
+ * Tempo de vida (TTL) em segundos para o cache de retentativa de mensagem do Baileys.
40
+ * @type {number}
41
+ */
42
+ const MSG_RETRY_CACHE_TTL_SECONDS = parseEnvInt(process.env.BAILEYS_MSG_RETRY_CACHE_TTL_SECONDS, 600, 60, 24 * 60 * 60);
43
+ /**
44
+ * Período de verificação em segundos para o cache de retentativa de mensagem do Baileys.
45
+ * @type {number}
46
+ */
47
+ const MSG_RETRY_CACHE_CHECKPERIOD_SECONDS = parseEnvInt(process.env.BAILEYS_MSG_RETRY_CACHE_CHECKPERIOD_SECONDS, 120, 30, 3600);
48
+ /**
49
+ * Habilita ou desabilita o log de eventos do Baileys.
50
+ * @type {boolean}
51
+ */
52
+ const BAILEYS_EVENT_LOG_ENABLED = parseEnvBool(process.env.BAILEYS_EVENT_LOG_ENABLED, !IS_PRODUCTION);
53
+ /**
54
+ * Tempo em milissegundos para resetar a contagem de tentativas de reconexão do Baileys.
55
+ * @type {number}
56
+ */
57
+ const BAILEYS_RECONNECT_ATTEMPT_RESET_MS = parseEnvInt(process.env.BAILEYS_RECONNECT_ATTEMPT_RESET_MS, 10 * 60 * 1000, 60 * 1000, 24 * 60 * 60 * 1000);
58
+ /**
59
+ * Habilita ou desabilita a sincronização de grupos na conexão.
60
+ * @type {boolean}
61
+ */
62
+ const GROUP_SYNC_ON_CONNECT = parseEnvBool(process.env.GROUP_SYNC_ON_CONNECT, true);
63
+ /**
64
+ * Tempo limite em milissegundos para a sincronização de grupos.
65
+ * @type {number}
66
+ */
67
+ const GROUP_SYNC_TIMEOUT_MS = parseEnvInt(process.env.GROUP_SYNC_TIMEOUT_MS, 30 * 1000, 5 * 1000, 120 * 1000);
68
+ /**
69
+ * Número máximo de grupos a serem sincronizados.
70
+ * @type {number}
71
+ */
72
+ const GROUP_SYNC_MAX_GROUPS = parseEnvInt(process.env.GROUP_SYNC_MAX_GROUPS, 0, 0, 10_000);
73
+ /**
74
+ * Tamanho do lote para a sincronização de grupos.
75
+ * @type {number}
76
+ */
77
+ const GROUP_SYNC_BATCH_SIZE = parseEnvInt(process.env.GROUP_SYNC_BATCH_SIZE, 50, 1, 1000);
78
+ /**
79
+ * Habilita ou desabilita a rejeição automática de chamadas do Baileys.
80
+ * @type {boolean}
81
+ */
82
+ const BAILEYS_AUTO_REJECT_CALLS = parseEnvBool(process.env.BAILEYS_AUTO_REJECT_CALLS, true);
83
+ /**
84
+ * Habilita ou desabilita a recriação automática de sessão do Baileys.
85
+ * @type {boolean}
86
+ */
87
+ const BAILEYS_ENABLE_AUTO_SESSION_RECREATION = parseEnvBool(process.env.BAILEYS_ENABLE_AUTO_SESSION_RECREATION, true);
88
+ /**
89
+ * Habilita ou desabilita o cache de mensagens recentes do Baileys.
90
+ * @type {boolean}
91
+ */
92
+ const BAILEYS_ENABLE_RECENT_MESSAGE_CACHE = parseEnvBool(process.env.BAILEYS_ENABLE_RECENT_MESSAGE_CACHE, true);
93
+ /**
94
+ * Habilita ou desabilita a geração de pré-visualizações de link de alta qualidade do Baileys.
95
+ * @type {boolean}
96
+ */
97
+ const BAILEYS_GENERATE_HIGH_QUALITY_LINK_PREVIEW = parseEnvBool(process.env.BAILEYS_GENERATE_HIGH_QUALITY_LINK_PREVIEW, false);
98
+ /**
99
+ * Habilita ou desabilita o patch de mensagens antes do envio.
100
+ * @type {boolean}
101
+ */
102
+ const BAILEYS_PATCH_MESSAGE_BEFORE_SENDING = parseEnvBool(process.env.BAILEYS_PATCH_MESSAGE_BEFORE_SENDING, true);
103
+ /**
104
+ * Tempo de vida (TTL) em segundos para o cache de dispositivos de usuário do Baileys.
105
+ * @type {number}
106
+ */
107
+ const BAILEYS_USER_DEVICES_CACHE_TTL_SECONDS = parseEnvInt(process.env.BAILEYS_USER_DEVICES_CACHE_TTL_SECONDS, 300, 30, 24 * 60 * 60);
108
+ /**
109
+ * Período de verificação em segundos para o cache de dispositivos de usuário do Baileys.
110
+ * @type {number}
111
+ */
112
+ const BAILEYS_USER_DEVICES_CACHE_CHECKPERIOD_SECONDS = parseEnvInt(process.env.BAILEYS_USER_DEVICES_CACHE_CHECKPERIOD_SECONDS, 60, 15, 3600);
113
+ /**
114
+ * Tempo de vida (TTL) em segundos para o cache de mídia do Baileys.
115
+ * @type {number}
116
+ */
117
+ const BAILEYS_MEDIA_CACHE_TTL_SECONDS = parseEnvInt(process.env.BAILEYS_MEDIA_CACHE_TTL_SECONDS, 3600, 60, 7 * 24 * 60 * 60);
118
+ /**
119
+ * Período de verificação em segundos para o cache de mídia do Baileys.
120
+ * @type {number}
121
+ */
122
+ const BAILEYS_MEDIA_CACHE_CHECKPERIOD_SECONDS = parseEnvInt(process.env.BAILEYS_MEDIA_CACHE_CHECKPERIOD_SECONDS, 300, 30, 3600);
123
+ /**
124
+ * Tempo de vida (TTL) em segundos para o cache de metadados de grupo do Baileys.
125
+ * @type {number}
126
+ */
127
+ const BAILEYS_GROUP_METADATA_CACHE_TTL_SECONDS = parseEnvInt(process.env.BAILEYS_GROUP_METADATA_CACHE_TTL_SECONDS, 120, 10, 3600);
128
+ /**
129
+ * Período de verificação em segundos para o cache de metadados de grupo do Baileys.
130
+ * @type {number}
131
+ */
132
+ const BAILEYS_GROUP_METADATA_CACHE_CHECKPERIOD_SECONDS = parseEnvInt(process.env.BAILEYS_GROUP_METADATA_CACHE_CHECKPERIOD_SECONDS, 60, 10, 1800);
133
+ /**
134
+ * Identificador lógico da sessão de autenticação do Baileys no MySQL.
135
+ * Permite isolar múltiplas sessões no mesmo banco.
136
+ * @type {string}
137
+ */
138
+ const BAILEYS_AUTH_SESSION_ID = (() => {
139
+ const raw = String(process.env.BAILEYS_AUTH_SESSION_ID || '').trim();
140
+ return raw || 'default';
141
+ })();
142
+ /**
143
+ * Habilita bootstrap inicial do auth state no MySQL usando os arquivos locais legados.
144
+ * @type {boolean}
145
+ */
146
+ const BAILEYS_AUTH_BOOTSTRAP_FROM_FILES = parseEnvBool(process.env.BAILEYS_AUTH_BOOTSTRAP_FROM_FILES, true);
147
+ /**
148
+ * Habilita lock de escritor único para evitar múltiplos processos escrevendo a mesma sessão.
149
+ * @type {boolean}
150
+ */
151
+ const BAILEYS_SINGLE_WRITER_LOCK_ENABLED = parseEnvBool(process.env.BAILEYS_SINGLE_WRITER_LOCK_ENABLED, true);
152
+ /**
153
+ * Timeout (segundos) para tentar adquirir lock de escritor único no MySQL.
154
+ * @type {number}
155
+ */
156
+ const BAILEYS_SINGLE_WRITER_LOCK_TIMEOUT_SECONDS = parseEnvInt(process.env.BAILEYS_SINGLE_WRITER_LOCK_TIMEOUT_SECONDS, 2, 0, 60);
157
+ /**
158
+ * Atraso para nova tentativa quando lock de escritor único estiver ocupado.
159
+ * @type {number}
160
+ */
161
+ const BAILEYS_SINGLE_WRITER_LOCK_RETRY_DELAY_MS = parseEnvInt(process.env.BAILEYS_SINGLE_WRITER_LOCK_RETRY_DELAY_MS, 15_000, 1_000, 300_000);
162
+ /**
163
+ * Nome do lock de escritor único usado no MySQL.
164
+ * @type {string}
165
+ */
166
+ const BAILEYS_SINGLE_WRITER_LOCK_NAME = (() => {
167
+ const raw = String(process.env.BAILEYS_SINGLE_WRITER_LOCK_NAME || '').trim();
168
+ if (raw) return raw;
169
+ const dbLabel = String(dbConfig?.database || 'db').replace(/[^a-zA-Z0-9:_-]+/g, '_');
170
+ return `omnizap:baileys:writer:${dbLabel}`;
171
+ })();
172
+ /**
173
+ * Habilita ou desabilita o diário de eventos do Baileys.
174
+ * @type {boolean}
175
+ */
176
+ const BAILEYS_EVENT_JOURNAL_ENABLED = parseEnvBool(process.env.BAILEYS_EVENT_JOURNAL_ENABLED, false);
177
+ /**
178
+ * Lista de eventos padrão para o diário de eventos do Baileys.
179
+ * @type {string[]}
180
+ */
181
+ const DEFAULT_BAILEYS_EVENT_JOURNAL_EVENTS = ['connection.update', 'messages.upsert', 'messages.update', 'messages.delete', 'messages.reaction', 'message-receipt.update', 'chats.upsert', 'chats.update', 'chats.delete', 'groups.upsert', 'groups.update', 'group-participants.update', 'group.join-request', 'lid-mapping.update'];
182
+ /**
183
+ * Lista de eventos configurados para o diário de eventos do Baileys, obtidos do ambiente.
184
+ * @type {string[]}
185
+ */
186
+ const BAILEYS_EVENT_JOURNAL_EVENT_LIST = parseEnvCsv(process.env.BAILEYS_EVENT_JOURNAL_EVENTS, DEFAULT_BAILEYS_EVENT_JOURNAL_EVENTS);
187
+ /**
188
+ * Indica se todos os eventos do Baileys devem ser registrados no diário.
189
+ * @type {boolean}
190
+ */
191
+ const BAILEYS_EVENT_JOURNAL_ALL_EVENTS = BAILEYS_EVENT_JOURNAL_EVENT_LIST.includes('*');
192
+ /**
193
+ * Conjunto de eventos do Baileys a serem registrados no diário.
194
+ * @type {Set<string>}
195
+ */
196
+ const BAILEYS_EVENT_JOURNAL_EVENTS = new Set(BAILEYS_EVENT_JOURNAL_EVENT_LIST.filter((eventName) => eventName !== '*'));
197
+ /**
198
+ * Conjunto de tipos de recibo que não mapeiam diretamente para status no Baileys.
199
+ * @type {Set<string>}
200
+ */
201
+ const MESSAGE_RECEIPT_TYPES_WITHOUT_STATUS = new Set(['hist_sync', 'peer_msg', 'inactive']);
202
+ /**
203
+ * Mapeia códigos de status de mensagem do Baileys para seus nomes.
204
+ * @type {Map<number, string>}
205
+ */
206
+ const MESSAGE_STATUS_CODE_TO_NAME = new Map(
207
+ Object.entries(WAMessageStatus)
208
+ .filter(([, value]) => typeof value === 'number')
209
+ .map(([name, value]) => [value, name]),
210
+ );
211
+ /**
212
+ * Mapeia códigos de tipo de stub de mensagem do Baileys para seus nomes.
213
+ * @type {Map<number, string>}
214
+ */
215
+ const MESSAGE_STUB_CODE_TO_NAME = new Map(
216
+ Object.entries(WAMessageStubType)
217
+ .filter(([, value]) => typeof value === 'number')
218
+ .map(([name, value]) => [value, name]),
219
+ );
220
+
221
+ /**
222
+ * Normaliza o status de uma mensagem do Baileys.
223
+ * @param {string | number} status - O código de status da mensagem.
224
+ * @returns {{code: number, name: string | null} | null} Objeto contendo o código e o nome do status, ou null se inválido.
225
+ */
226
+ const normalizeMessageStatus = (status) => {
227
+ const statusCode = Number(status);
228
+ if (!Number.isFinite(statusCode)) return null;
229
+ return {
230
+ code: statusCode,
231
+ name: MESSAGE_STATUS_CODE_TO_NAME.get(statusCode) || null,
232
+ };
233
+ };
234
+
235
+ /**
236
+ * Normaliza o tipo de stub de uma mensagem do Baileys.
237
+ * @param {string | number} stubType - O código do tipo de stub da mensagem.
238
+ * @returns {{code: number, name: string | null} | null} Objeto contendo o código e o nome do tipo de stub, ou null se inválido.
239
+ */
240
+ const normalizeMessageStubType = (stubType) => {
241
+ const stubTypeCode = Number(stubType);
242
+ if (!Number.isFinite(stubTypeCode)) return null;
243
+ return {
244
+ code: stubTypeCode,
245
+ name: MESSAGE_STUB_CODE_TO_NAME.get(stubTypeCode) || null,
246
+ };
247
+ };
248
+
249
+ /**
250
+ * Normaliza o tipo de recibo de mensagem.
251
+ * @param {string | undefined} receiptType - O tipo de recibo da mensagem.
252
+ * @returns {string | undefined} O tipo de recibo normalizado, ou undefined se inválido.
253
+ */
254
+ const normalizeMessageReceiptType = (receiptType) => {
255
+ if (receiptType === undefined) return undefined;
256
+ if (typeof receiptType !== 'string') return undefined;
257
+ const normalizedType = receiptType.trim();
258
+ if (!normalizedType) return undefined;
259
+ if (getStatusFromReceiptType(normalizedType) !== undefined) return normalizedType;
260
+ return MESSAGE_RECEIPT_TYPES_WITHOUT_STATUS.has(normalizedType) ? normalizedType : undefined;
261
+ };
262
+
263
+ /**
264
+ * Instância ativa do socket do WhatsApp.
265
+ * @type {import('@whiskeysockets/baileys').WASocket | null}
266
+ */
267
+ let activeSocket = null;
268
+ /**
269
+ * Contador de tentativas de conexão.
270
+ * @type {number}
271
+ */
272
+ let connectionAttempts = 0;
273
+ /**
274
+ * Timestamp do início da janela de reconexão.
275
+ * @type {number}
276
+ */
277
+ let reconnectWindowStartedAt = 0;
278
+ /**
279
+ * Cache para contadores de retentativa de mensagens.
280
+ * @type {NodeCache}
281
+ */
282
+ const msgRetryCounterCache = new NodeCache({
283
+ stdTTL: MSG_RETRY_CACHE_TTL_SECONDS,
284
+ checkperiod: MSG_RETRY_CACHE_CHECKPERIOD_SECONDS,
285
+ useClones: false,
286
+ });
287
+ /**
288
+ * Backend de cache para informações de dispositivos de usuário.
289
+ * @type {NodeCache}
290
+ */
291
+ const userDevicesCacheBackend = new NodeCache({
292
+ stdTTL: BAILEYS_USER_DEVICES_CACHE_TTL_SECONDS,
293
+ checkperiod: BAILEYS_USER_DEVICES_CACHE_CHECKPERIOD_SECONDS,
294
+ useClones: false,
295
+ });
296
+ /**
297
+ * Backend de cache para mídia.
298
+ * @type {NodeCache}
299
+ */
300
+ const mediaCacheBackend = new NodeCache({
301
+ stdTTL: BAILEYS_MEDIA_CACHE_TTL_SECONDS,
302
+ checkperiod: BAILEYS_MEDIA_CACHE_CHECKPERIOD_SECONDS,
303
+ useClones: false,
304
+ });
305
+ /**
306
+ * Cache para metadados de grupos.
307
+ * @type {NodeCache}
308
+ */
309
+ const groupMetadataCache = new NodeCache({
310
+ stdTTL: BAILEYS_GROUP_METADATA_CACHE_TTL_SECONDS,
311
+ checkperiod: BAILEYS_GROUP_METADATA_CACHE_CHECKPERIOD_SECONDS,
312
+ useClones: false,
313
+ });
314
+ /**
315
+ * Número máximo de tentativas de conexão antes de entrar em um período de espera.
316
+ * @type {number}
317
+ */
318
+ const MAX_CONNECTION_ATTEMPTS = 5;
319
+ /**
320
+ * Atraso inicial em milissegundos para a primeira tentativa de reconexão.
321
+ * @type {number}
322
+ */
323
+ const INITIAL_RECONNECT_DELAY = 3000;
324
+ /**
325
+ * Timeout para reconexão.
326
+ * @type {ReturnType<typeof delayCancellable> | null}
327
+ */
328
+ let reconnectTimeout = null;
329
+ /**
330
+ * Promessa de conexão ativa.
331
+ * @type {Promise<void> | null}
332
+ */
333
+ let connectPromise = null;
334
+ /**
335
+ * Geração atual do socket (incrementado a cada nova conexão).
336
+ * @type {number}
337
+ */
338
+ let socketGeneration = 0;
339
+ /**
340
+ * Conexão MySQL dedicada para manter lock de escritor único do Baileys.
341
+ * @type {import('mysql2/promise').PoolConnection | null}
342
+ */
343
+ let baileysWriterLockConnection = null;
344
+ /**
345
+ * Nomes de todos os eventos do Baileys que são monitorados.
346
+ * @type {string[]}
347
+ */
348
+ const BAILEYS_EVENT_NAMES = ['connection.update', 'creds.update', 'messaging-history.set', 'chats.upsert', 'chats.update', 'lid-mapping.update', 'chats.delete', 'presence.update', 'contacts.upsert', 'contacts.update', 'messages.delete', 'messages.update', 'messages.media-update', 'messages.upsert', 'messages.reaction', 'message-receipt.update', 'groups.upsert', 'groups.update', 'group-participants.update', 'group.join-request', 'group.member-tag.update', 'blocklist.set', 'blocklist.update', 'call', 'labels.edit', 'labels.association', 'newsletter.reaction', 'newsletter.view', 'newsletter-participants.update', 'newsletter-settings.update', 'chats.lock', 'settings.update'];
349
+ /**
350
+ * Conjunto de eventos do Baileys que possuem lógica de log interna.
351
+ * @type {Set<string>}
352
+ */
353
+ const BAILEYS_EVENTS_WITH_INTERNAL_LOG = new Set(['creds.update', 'connection.update', 'messages.upsert', 'messages.update', 'messages.media-update', 'message-receipt.update', 'groups.update', 'group-participants.update']);
354
+ /**
355
+ * Conjunto de eventos do Baileys que são considerados "barulhentos" em produção e podem ser desabilitados.
356
+ * @type {Set<string>}
357
+ */
358
+ const BAILEYS_NOISY_EVENTS_IN_PRODUCTION = new Set(['presence.update']);
359
+
360
+ /**
361
+ * Cria um adaptador de armazenamento de cache simples para um NodeCache.
362
+ * @param {NodeCache} cache - Instância do NodeCache.
363
+ * @returns {{get: Function, set: Function, del: Function, flushAll: Function}} Adaptador de cache.
364
+ */
365
+ const createCacheStoreAdapter = (cache) => ({
366
+ get: (key) => cache.get(key),
367
+ set: (key, value) => cache.set(key, value),
368
+ del: (key) => cache.del(key),
369
+ flushAll: () => cache.flushAll(),
370
+ });
371
+
372
+ /**
373
+ * Cria um adaptador de armazenamento de cache estendido com métodos multi para um NodeCache.
374
+ * @param {NodeCache} cache - Instância do NodeCache.
375
+ * @returns {{get: Function, set: Function, del: Function, flushAll: Function, mget: Function, mset: Function, mdel: Function}} Adaptador de cache estendido.
376
+ */
377
+ const createExtendedCacheStoreAdapter = (cache) => ({
378
+ ...createCacheStoreAdapter(cache),
379
+ mget: (keys) => cache.mget(keys),
380
+ mset: (entries) => {
381
+ if (!Array.isArray(entries) || entries.length === 0) return;
382
+ cache.mset(
383
+ entries
384
+ .filter((entry) => entry && typeof entry.key === 'string')
385
+ .map((entry) => ({
386
+ key: entry.key,
387
+ val: entry.value,
388
+ })),
389
+ );
390
+ },
391
+ mdel: (keys) => cache.del(keys),
392
+ });
393
+
394
+ /**
395
+ * Cache para informações de dispositivos de usuário.
396
+ * @type {ReturnType<typeof createExtendedCacheStoreAdapter>}
397
+ */
398
+ const userDevicesCache = createExtendedCacheStoreAdapter(userDevicesCacheBackend);
399
+ /**
400
+ * Cache para mídia.
401
+ * @type {ReturnType<typeof createCacheStoreAdapter>}
402
+ */
403
+ const mediaCache = createCacheStoreAdapter(mediaCacheBackend);
404
+
405
+ /**
406
+ * Verifica se um valor é um objeto "simples" (plain object).
407
+ * @param {unknown} value - O valor a ser verificado.
408
+ * @returns {boolean} True se for um objeto simples, false caso contrário.
409
+ */
410
+ const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]';
411
+
412
+ /**
413
+ * Sanitiza o payload de uma mensagem removendo funções, valores undefined e referências circulares.
414
+ * @param {any} value - O payload da mensagem a ser sanitizado.
415
+ * @param {WeakSet<object>} [seen=new WeakSet()] - Conjunto de objetos já vistos para evitar referências circulares.
416
+ * @returns {any} O payload sanitizado.
417
+ */
418
+ const sanitizeMessagePayload = (value, seen = new WeakSet()) => {
419
+ if (value === undefined || typeof value === 'function') return undefined;
420
+ if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'bigint') return value;
421
+ if (typeof value === 'number') return Number.isFinite(value) ? value : undefined;
422
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array || value instanceof Date) return value;
423
+ if (typeof value !== 'object') return value;
424
+ if (seen.has(value)) return undefined;
425
+
426
+ seen.add(value);
427
+ if (Array.isArray(value)) {
428
+ const sanitizedArray = [];
429
+ for (const item of value) {
430
+ const sanitized = sanitizeMessagePayload(item, seen);
431
+ if (typeof sanitized !== 'undefined') sanitizedArray.push(sanitized);
432
+ }
433
+ return sanitizedArray;
434
+ }
435
+
436
+ if (!isPlainObject(value)) return value;
437
+
438
+ const sanitizedObject = {};
439
+ for (const [key, item] of Object.entries(value)) {
440
+ const sanitized = sanitizeMessagePayload(item, seen);
441
+ if (typeof sanitized !== 'undefined') {
442
+ sanitizedObject[key] = sanitized;
443
+ }
444
+ }
445
+
446
+ return sanitizedObject;
447
+ };
448
+
449
+ /**
450
+ * Aplica saneamento a uma mensagem antes de enviá-la, se a funcionalidade estiver habilitada.
451
+ * @async
452
+ * @param {any} message - A mensagem a ser potencialmente sanitizada.
453
+ * @returns {Promise<any>} A mensagem sanitizada ou original.
454
+ */
455
+ const patchMessageBeforeSending = async (message) => {
456
+ if (!BAILEYS_PATCH_MESSAGE_BEFORE_SENDING) return message;
457
+ try {
458
+ const sanitized = sanitizeMessagePayload(message);
459
+ if (!sanitized || typeof sanitized !== 'object') return message;
460
+ return sanitized;
461
+ } catch (error) {
462
+ logger.warn('Falha ao sanitizar payload de mensagem antes do envio. Usando payload original.', {
463
+ action: 'patch_message_before_sending_error',
464
+ error: error?.message,
465
+ });
466
+ return message;
467
+ }
468
+ };
469
+
470
+ /**
471
+ * Constrói uma lista de participantes de grupo a partir de dados brutos do DB.
472
+ * @param {any} participantsRaw - Dados brutos dos participantes.
473
+ * @returns {Array<{id: string, admin?: 'admin' | 'superadmin'} | null>} Lista de participantes formatada.
474
+ */
475
+ const buildCachedParticipants = (participantsRaw) => {
476
+ const participants = parseParticipantsFromDb(participantsRaw);
477
+ if (!Array.isArray(participants) || participants.length === 0) return [];
478
+ return participants
479
+ .map((entry) => {
480
+ const id = entry?.id || entry?.jid || entry?.lid || null;
481
+ if (!id) return null;
482
+ const admin = entry?.admin === 'admin' || entry?.admin === 'superadmin' ? entry.admin : entry?.isAdmin ? 'admin' : undefined;
483
+ return admin ? { id, admin } : { id };
484
+ })
485
+ .filter(Boolean);
486
+ };
487
+
488
+ /**
489
+ * Resolve metadados de grupo do cache local ou do banco de dados.
490
+ * @async
491
+ * @param {string} jid - O JID do grupo.
492
+ * @returns {Promise<object | undefined>} Metadados do grupo ou undefined.
493
+ */
494
+ const resolveCachedGroupMetadata = async (jid) => {
495
+ if (!jid || typeof jid !== 'string' || !jid.endsWith('@g.us')) return undefined;
496
+
497
+ const cached = groupMetadataCache.get(jid);
498
+ if (cached) return cached;
499
+
500
+ try {
501
+ const row = await findById('groups_metadata', jid);
502
+ const data = Array.isArray(row) ? row[0] : row;
503
+ if (!data) return undefined;
504
+
505
+ const participants = buildCachedParticipants(data.participants || data.participants_json);
506
+ if (participants.length === 0) return undefined;
507
+
508
+ const metadata = {
509
+ id: data.id || jid,
510
+ subject: data.subject || data.name || '',
511
+ desc: data.description || data.desc || '',
512
+ owner: data.owner_jid || data.owner || undefined,
513
+ creation: Number.isFinite(Number(data.creation)) ? Number(data.creation) : undefined,
514
+ participants,
515
+ addressingMode: normalizeAddressingMode(data.addressing_mode || data.addressingMode),
516
+ ephemeralDuration: Number.isFinite(Number(data.ephemeral_duration ?? data.ephemeralDuration)) ? Number(data.ephemeral_duration ?? data.ephemeralDuration) : undefined,
517
+ };
518
+
519
+ groupMetadataCache.set(jid, metadata);
520
+ return metadata;
521
+ } catch (error) {
522
+ logger.debug('Falha ao resolver metadados de grupo do cache local.', {
523
+ action: 'cached_group_metadata_lookup_error',
524
+ jid,
525
+ error: error?.message,
526
+ });
527
+ return undefined;
528
+ }
529
+ };
530
+
531
+ /**
532
+ * Invalida o cache de metadados para um grupo específico.
533
+ * @param {string} groupId - O ID do grupo a ser invalidado no cache.
534
+ * @returns {void}
535
+ */
536
+ const invalidateCachedGroupMetadata = (groupId) => {
537
+ if (!groupId || typeof groupId !== 'string') return;
538
+ groupMetadataCache.del(groupId);
539
+ };
540
+
541
+ /**
542
+ * Gera um resumo conciso do payload de um evento do Baileys para fins de log e persistência.
543
+ * @param {string} eventName - O nome do evento do Baileys.
544
+ * @param {any} payload - O payload do evento.
545
+ * @returns {object} Um objeto resumindo o payload.
546
+ */
547
+ const summarizeBaileysEventPayload = (eventName, payload) => {
548
+ if (payload === null) return { payloadType: 'null' };
549
+ if (payload === undefined) return { payloadType: 'undefined' };
550
+ if (Buffer.isBuffer(payload)) {
551
+ return { payloadType: 'buffer', bytes: payload.length };
552
+ }
553
+
554
+ if (Array.isArray(payload)) {
555
+ const summary = { payloadType: 'array', items: payload.length };
556
+ if (payload.length > 0 && payload[0] && typeof payload[0] === 'object') {
557
+ summary.sampleKeys = Object.keys(payload[0]).slice(0, 6);
558
+ }
559
+ return summary;
560
+ }
561
+
562
+ if (typeof payload !== 'object') {
563
+ if (typeof payload === 'string') {
564
+ return { payloadType: 'string', length: payload.length };
565
+ }
566
+ return { payloadType: typeof payload, value: payload };
567
+ }
568
+
569
+ const summary = { payloadType: 'object', keys: Object.keys(payload).slice(0, 10) };
570
+
571
+ switch (eventName) {
572
+ case 'messaging-history.set':
573
+ summary.chats = Array.isArray(payload.chats) ? payload.chats.length : 0;
574
+ summary.contacts = Array.isArray(payload.contacts) ? payload.contacts.length : 0;
575
+ summary.messages = Array.isArray(payload.messages) ? payload.messages.length : 0;
576
+ summary.isLatest = payload.isLatest ?? null;
577
+ summary.progress = payload.progress ?? null;
578
+ summary.syncType = payload.syncType ?? null;
579
+ break;
580
+ case 'presence.update':
581
+ summary.id = payload.id ?? null;
582
+ summary.presencesCount = payload.presences ? Object.keys(payload.presences).length : 0;
583
+ break;
584
+ case 'chats.lock':
585
+ summary.id = payload.id ?? null;
586
+ summary.locked = payload.locked ?? null;
587
+ break;
588
+ case 'settings.update':
589
+ summary.setting = payload.setting ?? null;
590
+ break;
591
+ case 'messages.delete':
592
+ if (payload.all === true) {
593
+ summary.all = true;
594
+ summary.jid = payload.jid ?? null;
595
+ } else if (Array.isArray(payload.keys)) {
596
+ summary.keysCount = payload.keys.length;
597
+ }
598
+ break;
599
+ case 'messages.update':
600
+ if (Array.isArray(payload)) {
601
+ summary.updatesCount = payload.length;
602
+ const firstUpdate = payload[0];
603
+ const status = normalizeMessageStatus(firstUpdate?.update?.status);
604
+ const stubType = normalizeMessageStubType(firstUpdate?.update?.messageStubType ?? firstUpdate?.update?.stubType);
605
+ const addressingMode = resolveAddressingModeFromMessageKey(firstUpdate?.key);
606
+ summary.pollUpdatesCount = payload.filter((entry) => Array.isArray(entry?.update?.pollUpdates) && entry.update.pollUpdates.length > 0).length;
607
+ summary.firstStatusCode = status?.code ?? null;
608
+ summary.firstStatusName = status?.name ?? null;
609
+ summary.firstStubTypeCode = stubType?.code ?? null;
610
+ summary.firstStubTypeName = stubType?.name ?? null;
611
+ summary.firstAddressingMode = addressingMode ?? null;
612
+ }
613
+ break;
614
+ case 'messages.media-update':
615
+ if (Array.isArray(payload)) {
616
+ summary.updatesCount = payload.length;
617
+ summary.withMediaCount = payload.filter((entry) => Boolean(entry?.media)).length;
618
+ summary.withErrorCount = payload.filter((entry) => Boolean(entry?.error)).length;
619
+ }
620
+ break;
621
+ case 'message-receipt.update':
622
+ if (Array.isArray(payload)) {
623
+ summary.receiptsCount = payload.length;
624
+ const receiptTypes = new Set();
625
+ for (const entry of payload) {
626
+ const type = normalizeMessageReceiptType(entry?.receipt?.type);
627
+ if (type) receiptTypes.add(type);
628
+ }
629
+ summary.receiptTypes = Array.from(receiptTypes).slice(0, 8);
630
+ }
631
+ break;
632
+ case 'blocklist.set':
633
+ summary.blocklistCount = Array.isArray(payload.blocklist) ? payload.blocklist.length : 0;
634
+ break;
635
+ case 'blocklist.update':
636
+ summary.blocklistCount = Array.isArray(payload.blocklist) ? payload.blocklist.length : 0;
637
+ summary.type = payload.type ?? null;
638
+ break;
639
+ case 'group.join-request':
640
+ summary.groupId = payload.id ?? null;
641
+ summary.participant = payload.participant ?? null;
642
+ summary.action = payload.action ?? null;
643
+ summary.method = payload.method ?? null;
644
+ break;
645
+ case 'group.member-tag.update':
646
+ summary.groupId = payload.groupId ?? null;
647
+ summary.participant = payload.participant ?? null;
648
+ summary.label = payload.label ?? null;
649
+ break;
650
+ case 'labels.association':
651
+ summary.type = payload.type ?? null;
652
+ summary.labelId = payload.association?.labelId ?? null;
653
+ summary.chatId = payload.association?.chatId ?? null;
654
+ break;
655
+ case 'labels.edit':
656
+ summary.labelId = payload.id ?? null;
657
+ summary.labelName = payload.name ?? payload.label ?? null;
658
+ break;
659
+ case 'newsletter.reaction':
660
+ summary.id = payload.id ?? null;
661
+ summary.serverId = payload.server_id ?? null;
662
+ summary.reactionCode = payload.reaction?.code ?? null;
663
+ summary.reactionCount = payload.reaction?.count ?? null;
664
+ summary.reactionRemoved = payload.reaction?.removed ?? null;
665
+ break;
666
+ case 'newsletter.view':
667
+ summary.id = payload.id ?? null;
668
+ summary.serverId = payload.server_id ?? null;
669
+ summary.count = payload.count ?? null;
670
+ break;
671
+ case 'newsletter-participants.update':
672
+ summary.id = payload.id ?? null;
673
+ summary.user = payload.user ?? null;
674
+ summary.action = payload.action ?? null;
675
+ summary.newRole = payload.new_role ?? null;
676
+ break;
677
+ case 'newsletter-settings.update':
678
+ summary.id = payload.id ?? null;
679
+ summary.updateKeys = payload.update ? Object.keys(payload.update).slice(0, 6) : [];
680
+ break;
681
+ case 'lid-mapping.update':
682
+ summary.lid = payload.lid ?? null;
683
+ summary.pn = payload.pn ?? payload.jid ?? null;
684
+ break;
685
+ default:
686
+ break;
687
+ }
688
+
689
+ return summary;
690
+ };
691
+
692
+ /**
693
+ * Determina se um evento específico do Baileys deve ser logado.
694
+ * @param {string} eventName - O nome do evento do Baileys.
695
+ * @returns {boolean} True se o evento deve ser logado, false caso contrário.
696
+ */
697
+ const shouldLogBaileysEvent = (eventName) => {
698
+ if (!BAILEYS_EVENT_LOG_ENABLED) return false;
699
+ if (IS_PRODUCTION && BAILEYS_NOISY_EVENTS_IN_PRODUCTION.has(eventName)) return false;
700
+ return true;
701
+ };
702
+
703
+ /**
704
+ * Registra os loggers para os eventos do Baileys.
705
+ * @param {import('@whiskeysockets/baileys').WASocket} sock - A instância do socket do Baileys.
706
+ * @returns {void}
707
+ */
708
+ const registerBaileysEventLoggers = (sock) => {
709
+ const eventsToLog = BAILEYS_EVENT_NAMES.filter((eventName) => !BAILEYS_EVENTS_WITH_INTERNAL_LOG.has(eventName) && shouldLogBaileysEvent(eventName));
710
+
711
+ for (const eventName of eventsToLog) {
712
+ sock.ev.on(eventName, (payload) => {
713
+ const summary = summarizeBaileysEventPayload(eventName, payload);
714
+ logger.debug('Evento Baileys recebido.', {
715
+ action: 'baileys_event',
716
+ event: eventName,
717
+ ...summary,
718
+ timestamp: new Date().toISOString(),
719
+ });
720
+ });
721
+ }
722
+
723
+ logger.debug('Loggers de eventos Baileys registrados.', {
724
+ action: 'baileys_event_logger_ready',
725
+ enabled: BAILEYS_EVENT_LOG_ENABLED,
726
+ eventsCount: eventsToLog.length,
727
+ events: eventsToLog,
728
+ });
729
+ };
730
+
731
+ /**
732
+ * Determina se um evento do Baileys deve ser persistido no diário de eventos.
733
+ * @param {string} eventName - O nome do evento do Baileys.
734
+ * @returns {boolean} True se o evento deve ser persistido, false caso contrário.
735
+ */
736
+ const shouldPersistBaileysEvent = (eventName) => {
737
+ if (!BAILEYS_EVENT_JOURNAL_ENABLED) return false;
738
+ if (BAILEYS_EVENT_JOURNAL_ALL_EVENTS) return BAILEYS_EVENT_NAMES.includes(eventName);
739
+ return BAILEYS_EVENT_JOURNAL_EVENTS.has(eventName);
740
+ };
741
+
742
+ /**
743
+ * Retorna a primeira string não vazia de uma lista de valores.
744
+ * @param {...(string | null | undefined)} values - Os valores a serem verificados.
745
+ * @returns {string | null} A primeira string não vazia encontrada ou null.
746
+ */
747
+ const takeFirstString = (...values) => {
748
+ for (const value of values) {
749
+ if (typeof value !== 'string') continue;
750
+ const normalized = value.trim();
751
+ if (normalized) return normalized;
752
+ }
753
+ return null;
754
+ };
755
+
756
+ /**
757
+ * Extrai referências (chatId, messageId, participantId) de um payload de evento do Baileys.
758
+ * @param {any} payload - O payload do evento do Baileys.
759
+ * @returns {{chatId: string | null, messageId: string | null, participantId: string | null}} Objeto contendo as referências extraídas.
760
+ */
761
+ const extractBaileysEventReferences = (payload) => {
762
+ const refs = {
763
+ chatId: null,
764
+ messageId: null,
765
+ participantId: null,
766
+ };
767
+
768
+ const assignChat = (...values) => {
769
+ if (refs.chatId) return;
770
+ refs.chatId = takeFirstString(...values);
771
+ };
772
+
773
+ const assignMessage = (...values) => {
774
+ if (refs.messageId) return;
775
+ refs.messageId = takeFirstString(...values);
776
+ };
777
+
778
+ const assignParticipant = (...values) => {
779
+ if (refs.participantId) return;
780
+ refs.participantId = takeFirstString(...values);
781
+ };
782
+
783
+ const applyFromKey = (key) => {
784
+ if (!key || typeof key !== 'object') return;
785
+ assignChat(key.remoteJid, key.remoteJidAlt);
786
+ assignMessage(key.id);
787
+ assignParticipant(key.participant, key.participantAlt);
788
+ };
789
+
790
+ const applyFromObject = (value) => {
791
+ if (!value || typeof value !== 'object') return;
792
+
793
+ applyFromKey(value.key);
794
+ applyFromKey(value.msg?.key);
795
+ applyFromKey(value.reaction?.key);
796
+ applyFromKey(value.reactionMessage?.key);
797
+ applyFromKey(value.reactedKey);
798
+
799
+ assignChat(value.id, value.groupId, value.jid, value.chatId);
800
+ assignMessage(value.messageId, value.msgId, value.server_id);
801
+ assignParticipant(value.participant, value.user, value.lid, value.pn);
802
+
803
+ if (Array.isArray(value.participants) && value.participants.length > 0) {
804
+ assignParticipant(value.participants[0]);
805
+ }
806
+ if (Array.isArray(value.keys) && value.keys.length > 0) {
807
+ applyFromKey(value.keys[0]);
808
+ }
809
+ if (Array.isArray(value.messages) && value.messages.length > 0) {
810
+ applyFromObject(value.messages[0]);
811
+ }
812
+ };
813
+
814
+ if (Array.isArray(payload)) {
815
+ for (const item of payload) {
816
+ applyFromObject(item);
817
+ if (refs.chatId && refs.messageId && refs.participantId) break;
818
+ }
819
+ } else {
820
+ applyFromObject(payload);
821
+ }
822
+
823
+ return refs;
824
+ };
825
+
826
+ /**
827
+ * Resolve IDs de contato (LID/JID) a partir de payloads de contacts.upsert/contacts.update.
828
+ * @param {any} contact
829
+ * @returns {{lid: string | null, jid: string | null}}
830
+ */
831
+ const resolveContactIdsForLidMap = (contact) => {
832
+ const lidCandidates = [contact?.lid, contact?.id, contact?.jid];
833
+ let lid = null;
834
+ for (const candidate of lidCandidates) {
835
+ if (isLidUserId(candidate)) {
836
+ lid = candidate;
837
+ break;
838
+ }
839
+ }
840
+
841
+ const jidCandidates = [contact?.jid, contact?.phoneNumber, contact?.id];
842
+ let jid = null;
843
+ for (const candidate of jidCandidates) {
844
+ if (isWhatsAppUserId(candidate)) {
845
+ jid = candidate;
846
+ break;
847
+ }
848
+ const normalizedPn = normalizePnToJid(candidate);
849
+ if (normalizedPn && isWhatsAppUserId(normalizedPn)) {
850
+ jid = normalizedPn;
851
+ break;
852
+ }
853
+ }
854
+
855
+ return { lid, jid };
856
+ };
857
+
858
+ /**
859
+ * Enfileira atualizações do lid_map a partir de eventos de contato.
860
+ * @param {Array<any>} contacts
861
+ * @param {string} source
862
+ * @returns {void}
863
+ */
864
+ const queueContactsLidUpdates = (contacts, source) => {
865
+ if (!Array.isArray(contacts) || contacts.length === 0) return;
866
+
867
+ for (const contact of contacts) {
868
+ try {
869
+ const { lid, jid } = resolveContactIdsForLidMap(contact);
870
+ if (!lid) continue;
871
+ queueLidUpdate(lid, jid, source);
872
+ } catch (error) {
873
+ logger.warn('Falha ao processar evento de contatos para lid_map.', {
874
+ source,
875
+ error: error.message,
876
+ });
877
+ }
878
+ }
879
+ };
880
+
881
+ /**
882
+ * Registra o mecanismo de diário (journal) para os eventos do Baileys.
883
+ * Eventos selecionados são enfileirados para persistência.
884
+ * @param {import('@whiskeysockets/baileys').WASocket} sock - A instância do socket do Baileys.
885
+ * @param {number} generation - A geração atual do socket.
886
+ * @returns {void}
887
+ */
888
+ const registerBaileysEventJournal = (sock, generation) => {
889
+ if (!BAILEYS_EVENT_JOURNAL_ENABLED) {
890
+ logger.debug('Journal de eventos Baileys desativado por configuração.', {
891
+ action: 'baileys_event_journal_disabled',
892
+ });
893
+ return;
894
+ }
895
+
896
+ const unknownEvents = BAILEYS_EVENT_JOURNAL_EVENT_LIST.filter((eventName) => eventName !== '*' && !BAILEYS_EVENT_NAMES.includes(eventName));
897
+ if (unknownEvents.length > 0) {
898
+ logger.warn('Alguns eventos configurados para journal não existem na lista conhecida do Baileys.', {
899
+ action: 'baileys_event_journal_unknown_events',
900
+ unknownEvents,
901
+ });
902
+ }
903
+
904
+ const eventsToPersist = BAILEYS_EVENT_NAMES.filter((eventName) => shouldPersistBaileysEvent(eventName));
905
+ if (eventsToPersist.length === 0) {
906
+ logger.warn('Journal de eventos Baileys habilitado sem eventos válidos para persistir.', {
907
+ action: 'baileys_event_journal_empty',
908
+ configuredEvents: BAILEYS_EVENT_JOURNAL_EVENT_LIST,
909
+ });
910
+ return;
911
+ }
912
+
913
+ for (const eventName of eventsToPersist) {
914
+ sock.ev.on(eventName, (payload) => {
915
+ try {
916
+ const summary = summarizeBaileysEventPayload(eventName, payload);
917
+ const refs = extractBaileysEventReferences(payload);
918
+ queueBaileysEventInsert({
919
+ event_name: eventName,
920
+ socket_generation: generation,
921
+ chat_id: refs.chatId,
922
+ message_id: refs.messageId,
923
+ participant_id: refs.participantId,
924
+ payload_summary: summary,
925
+ event_timestamp: new Date(),
926
+ });
927
+ } catch (error) {
928
+ logger.warn('Falha ao enfileirar evento Baileys para journal.', {
929
+ action: 'baileys_event_journal_enqueue_failed',
930
+ eventName,
931
+ error: error?.message,
932
+ });
933
+ }
934
+ });
935
+ }
936
+
937
+ logger.info('Journal de eventos Baileys habilitado.', {
938
+ action: 'baileys_event_journal_ready',
939
+ generation,
940
+ eventsCount: eventsToPersist.length,
941
+ events: eventsToPersist,
942
+ });
943
+ };
944
+
945
+ /**
946
+ * Faz parse seguro de JSON com suporte a Buffer e retorna fallback em caso de erro.
947
+ * @param {unknown} value - Valor a ser interpretado.
948
+ * @param {any} fallback - Valor retornado quando o parse falha ou o valor é inválido.
949
+ * @returns {any} Objeto parseado ou fallback.
950
+ */
951
+ const safeJsonParse = (value, fallback) => {
952
+ if (value === null || value === undefined) return fallback;
953
+ if (Buffer.isBuffer(value)) {
954
+ return safeJsonParse(value.toString('utf8'), fallback);
955
+ }
956
+ if (typeof value === 'object') return value;
957
+ if (typeof value !== 'string') return fallback;
958
+ try {
959
+ return JSON.parse(value);
960
+ } catch (error) {
961
+ logger.warn('Falha ao fazer parse de JSON armazenado.', {
962
+ error: error.message,
963
+ });
964
+ return fallback;
965
+ }
966
+ };
967
+
968
+ /**
969
+ * Normaliza PN para JID de WhatsApp quando o payload vier sem domínio.
970
+ * @param {string|null|undefined} pn
971
+ * @returns {string|null}
972
+ */
973
+ /**
974
+ * Persiste mensagens recebidas quando o tipo do upsert permite salvamento.
975
+ * @async
976
+ * @param {Array<import('@whiskeysockets/baileys').WAMessage>} incomingMessages - Mensagens recebidas.
977
+ * @param {'append' | 'notify' | string} type - Tipo do evento de upsert.
978
+ * @returns {Promise<void>} Conclusão da persistência.
979
+ */
980
+ async function persistIncomingMessages(incomingMessages, type) {
981
+ if (type !== 'append' && type !== 'notify') return;
982
+
983
+ const entries = [];
984
+ const lidsToPrime = new Set();
985
+
986
+ for (const msg of incomingMessages) {
987
+ if (!msg.message || msg.key.remoteJid === 'status@broadcast') continue;
988
+ const resolvedAddressingMode = resolveAddressingModeFromMessageKey(msg?.key);
989
+ const normalizedMsg =
990
+ resolvedAddressingMode && msg?.key && msg.key.addressingMode !== resolvedAddressingMode
991
+ ? {
992
+ ...msg,
993
+ key: {
994
+ ...msg.key,
995
+ addressingMode: resolvedAddressingMode,
996
+ },
997
+ }
998
+ : msg;
999
+
1000
+ const senderInfo = extractSenderInfoFromMessage(normalizedMsg);
1001
+ if (senderInfo.lid) lidsToPrime.add(senderInfo.lid);
1002
+ entries.push({ msg: normalizedMsg, senderInfo });
1003
+ }
1004
+
1005
+ if (lidsToPrime.size > 0) {
1006
+ try {
1007
+ await primeLidCache(Array.from(lidsToPrime));
1008
+ } catch (error) {
1009
+ logger.warn('Falha ao aquecer cache de LID.', { error: error.message });
1010
+ }
1011
+ }
1012
+
1013
+ for (const { msg, senderInfo } of entries) {
1014
+ if (senderInfo.lid) {
1015
+ queueLidUpdate(senderInfo.lid, senderInfo.jid, 'message');
1016
+ }
1017
+
1018
+ const canonicalSenderId = resolveUserIdCached(senderInfo) || msg.key.participant || msg.key.remoteJid;
1019
+
1020
+ const messageData = buildMessageData(msg, canonicalSenderId);
1021
+ queueMessageInsert(messageData);
1022
+ }
1023
+ }
1024
+
1025
+ /**
1026
+ * Recupera mensagem armazenada para suporte a recursos (ex.: enquetes) do Baileys.
1027
+ * @async
1028
+ * @param {import('@whiskeysockets/baileys').WAMessageKey} key - Chave da mensagem.
1029
+ * @returns {Promise<import('@whiskeysockets/baileys').proto.IMessage | undefined>} Conteúdo da mensagem armazenada.
1030
+ */
1031
+ async function getStoredMessage(key) {
1032
+ const messageId = key?.id;
1033
+ const remoteJid = key?.remoteJid;
1034
+ if (!messageId || !remoteJid) return undefined;
1035
+
1036
+ try {
1037
+ const results = await findBy('messages', { message_id: messageId, chat_id: remoteJid }, { limit: 1 });
1038
+ const record = results?.[0];
1039
+ const stored = safeJsonParse(record?.raw_message, null);
1040
+ if (record?.raw_message && !stored) {
1041
+ logger.error('Falha ao interpretar raw_message armazenado.', {
1042
+ messageId,
1043
+ remoteJid,
1044
+ });
1045
+ }
1046
+ return stored?.message ?? undefined;
1047
+ } catch (error) {
1048
+ logger.error('Erro ao buscar mensagem armazenada no banco:', {
1049
+ error: error.message,
1050
+ messageId,
1051
+ remoteJid,
1052
+ });
1053
+ return undefined;
1054
+ }
1055
+ }
1056
+
1057
+ /**
1058
+ * Limpa o timeout de reconexão agendado, se houver.
1059
+ * @returns {void}
1060
+ */
1061
+ const clearReconnectTimeout = () => {
1062
+ if (!reconnectTimeout) return;
1063
+ reconnectTimeout.cancel();
1064
+ reconnectTimeout = null;
1065
+ };
1066
+
1067
+ /**
1068
+ * Reseta o estado das tentativas de reconexão.
1069
+ * @returns {void}
1070
+ */
1071
+ const resetReconnectState = () => {
1072
+ connectionAttempts = 0;
1073
+ reconnectWindowStartedAt = 0;
1074
+ };
1075
+
1076
+ /**
1077
+ * Calcula o número da próxima tentativa de reconexão.
1078
+ * Reseta a contagem de tentativas se a janela de reconexão expirou.
1079
+ * @returns {number} O número da próxima tentativa.
1080
+ */
1081
+ const getNextReconnectAttempt = () => {
1082
+ const now = Date.now();
1083
+ if (!reconnectWindowStartedAt || now - reconnectWindowStartedAt >= BAILEYS_RECONNECT_ATTEMPT_RESET_MS) {
1084
+ reconnectWindowStartedAt = now;
1085
+ connectionAttempts = 0;
1086
+ }
1087
+ connectionAttempts += 1;
1088
+ return connectionAttempts;
1089
+ };
1090
+
1091
+ /**
1092
+ * Agenda uma reconexão com o WhatsApp após um determinado atraso.
1093
+ * Evita agendar múltiplas reconexões.
1094
+ * @param {number} delay - O atraso em milissegundos antes de tentar a reconexão.
1095
+ * @returns {void}
1096
+ */
1097
+ const scheduleReconnect = (delay) => {
1098
+ if (reconnectTimeout) return;
1099
+ const pendingReconnect = delayCancellable(Math.max(0, Number(delay) || 0));
1100
+ reconnectTimeout = pendingReconnect;
1101
+ pendingReconnect.delay
1102
+ .then(() => {
1103
+ if (reconnectTimeout !== pendingReconnect) return;
1104
+ reconnectTimeout = null;
1105
+ connectToWhatsApp().catch((error) => {
1106
+ logger.error('Falha ao executar reconexão agendada.', {
1107
+ action: 'reconnect_schedule_failure',
1108
+ errorMessage: error?.message,
1109
+ stack: error?.stack,
1110
+ timestamp: new Date().toISOString(),
1111
+ });
1112
+ });
1113
+ })
1114
+ .catch((error) => {
1115
+ if (reconnectTimeout === pendingReconnect) {
1116
+ reconnectTimeout = null;
1117
+ }
1118
+ if (
1119
+ String(error?.message || '')
1120
+ .trim()
1121
+ .toLowerCase() === 'cancelled'
1122
+ ) {
1123
+ return;
1124
+ }
1125
+ logger.warn('Falha ao aguardar atraso da reconexão agendada.', {
1126
+ action: 'reconnect_schedule_delay_error',
1127
+ errorMessage: error?.message,
1128
+ });
1129
+ });
1130
+ };
1131
+
1132
+ /**
1133
+ * Libera lock de escritor único do Baileys, se estiver ativo.
1134
+ * @param {string} reason
1135
+ * @returns {Promise<void>}
1136
+ */
1137
+ const releaseBaileysWriterLock = async (reason = 'unknown') => {
1138
+ const connection = baileysWriterLockConnection;
1139
+ if (!connection) return;
1140
+
1141
+ baileysWriterLockConnection = null;
1142
+
1143
+ try {
1144
+ const rows = await executeQuery('SELECT RELEASE_LOCK(?) AS released', [BAILEYS_SINGLE_WRITER_LOCK_NAME], connection);
1145
+ const released = Number(rows?.[0]?.released) === 1;
1146
+ logger.info('Lock de escritor do Baileys liberado.', {
1147
+ action: 'baileys_writer_lock_released',
1148
+ reason,
1149
+ released,
1150
+ lockName: BAILEYS_SINGLE_WRITER_LOCK_NAME,
1151
+ timestamp: new Date().toISOString(),
1152
+ });
1153
+ } catch (error) {
1154
+ logger.warn('Falha ao liberar lock de escritor do Baileys.', {
1155
+ action: 'baileys_writer_lock_release_error',
1156
+ reason,
1157
+ lockName: BAILEYS_SINGLE_WRITER_LOCK_NAME,
1158
+ errorMessage: error?.message,
1159
+ timestamp: new Date().toISOString(),
1160
+ });
1161
+ } finally {
1162
+ try {
1163
+ connection.release();
1164
+ } catch (error) {
1165
+ logger.debug('Conexao dedicada do lock ja estava encerrada.', {
1166
+ action: 'baileys_writer_lock_connection_already_closed',
1167
+ errorMessage: error?.message,
1168
+ });
1169
+ }
1170
+ }
1171
+ };
1172
+
1173
+ /**
1174
+ * Garante lock de escritor único para a sessão do Baileys.
1175
+ * @returns {Promise<boolean>}
1176
+ */
1177
+ const ensureBaileysWriterLock = async () => {
1178
+ if (!BAILEYS_SINGLE_WRITER_LOCK_ENABLED) {
1179
+ return true;
1180
+ }
1181
+
1182
+ if (baileysWriterLockConnection) {
1183
+ return true;
1184
+ }
1185
+
1186
+ const connection = await pool.getConnection();
1187
+
1188
+ try {
1189
+ const rows = await executeQuery('SELECT GET_LOCK(?, ?) AS lock_status', [BAILEYS_SINGLE_WRITER_LOCK_NAME, BAILEYS_SINGLE_WRITER_LOCK_TIMEOUT_SECONDS], connection);
1190
+ const lockStatus = Number(rows?.[0]?.lock_status);
1191
+ if (lockStatus !== 1) {
1192
+ connection.release();
1193
+ logger.warn('Nao foi possivel adquirir lock de escritor do Baileys nesta tentativa.', {
1194
+ action: 'baileys_writer_lock_busy',
1195
+ lockName: BAILEYS_SINGLE_WRITER_LOCK_NAME,
1196
+ timeoutSeconds: BAILEYS_SINGLE_WRITER_LOCK_TIMEOUT_SECONDS,
1197
+ status: Number.isFinite(lockStatus) ? lockStatus : null,
1198
+ retryAfterMs: BAILEYS_SINGLE_WRITER_LOCK_RETRY_DELAY_MS,
1199
+ timestamp: new Date().toISOString(),
1200
+ });
1201
+ return false;
1202
+ }
1203
+
1204
+ baileysWriterLockConnection = connection;
1205
+ logger.info('Lock de escritor do Baileys adquirido com sucesso.', {
1206
+ action: 'baileys_writer_lock_acquired',
1207
+ lockName: BAILEYS_SINGLE_WRITER_LOCK_NAME,
1208
+ timeoutSeconds: BAILEYS_SINGLE_WRITER_LOCK_TIMEOUT_SECONDS,
1209
+ timestamp: new Date().toISOString(),
1210
+ });
1211
+ return true;
1212
+ } catch (error) {
1213
+ try {
1214
+ connection.release();
1215
+ } catch {
1216
+ // Ignora falhas ao liberar conexão após erro de aquisição.
1217
+ }
1218
+ throw error;
1219
+ }
1220
+ };
1221
+
1222
+ process.once('beforeExit', () => {
1223
+ releaseBaileysWriterLock('before_exit').catch(() => {});
1224
+ });
1225
+
1226
+ process.once('SIGINT', () => {
1227
+ releaseBaileysWriterLock('sigint').catch(() => {});
1228
+ });
1229
+
1230
+ process.once('SIGTERM', () => {
1231
+ releaseBaileysWriterLock('sigterm').catch(() => {});
1232
+ });
1233
+
1234
+ /**
1235
+ * Envolve uma promessa com um timeout. Se a promessa não resolver dentro do tempo limite, ela é rejeitada.
1236
+ * @template T
1237
+ * @param {Promise<T>} promise - A promessa a ser envolvida.
1238
+ * @param {number} timeoutMs - O tempo limite em milissegundos.
1239
+ * @param {string} [timeoutLabel='operation_timeout'] - Rótulo para o erro de timeout.
1240
+ * @returns {Promise<T>} A promessa envolvida com timeout.
1241
+ */
1242
+ const withTimeout = (promise, timeoutMs, timeoutLabel = 'operation_timeout') =>
1243
+ promiseTimeout(timeoutMs, (resolve, reject) => {
1244
+ promise.then(resolve).catch(reject);
1245
+ }).catch((error) => {
1246
+ if (
1247
+ String(error?.message || '')
1248
+ .trim()
1249
+ .toLowerCase() === 'timed out'
1250
+ ) {
1251
+ throw new Error(timeoutLabel);
1252
+ }
1253
+ throw error;
1254
+ });
1255
+
1256
+ /**
1257
+ * Sincroniza metadados de grupos ao abrir a conexão com o WhatsApp.
1258
+ * Busca todos os grupos participantes e os atualiza no banco de dados.
1259
+ * @async
1260
+ * @param {import('@whiskeysockets/baileys').WASocket} sock - A instância do socket do Baileys.
1261
+ * @returns {Promise<void>}
1262
+ */
1263
+ const syncGroupsOnConnectionOpen = async (sock) => {
1264
+ if (!GROUP_SYNC_ON_CONNECT) {
1265
+ logger.info('Sincronização de grupos no connect desativada por configuração.', {
1266
+ action: 'groups_sync_disabled',
1267
+ timestamp: new Date().toISOString(),
1268
+ });
1269
+ return;
1270
+ }
1271
+
1272
+ const allGroups = await withTimeout(sock.groupFetchAllParticipating(), GROUP_SYNC_TIMEOUT_MS, `groups_sync_timeout_${GROUP_SYNC_TIMEOUT_MS}ms`);
1273
+ const allGroupEntries = Object.values(allGroups || {});
1274
+ const selectedGroups = GROUP_SYNC_MAX_GROUPS > 0 ? allGroupEntries.slice(0, GROUP_SYNC_MAX_GROUPS) : allGroupEntries;
1275
+
1276
+ let syncedCount = 0;
1277
+ let failedCount = 0;
1278
+
1279
+ for (let offset = 0; offset < selectedGroups.length; offset += GROUP_SYNC_BATCH_SIZE) {
1280
+ const batch = selectedGroups.slice(offset, offset + GROUP_SYNC_BATCH_SIZE);
1281
+ const results = await Promise.allSettled(
1282
+ batch.map((group) =>
1283
+ upsertGroupMetadata(group.id, buildGroupMetadataFromGroup(group), {
1284
+ mergeExisting: false,
1285
+ }).then((result) => {
1286
+ invalidateCachedGroupMetadata(group.id);
1287
+ return result;
1288
+ }),
1289
+ ),
1290
+ );
1291
+ for (const result of results) {
1292
+ if (result.status === 'fulfilled') syncedCount += 1;
1293
+ else failedCount += 1;
1294
+ }
1295
+ }
1296
+
1297
+ logger.info('📁 Metadados de grupos sincronizados com MySQL.', {
1298
+ action: 'groups_synced',
1299
+ totalFetched: allGroupEntries.length,
1300
+ totalSynced: syncedCount,
1301
+ totalFailed: failedCount,
1302
+ totalSkipped: Math.max(0, allGroupEntries.length - selectedGroups.length),
1303
+ batchSize: GROUP_SYNC_BATCH_SIZE,
1304
+ maxGroups: GROUP_SYNC_MAX_GROUPS > 0 ? GROUP_SYNC_MAX_GROUPS : null,
1305
+ timeoutMs: GROUP_SYNC_TIMEOUT_MS,
1306
+ timestamp: new Date().toISOString(),
1307
+ });
1308
+ };
1309
+
1310
+ /**
1311
+ * Inicia e gerencia a conexão com o WhatsApp usando o Baileys.
1312
+ * Configura autenticação, cria o socket e registra handlers de eventos.
1313
+ * Gerencia a lógica de reconexão e a distribuição de eventos.
1314
+ * @async
1315
+ * @returns {Promise<void>} Conclusão da inicialização e do registro de handlers.
1316
+ * @throws {Error} Lança erro se a conexão inicial falhar.
1317
+ */
1318
+ export async function connectToWhatsApp() {
1319
+ if (connectPromise) {
1320
+ return connectPromise;
1321
+ }
1322
+
1323
+ if (isSocketOpen(activeSocket)) {
1324
+ return;
1325
+ }
1326
+
1327
+ logger.info('Iniciando conexão com o WhatsApp...', {
1328
+ action: 'connect_init',
1329
+ timestamp: new Date().toISOString(),
1330
+ });
1331
+ connectPromise = (async () => {
1332
+ clearReconnectTimeout();
1333
+ const isWriterReady = await ensureBaileysWriterLock();
1334
+ if (!isWriterReady) {
1335
+ scheduleReconnect(BAILEYS_SINGLE_WRITER_LOCK_RETRY_DELAY_MS);
1336
+ return;
1337
+ }
1338
+
1339
+ const generation = ++socketGeneration;
1340
+ const legacyAuthPath = path.join(__dirname, 'auth');
1341
+ const { state, saveCreds } = await useDbAuthState({
1342
+ sessionId: BAILEYS_AUTH_SESSION_ID,
1343
+ bootstrapFromDir: legacyAuthPath,
1344
+ bootstrapFromFiles: BAILEYS_AUTH_BOOTSTRAP_FROM_FILES,
1345
+ });
1346
+
1347
+ const version = await resolveBaileysVersion();
1348
+
1349
+ logger.debug('Dados de autenticação carregados com sucesso.', {
1350
+ authSessionId: BAILEYS_AUTH_SESSION_ID,
1351
+ bootstrappedFromFiles: BAILEYS_AUTH_BOOTSTRAP_FROM_FILES,
1352
+ version,
1353
+ generation,
1354
+ });
1355
+
1356
+ /** @type {import('@whiskeysockets/baileys').UserFacingSocketConfig} */
1357
+ const socketConfig = {
1358
+ version,
1359
+ auth: state,
1360
+ logger: baileysSocketLogger,
1361
+ browser: Browsers.macOS('Desktop'),
1362
+ qrTimeout: 30000,
1363
+ syncFullHistory: false,
1364
+ markOnlineOnConnect: true,
1365
+ msgRetryCounterCache,
1366
+ maxMsgRetryCount: 5,
1367
+ retryRequestDelayMs: 250,
1368
+ getMessage: getStoredMessage,
1369
+ userDevicesCache,
1370
+ mediaCache,
1371
+ cachedGroupMetadata: resolveCachedGroupMetadata,
1372
+ patchMessageBeforeSending,
1373
+ enableAutoSessionRecreation: BAILEYS_ENABLE_AUTO_SESSION_RECREATION,
1374
+ enableRecentMessageCache: BAILEYS_ENABLE_RECENT_MESSAGE_CACHE,
1375
+ generateHighQualityLinkPreview: BAILEYS_GENERATE_HIGH_QUALITY_LINK_PREVIEW,
1376
+ };
1377
+
1378
+ const sock = makeWASocket(socketConfig);
1379
+
1380
+ activeSocket = sock;
1381
+ storeActiveSocket(sock);
1382
+
1383
+ const isCurrentSocket = () => activeSocket === sock && generation === socketGeneration;
1384
+
1385
+ sock.ev.on('creds.update', async () => {
1386
+ if (!isCurrentSocket()) return;
1387
+ logger.debug('Atualizando credenciais de autenticação...', {
1388
+ action: 'creds_update',
1389
+ timestamp: new Date().toISOString(),
1390
+ });
1391
+ await saveCreds();
1392
+ });
1393
+
1394
+ sock.ev.on('connection.update', (update) => {
1395
+ if (!isCurrentSocket()) return;
1396
+ handleConnectionUpdate(update, sock);
1397
+ if (update.connection === 'open') {
1398
+ syncNewsBroadcastService();
1399
+ }
1400
+ logger.debug('Estado da conexão atualizado.', {
1401
+ action: 'connection_update',
1402
+ status: update.connection,
1403
+ lastDisconnect: update.lastDisconnect?.error?.message || null,
1404
+ isNewLogin: update.isNewLogin || false,
1405
+ timestamp: new Date().toISOString(),
1406
+ });
1407
+ });
1408
+
1409
+ sock.ev.on('messages.upsert', (update) => {
1410
+ if (!isCurrentSocket()) return;
1411
+ const start = process.hrtime.bigint();
1412
+ const messagesCount = Array.isArray(update?.messages) ? update.messages.length : 0;
1413
+ const eventType = update?.type || 'unknown';
1414
+ try {
1415
+ logger.debug('Novo(s) evento(s) em messages.upsert', {
1416
+ action: 'messages_upsert',
1417
+ type: update.type,
1418
+ messagesCount: update.messages.length,
1419
+ remoteJid: update.messages[0]?.key.remoteJid || null,
1420
+ });
1421
+ const persistPromise = persistIncomingMessages(update.messages, update.type).catch((error) => {
1422
+ logger.error('Erro ao persistir mensagens no banco de dados:', {
1423
+ error: error.message,
1424
+ });
1425
+ recordError('messages_upsert');
1426
+ });
1427
+ const handlePromise = handleMessages(update, sock).catch((error) => {
1428
+ recordError('messages_upsert');
1429
+ throw error;
1430
+ });
1431
+
1432
+ Promise.allSettled([persistPromise, handlePromise]).then((results) => {
1433
+ const ok = results.every((result) => result.status === 'fulfilled');
1434
+ const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
1435
+ recordMessagesUpsert({
1436
+ durationMs,
1437
+ type: eventType,
1438
+ messagesCount,
1439
+ ok,
1440
+ });
1441
+ });
1442
+ } catch (error) {
1443
+ logger.error('Erro no evento messages.upsert:', {
1444
+ error: error.message,
1445
+ stack: error.stack,
1446
+ action: 'messages_upsert_error',
1447
+ });
1448
+ recordError('messages_upsert');
1449
+ const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
1450
+ recordMessagesUpsert({
1451
+ durationMs,
1452
+ type: eventType,
1453
+ messagesCount,
1454
+ ok: false,
1455
+ });
1456
+ }
1457
+ });
1458
+
1459
+ sock.ev.on('chats.upsert', (newChats) => {
1460
+ if (!isCurrentSocket()) return;
1461
+ for (const chat of newChats) {
1462
+ queueChatUpdate(chat, { partial: false, forceName: true });
1463
+ }
1464
+ });
1465
+
1466
+ sock.ev.on('chats.update', (updates) => {
1467
+ if (!isCurrentSocket()) return;
1468
+ for (const update of updates) {
1469
+ queueChatUpdate(update, { partial: true });
1470
+ }
1471
+ });
1472
+
1473
+ sock.ev.on('chats.delete', (deletions) => {
1474
+ if (!isCurrentSocket()) return;
1475
+ for (const chatId of deletions) {
1476
+ remove('chats', chatId).catch((error) => {
1477
+ logger.error('Erro ao remover chat do banco:', {
1478
+ error: error.message,
1479
+ chatId,
1480
+ });
1481
+ });
1482
+ }
1483
+ });
1484
+
1485
+ sock.ev.on('groups.upsert', async (newGroups) => {
1486
+ if (!isCurrentSocket()) return;
1487
+ for (const group of newGroups) {
1488
+ try {
1489
+ await upsertGroupMetadata(group.id, buildGroupMetadataFromGroup(group), {
1490
+ mergeExisting: false,
1491
+ });
1492
+ invalidateCachedGroupMetadata(group.id);
1493
+ } catch (error) {
1494
+ logger.error('Erro no upsert do grupo:', {
1495
+ error: error.message,
1496
+ groupId: group.id,
1497
+ });
1498
+ }
1499
+ }
1500
+ });
1501
+
1502
+ sock.ev.on('contacts.upsert', (contacts) => {
1503
+ if (!isCurrentSocket()) return;
1504
+ queueContactsLidUpdates(contacts, 'contacts.upsert');
1505
+ });
1506
+
1507
+ sock.ev.on('contacts.update', (updates) => {
1508
+ if (!isCurrentSocket()) return;
1509
+ queueContactsLidUpdates(updates, 'contacts.update');
1510
+ });
1511
+
1512
+ sock.ev.on('lid-mapping.update', (update) => {
1513
+ if (!isCurrentSocket()) return;
1514
+ try {
1515
+ const lid = typeof update?.lid === 'string' ? update.lid : null;
1516
+ const pnJid = normalizePnToJid(update?.pn);
1517
+ if (!lid || !pnJid) return;
1518
+ queueLidUpdate(lid, pnJid, 'lid-mapping');
1519
+ } catch (error) {
1520
+ logger.warn('Falha ao processar lid-mapping.update para lid_map.', {
1521
+ error: error.message,
1522
+ });
1523
+ }
1524
+ });
1525
+
1526
+ sock.ev.on('messages.update', (update) => {
1527
+ if (!isCurrentSocket()) return;
1528
+ try {
1529
+ logger.debug('Atualização de mensagens recebida.', {
1530
+ action: 'messages_update',
1531
+ updatesCount: update.length,
1532
+ });
1533
+ handleMessageUpdate(update, sock);
1534
+ } catch (error) {
1535
+ logger.error('Erro no evento messages.update:', {
1536
+ error: error.message,
1537
+ stack: error.stack,
1538
+ action: 'messages_update_error',
1539
+ });
1540
+ }
1541
+ });
1542
+
1543
+ sock.ev.on('messages.media-update', (updates) => {
1544
+ if (!isCurrentSocket()) return;
1545
+ if (!Array.isArray(updates)) return;
1546
+
1547
+ const erroredUpdates = updates.filter((entry) => entry?.error);
1548
+ if (erroredUpdates.length > 0) {
1549
+ const firstError = erroredUpdates[0]?.error;
1550
+ logger.warn('Falha reportada em atualização de mídia.', {
1551
+ action: 'messages_media_update_error',
1552
+ updatesCount: updates.length,
1553
+ errorCount: erroredUpdates.length,
1554
+ firstMessageId: erroredUpdates[0]?.key?.id || null,
1555
+ firstRemoteJid: erroredUpdates[0]?.key?.remoteJid || null,
1556
+ firstErrorMessage: firstError?.message || null,
1557
+ });
1558
+ return;
1559
+ }
1560
+
1561
+ logger.debug('Atualização de mídia de mensagem recebida.', {
1562
+ action: 'messages_media_update',
1563
+ updatesCount: updates.length,
1564
+ });
1565
+ });
1566
+
1567
+ sock.ev.on('message-receipt.update', (updates) => {
1568
+ if (!isCurrentSocket()) return;
1569
+ if (!Array.isArray(updates) || updates.length === 0) return;
1570
+
1571
+ const receiptTypes = new Set();
1572
+ let invalidReceiptTypeCount = 0;
1573
+ for (const update of updates) {
1574
+ const receiptType = normalizeMessageReceiptType(update?.receipt?.type);
1575
+ if (receiptType) {
1576
+ receiptTypes.add(receiptType);
1577
+ } else if (update?.receipt?.type !== undefined) {
1578
+ invalidReceiptTypeCount += 1;
1579
+ }
1580
+ }
1581
+
1582
+ logger.debug('Atualização de recibos de mensagem recebida.', {
1583
+ action: 'message_receipt_update',
1584
+ updatesCount: updates.length,
1585
+ receiptTypes: Array.from(receiptTypes),
1586
+ invalidReceiptTypeCount,
1587
+ sampleMessageId: updates[0]?.key?.id || null,
1588
+ sampleRemoteJid: updates[0]?.key?.remoteJid || null,
1589
+ });
1590
+ });
1591
+
1592
+ sock.ev.on('messages.reaction', async (updates) => {
1593
+ if (!isCurrentSocket()) return;
1594
+ try {
1595
+ const reactions = Array.isArray(updates) ? updates : [updates];
1596
+ for (const update of reactions) {
1597
+ const key = update?.key || update?.msg?.key || update?.reaction?.key || null;
1598
+ const reaction = update?.reaction || update?.msg?.reaction || update?.reactionMessage || null;
1599
+ const reactedKey = reaction?.key || update?.reactedKey || update?.reactionMessage?.key || null;
1600
+
1601
+ const groupId = key?.remoteJid || reactedKey?.remoteJid || null;
1602
+ const senderJid = key?.participant || update?.participant || reaction?.sender || null;
1603
+ const senderIdentity = {
1604
+ participant: key?.participant || update?.participant || reaction?.sender || null,
1605
+ participantAlt: key?.participantAlt || update?.participantAlt || reaction?.participantAlt || reaction?.key?.participantAlt || null,
1606
+ jid: senderJid,
1607
+ };
1608
+ const reactedMessageId = reactedKey?.id || null;
1609
+ const reactionText = typeof reaction?.text === 'string' ? reaction.text : '';
1610
+
1611
+ if (groupId && (senderJid || senderIdentity.participantAlt)) {
1612
+ await resolveCaptchaByReaction({
1613
+ groupId,
1614
+ senderJid,
1615
+ senderIdentity,
1616
+ reactedMessageId,
1617
+ reactionText,
1618
+ });
1619
+ }
1620
+ }
1621
+ } catch (error) {
1622
+ logger.error('Erro no evento messages.reaction:', {
1623
+ error: error.message,
1624
+ stack: error.stack,
1625
+ action: 'messages_reaction_error',
1626
+ });
1627
+ }
1628
+ });
1629
+
1630
+ sock.ev.on('groups.update', (updates) => {
1631
+ if (!isCurrentSocket()) return;
1632
+ try {
1633
+ logger.debug('Grupo(s) atualizado(s).', {
1634
+ action: 'groups_update',
1635
+ groupCount: updates.length,
1636
+ groupIds: updates.map((u) => u.id),
1637
+ });
1638
+ handleGroupUpdate(updates);
1639
+ } catch (err) {
1640
+ logger.error('Erro no evento groups.update:', {
1641
+ error: err.message,
1642
+ stack: err.stack,
1643
+ action: 'groups_update_error',
1644
+ });
1645
+ }
1646
+ });
1647
+
1648
+ sock.ev.on('group-participants.update', (update) => {
1649
+ if (!isCurrentSocket()) return;
1650
+ try {
1651
+ logger.debug('Participantes do grupo atualizados.', {
1652
+ action: 'group_participants_update',
1653
+ groupId: update.id,
1654
+ actionType: update.action,
1655
+ participants: update.participants,
1656
+ });
1657
+ invalidateCachedGroupMetadata(update.id);
1658
+ handleGroupParticipantsEvent(sock, update.id, update.participants, update.action);
1659
+ } catch (err) {
1660
+ logger.error('Erro no evento group-participants.update:', {
1661
+ error: err.message,
1662
+ stack: err.stack,
1663
+ action: 'group_participants_update_error',
1664
+ });
1665
+ }
1666
+ });
1667
+
1668
+ sock.ev.on('group.join-request', (update) => {
1669
+ if (!isCurrentSocket()) return;
1670
+ try {
1671
+ logger.debug('Solicitação de entrada no grupo recebida.', {
1672
+ action: 'group_join_request',
1673
+ groupId: update?.id,
1674
+ participant: update?.participant,
1675
+ method: update?.method,
1676
+ joinAction: update?.action,
1677
+ });
1678
+ handleGroupJoinRequest(sock, update);
1679
+ } catch (err) {
1680
+ logger.error('Erro no evento group.join-request:', {
1681
+ error: err.message,
1682
+ stack: err.stack,
1683
+ action: 'group_join_request_error',
1684
+ });
1685
+ }
1686
+ });
1687
+
1688
+ sock.ev.on('call', async (calls) => {
1689
+ if (!isCurrentSocket()) return;
1690
+ if (!BAILEYS_AUTO_REJECT_CALLS) return;
1691
+ if (!Array.isArray(calls)) return;
1692
+
1693
+ for (const call of calls) {
1694
+ try {
1695
+ if (!call || call.status !== 'offer') continue;
1696
+ if (!call.id || !call.from) continue;
1697
+
1698
+ const myJid = sock.user?.id || null;
1699
+ if (myJid && areJidsSameUser(call.from, myJid)) {
1700
+ continue;
1701
+ }
1702
+
1703
+ await sock.rejectCall(call.id, call.from);
1704
+ logger.info('Chamada recebida rejeitada automaticamente.', {
1705
+ action: 'call_auto_reject',
1706
+ callId: call.id,
1707
+ from: call.from,
1708
+ isGroup: call.isGroup || false,
1709
+ isVideo: call.isVideo || false,
1710
+ timestamp: new Date().toISOString(),
1711
+ });
1712
+ } catch (error) {
1713
+ logger.warn('Falha ao rejeitar chamada automaticamente.', {
1714
+ action: 'call_auto_reject_failed',
1715
+ callId: call?.id || null,
1716
+ from: call?.from || null,
1717
+ error: error?.message,
1718
+ });
1719
+ }
1720
+ }
1721
+ });
1722
+
1723
+ registerBaileysEventLoggers(sock);
1724
+ registerBaileysEventJournal(sock, generation);
1725
+
1726
+ logger.info('Conexão com o WhatsApp estabelecida com sucesso.', {
1727
+ action: 'connect_success',
1728
+ generation,
1729
+ timestamp: new Date().toISOString(),
1730
+ });
1731
+ })();
1732
+
1733
+ try {
1734
+ await connectPromise;
1735
+ } finally {
1736
+ connectPromise = null;
1737
+ }
1738
+ }
1739
+
1740
+ /**
1741
+ * Gerencia atualizações de estado da conexão com o WhatsApp.
1742
+ * Lida com a exibição de QR code, reconexão automática e ações pós-conexão (como a sincronização de grupos).
1743
+ * @async
1744
+ * @param {import('@whiskeysockets/baileys').ConnectionState} update - Objeto contendo o estado atual da conexão.
1745
+ * @param {import('@whiskeysockets/baileys').WASocket} sock - Instância do socket do WhatsApp que disparou a atualização.
1746
+ * @returns {Promise<void>} Uma promessa que resolve quando o processamento do estado da conexão é concluído.
1747
+ */
1748
+ async function handleConnectionUpdate(update, sock) {
1749
+ if (sock !== activeSocket) return;
1750
+ const { connection, lastDisconnect, qr } = update;
1751
+
1752
+ if (qr) {
1753
+ logger.info('📱 QR Code gerado! Escaneie com seu WhatsApp.', {
1754
+ action: 'qr_code_generated',
1755
+ timestamp: new Date().toISOString(),
1756
+ });
1757
+ qrcode.generate(qr, { small: true });
1758
+ }
1759
+
1760
+ if (connection === 'close') {
1761
+ const disconnectCode = lastDisconnect?.error?.output?.statusCode || 'unknown';
1762
+ const errorMessage = lastDisconnect?.error?.message || 'Sem mensagem de erro';
1763
+
1764
+ const shouldReconnect = lastDisconnect?.error instanceof Boom && disconnectCode !== DisconnectReason.loggedOut;
1765
+
1766
+ if (shouldReconnect) {
1767
+ const attempt = getNextReconnectAttempt();
1768
+ if (attempt <= MAX_CONNECTION_ATTEMPTS) {
1769
+ const reconnectDelay = INITIAL_RECONNECT_DELAY * Math.pow(2, attempt - 1);
1770
+ logger.warn(`⚠️ Conexão perdida. Tentando reconectar...`, {
1771
+ action: 'reconnect_attempt',
1772
+ attempt,
1773
+ maxAttempts: MAX_CONNECTION_ATTEMPTS,
1774
+ delay: reconnectDelay,
1775
+ reasonCode: disconnectCode,
1776
+ errorMessage,
1777
+ timestamp: new Date().toISOString(),
1778
+ });
1779
+ activeSocket = null;
1780
+ storeActiveSocket(null);
1781
+ scheduleReconnect(reconnectDelay);
1782
+ } else {
1783
+ logger.error('❌ Limite de tentativas atingido; aguardando janela para novo retry.', {
1784
+ action: 'reconnect_backoff_window',
1785
+ totalAttempts: attempt,
1786
+ maxAttempts: MAX_CONNECTION_ATTEMPTS,
1787
+ retryAfterMs: BAILEYS_RECONNECT_ATTEMPT_RESET_MS,
1788
+ reasonCode: disconnectCode,
1789
+ errorMessage,
1790
+ timestamp: new Date().toISOString(),
1791
+ });
1792
+ activeSocket = null;
1793
+ storeActiveSocket(null);
1794
+ connectionAttempts = 0;
1795
+ reconnectWindowStartedAt = Date.now();
1796
+ scheduleReconnect(BAILEYS_RECONNECT_ATTEMPT_RESET_MS);
1797
+ }
1798
+ } else {
1799
+ logger.error('❌ Conexão fechada definitivamente.', {
1800
+ action: 'connection_closed',
1801
+ reasonCode: disconnectCode,
1802
+ errorMessage,
1803
+ timestamp: new Date().toISOString(),
1804
+ });
1805
+ activeSocket = null;
1806
+ storeActiveSocket(null);
1807
+ await releaseBaileysWriterLock('connection_closed_no_reconnect');
1808
+ }
1809
+ }
1810
+
1811
+ if (connection === 'open') {
1812
+ logger.info('✅ Conectado com sucesso ao WhatsApp!', {
1813
+ action: 'connection_open',
1814
+ timestamp: new Date().toISOString(),
1815
+ });
1816
+
1817
+ resetReconnectState();
1818
+ clearReconnectTimeout();
1819
+
1820
+ if (process.send) {
1821
+ process.send('ready');
1822
+ logger.info('🟢 Sinal de "ready" enviado ao PM2.', {
1823
+ action: 'pm2_ready_signal',
1824
+ timestamp: new Date().toISOString(),
1825
+ });
1826
+ }
1827
+
1828
+ try {
1829
+ await syncGroupsOnConnectionOpen(sock);
1830
+ } catch (error) {
1831
+ logger.error('❌ Erro ao carregar metadados de grupos na conexão.', {
1832
+ action: 'groups_load_error',
1833
+ errorMessage: error.message,
1834
+ stack: error.stack,
1835
+ timeoutMs: GROUP_SYNC_TIMEOUT_MS,
1836
+ timestamp: new Date().toISOString(),
1837
+ });
1838
+ }
1839
+ }
1840
+ }
1841
+
1842
+ /**
1843
+ * Processa atualizações em mensagens existentes, como votos em enquetes.
1844
+ * @async
1845
+ * @param {Array<import('@whiskeysockets/baileys').WAMessageUpdate>} updates - Array de objetos de atualização de mensagens.
1846
+ * @param {import('@whiskeysockets/baileys').WASocket} sock - Instância do socket do WhatsApp.
1847
+ * @returns {Promise<void>} Uma promessa que resolve quando o processamento das atualizações é concluído.
1848
+ */
1849
+ async function handleMessageUpdate(updates, sock) {
1850
+ for (const { key, update } of updates) {
1851
+ const status = normalizeMessageStatus(update?.status);
1852
+ const stubType = normalizeMessageStubType(update?.messageStubType ?? update?.stubType);
1853
+ const addressingMode = resolveAddressingModeFromMessageKey(key);
1854
+
1855
+ if (status || stubType || addressingMode) {
1856
+ logger.debug('Atualização de estado da mensagem recebida.', {
1857
+ action: 'message_state_update',
1858
+ remoteJid: key?.remoteJid || null,
1859
+ messageId: key?.id || null,
1860
+ participant: key?.participant || null,
1861
+ participantAlt: key?.participantAlt || null,
1862
+ addressingMode: addressingMode || null,
1863
+ statusCode: status?.code ?? null,
1864
+ statusName: status?.name ?? null,
1865
+ stubTypeCode: stubType?.code ?? null,
1866
+ stubTypeName: stubType?.name ?? null,
1867
+ timestamp: new Date().toISOString(),
1868
+ });
1869
+ }
1870
+
1871
+ if (update.pollUpdates) {
1872
+ try {
1873
+ const pollCreation = await sock.getMessage(key);
1874
+
1875
+ if (pollCreation) {
1876
+ const aggregatedVotes = getAggregateVotesInPollMessage({
1877
+ message: pollCreation,
1878
+ pollUpdates: update.pollUpdates,
1879
+ });
1880
+
1881
+ logger.info('📊 Votos da enquete atualizados.', {
1882
+ action: 'poll_votes_updated',
1883
+ remoteJid: key.remoteJid,
1884
+ messageId: key.id,
1885
+ participant: key.participant || null,
1886
+ votesCount: Object.values(aggregatedVotes || {}).reduce((a, b) => a + b, 0),
1887
+ votes: aggregatedVotes,
1888
+ timestamp: new Date().toISOString(),
1889
+ });
1890
+ } else {
1891
+ logger.warn('⚠️ Mensagem da enquete não encontrada.', {
1892
+ action: 'poll_message_not_found',
1893
+ key,
1894
+ timestamp: new Date().toISOString(),
1895
+ });
1896
+ }
1897
+ } catch (error) {
1898
+ logger.error('❌ Erro ao processar atualização de votos da enquete.', {
1899
+ action: 'poll_update_error',
1900
+ errorMessage: error.message,
1901
+ stack: error.stack,
1902
+ key,
1903
+ timestamp: new Date().toISOString(),
1904
+ });
1905
+ }
1906
+ }
1907
+ }
1908
+ }
1909
+
1910
+ /**
1911
+ * Atualiza metadados de grupos no banco MySQL a partir dos eventos do Baileys.
1912
+ * Processa alterações de grupo (título, descrição, proprietário e participantes)
1913
+ * persistindo a versão consolidada no MySQL.
1914
+ * @async
1915
+ * @param {Array<import('@whiskeysockets/baileys').GroupUpdate>} updates - Eventos de atualização de grupos.
1916
+ * @returns {Promise<void>} Conclusão das atualizações em lote.
1917
+ */
1918
+ async function handleGroupUpdate(updates) {
1919
+ await Promise.all(
1920
+ updates.map(async (event) => {
1921
+ try {
1922
+ const groupId = event.id;
1923
+ const oldData = (await findById('groups_metadata', groupId)) || {};
1924
+ const updatedData = buildGroupMetadataFromUpdate(event, oldData);
1925
+
1926
+ await upsertGroupMetadata(groupId, updatedData, { mergeExisting: false });
1927
+ invalidateCachedGroupMetadata(groupId);
1928
+
1929
+ const changedFields = Object.keys(event).filter((k) => event[k] !== oldData[k]);
1930
+ logger.info('📦 Metadados do grupo atualizados', {
1931
+ action: 'group_metadata_updated',
1932
+ groupId,
1933
+ groupName: updatedData.subject || oldData.subject || 'Desconhecido',
1934
+ changedFields,
1935
+ timestamp: new Date().toISOString(),
1936
+ });
1937
+ } catch (error) {
1938
+ logger.error('❌ Erro ao atualizar metadados do grupo', {
1939
+ action: 'group_metadata_update_error',
1940
+ errorMessage: error.message,
1941
+ stack: error.stack,
1942
+ event,
1943
+ timestamp: new Date().toISOString(),
1944
+ });
1945
+ }
1946
+ }),
1947
+ );
1948
+ }
1949
+
1950
+ /**
1951
+ * Retorna a instância atual do socket ativo do WhatsApp.
1952
+ * @returns {import('@whiskeysockets/baileys').WASocket | null} O objeto socket do Baileys ativo ou `null` se não houver conexão ativa.
1953
+ */
1954
+ export function getActiveSocket() {
1955
+ logger.debug('🔍 Recuperando instância do socket ativo.', {
1956
+ action: 'get_active_socket',
1957
+ socketExists: !!activeSocket,
1958
+ timestamp: new Date().toISOString(),
1959
+ });
1960
+ return activeSocket;
1961
+ }
1962
+
1963
+ /**
1964
+ * Executa um método centralizado no socket ativo, tratando erros e mapeando-os para respostas HTTP.
1965
+ * @async
1966
+ * @param {string} methodName - O nome do método a ser invocado no socket.
1967
+ * @param {...any} args - Argumentos a serem repassados para o método do socket.
1968
+ * @returns {Promise<any>} Uma promessa que resolve com o resultado do método do socket ou rejeita com um erro `Boom` em caso de falha.
1969
+ * @throws {Boom} Retorna um erro HTTP 503 se o socket não estiver disponível, ou 501 se o método não existir.
1970
+ */
1971
+ async function runControllerSocketMethod(methodName, ...args) {
1972
+ try {
1973
+ return await runActiveSocketMethod(methodName, ...args);
1974
+ } catch (error) {
1975
+ const message = String(error?.message || '');
1976
+ if (message.includes('Socket do WhatsApp indisponível')) {
1977
+ logger.warn('Socket ativo indisponível para operação.', {
1978
+ action: methodName,
1979
+ socketExists: !!activeSocket,
1980
+ socketOpen: isSocketOpen(activeSocket),
1981
+ timestamp: new Date().toISOString(),
1982
+ });
1983
+ throw new Boom('Socket do WhatsApp indisponível no momento.', { statusCode: 503 });
1984
+ }
1985
+ if (message.includes('não disponível no socket')) {
1986
+ throw new Boom(`Método "${methodName}" não disponível neste socket.`, { statusCode: 501 });
1987
+ }
1988
+ throw error;
1989
+ }
1990
+ }
1991
+
1992
+ /**
1993
+ * Retorna as configurações de privacidade da conta do WhatsApp.
1994
+ * @async
1995
+ * @param {boolean} [force=false] - Se `true`, força um refresh das configurações no servidor.
1996
+ * @returns {Promise<Record<string, string>>} Um objeto contendo as configurações de privacidade.
1997
+ */
1998
+ export async function fetchPrivacySettings(force = false) {
1999
+ return runControllerSocketMethod('fetchPrivacySettings', force);
2000
+ }
2001
+
2002
+ /**
2003
+ * Atualiza a configuração de privacidade para mensagens.
2004
+ * @async
2005
+ * @param {import('@whiskeysockets/baileys').WAPrivacyMessagesValue} value - O novo valor da configuração de privacidade de mensagens.
2006
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2007
+ */
2008
+ export async function updateMessagesPrivacy(value) {
2009
+ return runControllerSocketMethod('updateMessagesPrivacy', value);
2010
+ }
2011
+
2012
+ /**
2013
+ * Atualiza a configuração de privacidade para chamadas.
2014
+ * @async
2015
+ * @param {import('@whiskeysockets/baileys').WAPrivacyCallValue} value - O novo valor da configuração de privacidade de chamadas.
2016
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2017
+ */
2018
+ export async function updateCallPrivacy(value) {
2019
+ return runControllerSocketMethod('updateCallPrivacy', value);
2020
+ }
2021
+
2022
+ /**
2023
+ * Atualiza a configuração de privacidade para "visto por último".
2024
+ * @async
2025
+ * @param {import('@whiskeysockets/baileys').WAPrivacyValue} value - O novo valor da configuração de privacidade de "visto por último".
2026
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2027
+ */
2028
+ export async function updateLastSeenPrivacy(value) {
2029
+ return runControllerSocketMethod('updateLastSeenPrivacy', value);
2030
+ }
2031
+
2032
+ /**
2033
+ * Atualiza a configuração de privacidade para status "online".
2034
+ * @async
2035
+ * @param {import('@whiskeysockets/baileys').WAPrivacyOnlineValue} value - O novo valor da configuração de privacidade de status "online".
2036
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2037
+ */
2038
+ export async function updateOnlinePrivacy(value) {
2039
+ return runControllerSocketMethod('updateOnlinePrivacy', value);
2040
+ }
2041
+
2042
+ /**
2043
+ * Atualiza a configuração de privacidade da foto de perfil.
2044
+ * @async
2045
+ * @param {import('@whiskeysockets/baileys').WAPrivacyValue} value - O novo valor da configuração de privacidade da foto de perfil.
2046
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2047
+ */
2048
+ export async function updateProfilePicturePrivacy(value) {
2049
+ return runControllerSocketMethod('updateProfilePicturePrivacy', value);
2050
+ }
2051
+
2052
+ /**
2053
+ * Atualiza a configuração de privacidade para status.
2054
+ * @async
2055
+ * @param {import('@whiskeysockets/baileys').WAPrivacyValue} value - O novo valor da configuração de privacidade de status.
2056
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2057
+ */
2058
+ export async function updateStatusPrivacy(value) {
2059
+ return runControllerSocketMethod('updateStatusPrivacy', value);
2060
+ }
2061
+
2062
+ /**
2063
+ * Atualiza a configuração de privacidade para confirmações de leitura.
2064
+ * @async
2065
+ * @param {import('@whiskeysockets/baileys').WAReadReceiptsValue} value - O novo valor da configuração de privacidade de confirmações de leitura.
2066
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2067
+ */
2068
+ export async function updateReadReceiptsPrivacy(value) {
2069
+ return runControllerSocketMethod('updateReadReceiptsPrivacy', value);
2070
+ }
2071
+
2072
+ /**
2073
+ * Atualiza a configuração de privacidade para adição em grupos.
2074
+ * @async
2075
+ * @param {import('@whiskeysockets/baileys').WAPrivacyGroupAddValue} value - O novo valor da configuração de privacidade de adição em grupos.
2076
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2077
+ */
2078
+ export async function updateGroupsAddPrivacy(value) {
2079
+ return runControllerSocketMethod('updateGroupsAddPrivacy', value);
2080
+ }
2081
+
2082
+ /**
2083
+ * Atualiza a configuração de privacidade para pré-visualização de links.
2084
+ * @async
2085
+ * @param {boolean} isPreviewsDisabled - Se `true`, desabilita a pré-visualização de links.
2086
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2087
+ */
2088
+ export async function updateDisableLinkPreviewsPrivacy(isPreviewsDisabled) {
2089
+ return runControllerSocketMethod('updateDisableLinkPreviewsPrivacy', isPreviewsDisabled);
2090
+ }
2091
+
2092
+ /**
2093
+ * Atualiza o modo padrão de mensagens temporárias para novos chats.
2094
+ * @async
2095
+ * @param {number} duration - A duração em segundos para as mensagens temporárias (ex: 0 para desativar, 7 * 24 * 60 * 60 para 7 dias).
2096
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização é concluída.
2097
+ */
2098
+ export async function updateDefaultDisappearingMode(duration) {
2099
+ return runControllerSocketMethod('updateDefaultDisappearingMode', duration);
2100
+ }
2101
+
2102
+ /**
2103
+ * Envia uma atualização de presença para um chat ou para o status geral.
2104
+ * @async
2105
+ * @param {import('@whiskeysockets/baileys').WAPresence} type - O tipo de presença a ser enviado (e.g., 'available', 'composing', 'paused').
2106
+ * @param {string} [toJid] - O JID do destinatário para quem a presença será enviada (opcional).
2107
+ * @returns {Promise<void>} Uma promessa que resolve quando a atualização de presença é enviada.
2108
+ */
2109
+ export async function sendPresenceUpdate(type, toJid) {
2110
+ const normalizedType = normalizeWAPresence(type, 'available');
2111
+ return runControllerSocketMethod('sendPresenceUpdate', normalizedType, toJid);
2112
+ }
2113
+
2114
+ /**
2115
+ * Inscreve-se na presença de um JID específico para receber atualizações de status.
2116
+ * @async
2117
+ * @param {string} toJid - O JID do contato ou grupo cuja presença será observada.
2118
+ * @returns {Promise<void>} Uma promessa que resolve quando a inscrição é concluída.
2119
+ */
2120
+ export async function presenceSubscribe(toJid) {
2121
+ return runControllerSocketMethod('presenceSubscribe', toJid);
2122
+ }
2123
+
2124
+ /**
2125
+ * Executa uma modificação em um chat (e.g., arquivar, marcar como lido, mutar).
2126
+ * @async
2127
+ * @param {import('@whiskeysockets/baileys').ChatModification} mod - O objeto de modificação do chat.
2128
+ * @param {string} jid - O JID do chat a ser modificado.
2129
+ * @returns {Promise<void>} Uma promessa que resolve quando a modificação é aplicada.
2130
+ */
2131
+ export async function chatModify(mod, jid) {
2132
+ return runControllerSocketMethod('chatModify', mod, jid);
2133
+ }
2134
+
2135
+ /**
2136
+ * Atalho para arquivar ou desarquivar um chat.
2137
+ * @async
2138
+ * @param {string} jid - O JID do chat.
2139
+ * @param {Array<import('@whiskeysockets/baileys').proto.IWebMessageInfo>} [lastMessages=[]] - Últimas mensagens do chat (opcional, para contexto).
2140
+ * @param {boolean} [archive=true] - Se `true`, arquiva o chat; se `false`, desarquiva.
2141
+ * @returns {Promise<void>} Uma promessa que resolve quando o chat é arquivado/desarquivado.
2142
+ */
2143
+ export async function setChatArchived(jid, lastMessages = [], archive = true) {
2144
+ return chatModify({ archive, lastMessages }, jid);
2145
+ }
2146
+
2147
+ /**
2148
+ * Atalho para marcar um chat como lido ou não lido.
2149
+ * @async
2150
+ * @param {string} jid - O JID do chat.
2151
+ * @param {Array<import('@whiskeysockets/baileys').proto.IWebMessageInfo>} [lastMessages=[]] - Últimas mensagens do chat (opcional, para contexto).
2152
+ * @param {boolean} [markRead=true] - Se `true`, marca o chat como lido; se `false`, marca como não lido.
2153
+ * @returns {Promise<void>} Uma promessa que resolve quando o chat é marcado como lido/não lido.
2154
+ */
2155
+ export async function setChatRead(jid, lastMessages = [], markRead = true) {
2156
+ return chatModify({ markRead, lastMessages }, jid);
2157
+ }
2158
+
2159
+ /**
2160
+ * Atalho para mutar ou desmutar um chat.
2161
+ * @async
2162
+ * @param {string} jid - O JID do chat.
2163
+ * @param {number|null} muteMs - A duração do mute em milissegundos. Use `null` ou `0` para desmutar.
2164
+ * @returns {Promise<void>} Uma promessa que resolve quando o chat é mutado/desmutado.
2165
+ */
2166
+ export async function setChatMute(jid, muteMs) {
2167
+ return chatModify({ mute: muteMs }, jid);
2168
+ }
2169
+
2170
+ /**
2171
+ * Busca um histórico de mensagens sob demanda para um chat específico.
2172
+ * @async
2173
+ * @param {number} count - A quantidade de mensagens a serem solicitadas.
2174
+ * @param {import('@whiskeysockets/baileys').WAMessageKey} oldestMsgKey - A chave da mensagem mais antiga conhecida para iniciar a busca.
2175
+ * @param {number|import('long').default} oldestMsgTimestamp - O timestamp da mensagem mais antiga conhecida.
2176
+ * @returns {Promise<string>} Uma promessa que resolve com o ID da requisição da operação.
2177
+ */
2178
+ export async function fetchMessageHistory(count, oldestMsgKey, oldestMsgTimestamp) {
2179
+ return runControllerSocketMethod('fetchMessageHistory', count, oldestMsgKey, oldestMsgTimestamp);
2180
+ }
2181
+
2182
+ /**
2183
+ * Solicita o reenvio de um placeholder para uma mensagem indisponível.
2184
+ * @async
2185
+ * @param {import('@whiskeysockets/baileys').WAMessageKey} messageKey - A chave da mensagem para a qual o placeholder deve ser reenviado.
2186
+ * @param {Partial<import('@whiskeysockets/baileys').WAMessage>} [msgData] - Dados auxiliares da mensagem (opcional).
2187
+ * @returns {Promise<string|undefined>} Uma promessa que resolve com um ID de string ou `undefined`.
2188
+ */
2189
+ export async function requestPlaceholderResend(messageKey, msgData) {
2190
+ return runControllerSocketMethod('requestPlaceholderResend', messageKey, msgData);
2191
+ }
2192
+
2193
+ /**
2194
+ * Rejeita uma chamada recebida no WhatsApp.
2195
+ * @async
2196
+ * @param {string} callId - O ID da chamada a ser rejeitada.
2197
+ * @param {string} callFrom - O JID do originador da chamada.
2198
+ * @returns {Promise<void>} Uma promessa que resolve quando a chamada é rejeitada.
2199
+ */
2200
+ export async function rejectCall(callId, callFrom) {
2201
+ return runControllerSocketMethod('rejectCall', callId, callFrom);
2202
+ }
2203
+
2204
+ /**
2205
+ * Força uma nova tentativa de conexão ao WhatsApp.
2206
+ * Encerra o socket ativo atual, se existir, para disparar a lógica de reconexão.
2207
+ * Se nenhum socket estiver ativo, inicia uma nova conexão.
2208
+ * @async
2209
+ * @returns {Promise<void>} Uma promessa que resolve quando o fluxo de reconexão é iniciado ou uma nova conexão é tentada.
2210
+ */
2211
+ export async function reconnectToWhatsApp() {
2212
+ // eslint-disable-next-line no-undef
2213
+ if (activeSocket && activeSocket.ws?.readyState === WebSocket.OPEN) {
2214
+ logger.info('♻️ Forçando fechamento do socket para reconectar...', {
2215
+ action: 'force_reconnect',
2216
+ timestamp: new Date().toISOString(),
2217
+ });
2218
+ activeSocket.ws.close();
2219
+ } else {
2220
+ logger.warn('⚠️ Nenhum socket ativo detectado. Iniciando nova conexão manualmente.', {
2221
+ action: 'reconnect_no_active_socket',
2222
+ timestamp: new Date().toISOString(),
2223
+ });
2224
+ await connectToWhatsApp();
2225
+ }
2226
+ }
2227
+
2228
+ if (process.argv[1] === __filename) {
2229
+ logger.info('🚀 Socket Controller iniciado diretamente via CLI.', {
2230
+ action: 'module_direct_execution',
2231
+ timestamp: new Date().toISOString(),
2232
+ });
2233
+
2234
+ connectToWhatsApp().catch((err) => {
2235
+ logger.error('❌ Falha crítica ao tentar iniciar conexão via execução direta.', {
2236
+ action: 'direct_connection_failure',
2237
+ errorMessage: err.message,
2238
+ stack: err.stack,
2239
+ timestamp: new Date().toISOString(),
2240
+ });
2241
+ process.exit(1);
2242
+ });
2243
+ }