@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,347 @@
1
+ import axios from 'axios';
2
+ import logger from '#logger';
3
+ import groupConfigStore from '../../store/groupConfigStore.js';
4
+ import { TABLES, findAll } from '../../../database/index.js';
5
+ import { getActiveSocket } from '../../config/index.js';
6
+ import getImageBuffer from '../../utils/http/getImageBufferModule.js';
7
+ import { sendAndStore } from './messagePersistenceService.js';
8
+
9
+ const DEFAULT_NEWS_API_URL = 'http://127.0.0.1:3001';
10
+ const NEWS_API_URL = (process.env.NEWS_API_URL || DEFAULT_NEWS_API_URL).replace(/\/+$/, '');
11
+ const MIN_DELAY_MS = 60 * 1000;
12
+ const MAX_DELAY_MS = 120 * 1000;
13
+ const MAX_SENT_IDS = Number(process.env.NEWS_SENT_IDS_LIMIT || 500);
14
+ const LOOP_START_DELAY_MS = 5000;
15
+ const GROUP_UNAVAILABLE_ERROR_PATTERNS = ['item-not-found', 'not-authorized', 'not in group', 'group does not exist', 'recipient not found', 'recipient-unavailable'];
16
+
17
+ const groupLoops = new Map();
18
+
19
+ const getRandomDelayMs = () => {
20
+ const min = MIN_DELAY_MS;
21
+ const max = MAX_DELAY_MS;
22
+ return Math.floor(min + Math.random() * (max - min + 1));
23
+ };
24
+
25
+ const parseConfigValue = (value) => {
26
+ if (value === null || value === undefined) return {};
27
+ if (Buffer.isBuffer(value)) {
28
+ try {
29
+ return JSON.parse(value.toString('utf8'));
30
+ } catch (error) {
31
+ logger.warn('Falha ao fazer parse do config (buffer).', { error: error.message });
32
+ return {};
33
+ }
34
+ }
35
+ if (typeof value === 'string') {
36
+ try {
37
+ return JSON.parse(value);
38
+ } catch (error) {
39
+ logger.warn('Falha ao fazer parse do config (string).', { error: error.message });
40
+ return {};
41
+ }
42
+ }
43
+ if (typeof value === 'object') return value;
44
+ return {};
45
+ };
46
+
47
+ const loadEnabledGroupsFromDb = async () => {
48
+ const enabledGroups = [];
49
+ const limit = 100;
50
+ let offset = 0;
51
+
52
+ while (true) {
53
+ const rows = await findAll(TABLES.GROUP_CONFIGS, limit, offset);
54
+ if (!rows.length) break;
55
+
56
+ for (const row of rows) {
57
+ const config = parseConfigValue(row.config);
58
+ if (config?.newsEnabled) {
59
+ enabledGroups.push(row.id);
60
+ }
61
+ }
62
+
63
+ offset += rows.length;
64
+ if (rows.length < limit) break;
65
+ }
66
+
67
+ return enabledGroups;
68
+ };
69
+
70
+ const normalizeNewsItems = (data) => {
71
+ if (!Array.isArray(data)) return [];
72
+ return data
73
+ .filter((item) => item && typeof item === 'object')
74
+ .map((item) => ({
75
+ id: item.id,
76
+ timestamp: item.timestamp,
77
+ refined: item.refined || {},
78
+ }));
79
+ };
80
+
81
+ const fetchNewsItems = async () => {
82
+ try {
83
+ const response = await axios.get(NEWS_API_URL, { timeout: 15000 });
84
+ return normalizeNewsItems(response.data);
85
+ } catch (error) {
86
+ logger.error('Erro ao buscar noticias da API.', {
87
+ error: error.message,
88
+ url: NEWS_API_URL,
89
+ });
90
+ return [];
91
+ }
92
+ };
93
+
94
+ const buildNewsCaption = (newsItem) => {
95
+ const title = newsItem?.refined?.name || 'Notícia';
96
+ const summary = (newsItem?.refined?.summary || '').trim();
97
+ const url = newsItem?.refined?.url || '';
98
+
99
+ const lines = [`📰 *${title}*`];
100
+ if (summary) {
101
+ lines.push('', summary);
102
+ }
103
+ if (url) {
104
+ lines.push('', `🔗 ${url}`);
105
+ }
106
+ return lines.join('\n').trim();
107
+ };
108
+
109
+ const sortByTimestampAsc = (items) =>
110
+ items.sort((a, b) => {
111
+ const aTime = a?.timestamp ? Date.parse(a.timestamp) : 0;
112
+ const bTime = b?.timestamp ? Date.parse(b.timestamp) : 0;
113
+ if (Number.isNaN(aTime) && Number.isNaN(bTime)) return 0;
114
+ if (Number.isNaN(aTime)) return 1;
115
+ if (Number.isNaN(bTime)) return -1;
116
+ return aTime - bTime;
117
+ });
118
+
119
+ const trimSentIds = (ids) => {
120
+ if (!Array.isArray(ids)) return [];
121
+ if (!Number.isFinite(MAX_SENT_IDS) || MAX_SENT_IDS <= 0) return ids;
122
+ if (ids.length <= MAX_SENT_IDS) return ids;
123
+ return ids.slice(ids.length - MAX_SENT_IDS);
124
+ };
125
+
126
+ const toErrorFragments = (error) => {
127
+ const candidates = [error?.message, error?.data, error?.output?.payload?.message, error?.output?.payload?.error, error?.output?.statusCode, error?.status, error?.cause?.message, error?.cause?.data];
128
+
129
+ return candidates
130
+ .filter((value) => value !== null && value !== undefined)
131
+ .map((value) => {
132
+ if (typeof value === 'string') return value.toLowerCase();
133
+ if (typeof value === 'number') return String(value);
134
+ try {
135
+ return JSON.stringify(value).toLowerCase();
136
+ } catch {
137
+ return String(value).toLowerCase();
138
+ }
139
+ });
140
+ };
141
+
142
+ const isGroupUnavailableError = (error) => {
143
+ const fragments = toErrorFragments(error);
144
+ return fragments.some((fragment) => GROUP_UNAVAILABLE_ERROR_PATTERNS.some((pattern) => fragment.includes(pattern)));
145
+ };
146
+
147
+ const scheduleNextRun = (groupId, delayMs) => {
148
+ const state = groupLoops.get(groupId);
149
+ if (!state || state.stopped) return;
150
+ if (state.timeoutId) clearTimeout(state.timeoutId);
151
+ state.timeoutId = setTimeout(() => {
152
+ processGroupNews(groupId);
153
+ }, delayMs);
154
+ };
155
+
156
+ const stopGroupLoopInternal = (groupId) => {
157
+ const state = groupLoops.get(groupId);
158
+ if (!state) return;
159
+ if (state.timeoutId) clearTimeout(state.timeoutId);
160
+ state.stopped = true;
161
+ groupLoops.delete(groupId);
162
+ };
163
+
164
+ const processGroupNews = async (groupId) => {
165
+ const state = groupLoops.get(groupId);
166
+ if (!state || state.stopped) return;
167
+ if (state.inFlight) return;
168
+
169
+ state.inFlight = true;
170
+ let shouldSchedule = true;
171
+
172
+ try {
173
+ const config = await groupConfigStore.getGroupConfig(groupId);
174
+ if (!config?.newsEnabled) {
175
+ shouldSchedule = false;
176
+ stopGroupLoopInternal(groupId);
177
+ return;
178
+ }
179
+
180
+ const sock = getActiveSocket();
181
+ if (!sock) {
182
+ const now = Date.now();
183
+ if (!state.lastNotReadyLogAt || now - state.lastNotReadyLogAt > 60_000) {
184
+ state.lastNotReadyLogAt = now;
185
+ logger.debug('Socket nao disponivel para envio de noticias.', { groupId });
186
+ }
187
+ return;
188
+ }
189
+
190
+ const allNews = await fetchNewsItems();
191
+ if (allNews.length === 0) {
192
+ return;
193
+ }
194
+
195
+ const sentIds = new Set(Array.isArray(config.newsSentIds) ? config.newsSentIds : []);
196
+ const unsent = allNews.filter((item) => item?.id && !sentIds.has(item.id));
197
+
198
+ if (unsent.length === 0) {
199
+ return;
200
+ }
201
+
202
+ sortByTimestampAsc(unsent);
203
+ const nextItem = unsent[0];
204
+ const caption = buildNewsCaption(nextItem);
205
+ const imageUrl = nextItem?.refined?.image || '';
206
+ let sent = false;
207
+
208
+ try {
209
+ if (imageUrl && /^https?:\/\//i.test(imageUrl)) {
210
+ let imageBuffer = null;
211
+ try {
212
+ imageBuffer = await getImageBuffer(imageUrl);
213
+ } catch (error) {
214
+ logger.warn('Falha ao baixar imagem da noticia. Enviando texto.', {
215
+ groupId,
216
+ error: error.message,
217
+ imageUrl,
218
+ });
219
+ }
220
+
221
+ if (imageBuffer) {
222
+ try {
223
+ await sendAndStore(sock, groupId, { image: imageBuffer, caption });
224
+ sent = true;
225
+ } catch (error) {
226
+ if (isGroupUnavailableError(error)) {
227
+ throw error;
228
+ }
229
+ logger.warn('Falha ao enviar imagem da noticia. Enviando texto.', {
230
+ groupId,
231
+ error: error.message,
232
+ });
233
+ }
234
+ }
235
+ }
236
+
237
+ if (!sent) {
238
+ await sendAndStore(sock, groupId, { text: caption });
239
+ }
240
+
241
+ sentIds.add(nextItem.id);
242
+ const updatedSentIds = trimSentIds(Array.from(sentIds));
243
+ await groupConfigStore.updateGroupConfig(groupId, {
244
+ newsSentIds: updatedSentIds,
245
+ newsLastSentAt: new Date().toISOString(),
246
+ });
247
+ } catch (error) {
248
+ if (isGroupUnavailableError(error)) {
249
+ shouldSchedule = false;
250
+ stopGroupLoopInternal(groupId);
251
+
252
+ try {
253
+ await groupConfigStore.updateGroupConfig(groupId, { newsEnabled: false });
254
+ } catch (updateError) {
255
+ logger.error('Falha ao desativar noticias para grupo indisponivel.', {
256
+ groupId,
257
+ error: updateError.message,
258
+ });
259
+ }
260
+
261
+ logger.warn('Grupo indisponivel para envio de noticias. Envio automatico desativado.', {
262
+ groupId,
263
+ error: error.message,
264
+ });
265
+ return;
266
+ }
267
+
268
+ logger.error('Erro ao enviar noticia para grupo.', {
269
+ groupId,
270
+ error: error.message,
271
+ });
272
+ }
273
+ } catch (error) {
274
+ logger.error('Erro no processamento de noticias do grupo.', {
275
+ groupId,
276
+ error: error.message,
277
+ });
278
+ } finally {
279
+ state.inFlight = false;
280
+ if (shouldSchedule) {
281
+ scheduleNextRun(groupId, getRandomDelayMs());
282
+ }
283
+ }
284
+ };
285
+
286
+ export const startNewsBroadcastForGroup = (groupId, options = {}) => {
287
+ const existing = groupLoops.get(groupId);
288
+ if (existing && !existing.stopped) {
289
+ return;
290
+ }
291
+
292
+ const initialDelay = typeof options.initialDelayMs === 'number' ? options.initialDelayMs : LOOP_START_DELAY_MS;
293
+
294
+ groupLoops.set(groupId, {
295
+ timeoutId: null,
296
+ inFlight: false,
297
+ stopped: false,
298
+ });
299
+
300
+ scheduleNextRun(groupId, initialDelay);
301
+ };
302
+
303
+ export const stopNewsBroadcastForGroup = (groupId) => {
304
+ stopGroupLoopInternal(groupId);
305
+ };
306
+
307
+ export const syncNewsBroadcastService = async () => {
308
+ try {
309
+ const enabledGroups = await loadEnabledGroupsFromDb();
310
+ if (enabledGroups.length === 0) {
311
+ logger.info('Nenhum grupo com noticias ativadas encontrado.');
312
+ return;
313
+ }
314
+
315
+ enabledGroups.forEach((groupId) => {
316
+ startNewsBroadcastForGroup(groupId);
317
+ });
318
+
319
+ logger.info('Serviço de noticias sincronizado.', {
320
+ groups: enabledGroups.length,
321
+ });
322
+ } catch (error) {
323
+ logger.error('Falha ao sincronizar serviço de noticias.', { error: error.message });
324
+ }
325
+ };
326
+
327
+ export const initializeNewsBroadcastService = async () => syncNewsBroadcastService();
328
+
329
+ export const stopNewsBroadcastService = () => {
330
+ const groupIds = Array.from(groupLoops.keys());
331
+ if (!groupIds.length) {
332
+ return;
333
+ }
334
+
335
+ groupIds.forEach((groupId) => stopGroupLoopInternal(groupId));
336
+ logger.info('Servico de noticias parado.', { groups: groupIds.length });
337
+ };
338
+
339
+ export const getNewsStatusForGroup = async (groupId) => {
340
+ const config = await groupConfigStore.getGroupConfig(groupId);
341
+ const sentCount = Array.isArray(config.newsSentIds) ? config.newsSentIds.length : 0;
342
+ return {
343
+ enabled: Boolean(config.newsEnabled),
344
+ sentCount,
345
+ lastSentAt: config.newsLastSentAt || null,
346
+ };
347
+ };
@@ -0,0 +1,347 @@
1
+ import { jidNormalizedUser } from '@whiskeysockets/baileys';
2
+
3
+ const normalizeJid = (jid) => {
4
+ if (!jid) return '';
5
+ return jidNormalizedUser(jid);
6
+ };
7
+
8
+ const parseEnvInt = (value, fallback, min, max) => {
9
+ const numeric = Number(value);
10
+ if (!Number.isFinite(numeric)) return fallback;
11
+ return Math.max(min, Math.min(max, Math.floor(numeric)));
12
+ };
13
+
14
+ export const MIN_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES = 1;
15
+ export const MAX_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES = 24 * 60;
16
+ export const DEFAULT_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES = parseEnvInt(process.env.STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES ?? process.env.STICKER_FOCUS_TEXT_COOLDOWN_MINUTES, 60, MIN_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES, MAX_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES);
17
+ export const MIN_STICKER_FOCUS_MESSAGE_ALLOWANCE = 1;
18
+ export const MAX_STICKER_FOCUS_MESSAGE_ALLOWANCE = 10;
19
+ export const DEFAULT_STICKER_FOCUS_MESSAGE_ALLOWANCE = parseEnvInt(process.env.STICKER_FOCUS_MESSAGE_ALLOWANCE, 2, MIN_STICKER_FOCUS_MESSAGE_ALLOWANCE, MAX_STICKER_FOCUS_MESSAGE_ALLOWANCE);
20
+
21
+ export const MIN_STICKER_FOCUS_CHAT_WINDOW_MINUTES = 1;
22
+ export const MAX_STICKER_FOCUS_CHAT_WINDOW_MINUTES = 6 * 60;
23
+ export const DEFAULT_STICKER_FOCUS_CHAT_WINDOW_MINUTES = parseEnvInt(process.env.STICKER_FOCUS_CHAT_WINDOW_MINUTES, 15, MIN_STICKER_FOCUS_CHAT_WINDOW_MINUTES, MAX_STICKER_FOCUS_CHAT_WINDOW_MINUTES);
24
+
25
+ const STICKER_FOCUS_WARNING_COOLDOWN_MS = parseEnvInt(process.env.STICKER_FOCUS_WARNING_COOLDOWN_MS, 45_000, 10_000, 5 * 60_000);
26
+ const NON_HUMAN_PLACEHOLDERS = new Set(['Mensagem vazia', 'Tipo de mensagem não suportado ou sem conteúdo.']);
27
+ const IGNORED_MESSAGE_TYPES = new Set(['messagehistorybundle', 'messagehistorynotice', 'keydistribution', 'senderkeydistribution', 'reaction', 'devicesent', 'contextinfo', 'protocol', 'botinvoke']);
28
+ const NON_THROTTLED_MESSAGE_TYPES = new Set(['sticker', 'image', 'video', 'stickerpack', 'stickerpackmessage']);
29
+ const MESSAGE_WRAPPER_KEYS = ['ephemeralMessage', 'viewOnceMessage', 'viewOnceMessageV2', 'viewOnceMessageV2Extension', 'deviceSentMessage', 'editedMessage'];
30
+
31
+ const sharedMessageAllowance = globalThis.__omnizapStickerFocusMessageAllowance instanceof Map ? globalThis.__omnizapStickerFocusMessageAllowance : new Map();
32
+ const sharedWarningThrottle = globalThis.__omnizapStickerFocusWarningThrottle instanceof Map ? globalThis.__omnizapStickerFocusWarningThrottle : new Map();
33
+
34
+ globalThis.__omnizapStickerFocusMessageAllowance = sharedMessageAllowance;
35
+ globalThis.__omnizapStickerFocusWarningThrottle = sharedWarningThrottle;
36
+
37
+ const normalizeMinutes = (value, fallback, min, max) => {
38
+ const numeric = Number(value);
39
+ if (!Number.isFinite(numeric)) return fallback;
40
+ return Math.max(min, Math.min(max, Math.floor(numeric)));
41
+ };
42
+
43
+ const parseTimestampMs = (value) => {
44
+ if (value === null || value === undefined || value === '') return 0;
45
+ const numeric = Number(value);
46
+ if (Number.isFinite(numeric) && numeric > 0) return Math.floor(numeric);
47
+ const parsed = Date.parse(String(value));
48
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
49
+ return 0;
50
+ };
51
+
52
+ const buildSenderKey = ({ groupId = '', senderJid = '' }) => {
53
+ const normalizedGroup = normalizeJid(groupId) || String(groupId || '').trim();
54
+ const normalizedSender = normalizeJid(senderJid) || String(senderJid || '').trim();
55
+ if (!normalizedGroup || !normalizedSender) return '';
56
+ return `${normalizedGroup}:${normalizedSender}`;
57
+ };
58
+
59
+ const hasExplicitTextPayload = (messagePayload) => {
60
+ if (!messagePayload || typeof messagePayload !== 'object') return false;
61
+
62
+ const queue = [messagePayload];
63
+ while (queue.length > 0) {
64
+ const current = queue.shift();
65
+ if (!current || typeof current !== 'object') continue;
66
+
67
+ const conversationText = typeof current.conversation === 'string' ? current.conversation.trim() : '';
68
+ if (conversationText) return true;
69
+
70
+ const extendedText = typeof current.extendedTextMessage?.text === 'string' ? current.extendedTextMessage.text.trim() : '';
71
+ if (extendedText) return true;
72
+
73
+ for (const wrapperKey of MESSAGE_WRAPPER_KEYS) {
74
+ const nestedMessage = current?.[wrapperKey]?.message;
75
+ if (nestedMessage && typeof nestedMessage === 'object') {
76
+ queue.push(nestedMessage);
77
+ }
78
+ }
79
+ }
80
+
81
+ return false;
82
+ };
83
+
84
+ const hasStickerPackPayload = (messagePayload) => {
85
+ if (!messagePayload || typeof messagePayload !== 'object') return false;
86
+
87
+ const queue = [messagePayload];
88
+ while (queue.length > 0) {
89
+ const current = queue.shift();
90
+ if (!current || typeof current !== 'object') continue;
91
+
92
+ if (current.stickerPackMessage && typeof current.stickerPackMessage === 'object') {
93
+ return true;
94
+ }
95
+
96
+ for (const wrapperKey of MESSAGE_WRAPPER_KEYS) {
97
+ const nestedMessage = current?.[wrapperKey]?.message;
98
+ if (nestedMessage && typeof nestedMessage === 'object') {
99
+ queue.push(nestedMessage);
100
+ }
101
+ }
102
+ }
103
+
104
+ return false;
105
+ };
106
+
107
+ const isPlaceholderOnlyText = (value) => {
108
+ const normalized = String(value || '').trim();
109
+ if (!normalized) return true;
110
+ if (NON_HUMAN_PLACEHOLDERS.has(normalized)) return true;
111
+ return /^\[[^\]]+\]$/.test(normalized);
112
+ };
113
+
114
+ const normalizeMessageTypes = (mediaEntries = []) => {
115
+ if (!Array.isArray(mediaEntries)) return [];
116
+ return mediaEntries
117
+ .map((entry) =>
118
+ String(entry?.mediaType || '')
119
+ .trim()
120
+ .toLowerCase(),
121
+ )
122
+ .filter(Boolean);
123
+ };
124
+
125
+ const isIgnoredSystemMessageType = (type) => {
126
+ const normalized = String(type || '')
127
+ .trim()
128
+ .toLowerCase();
129
+ if (!normalized) return true;
130
+ if (IGNORED_MESSAGE_TYPES.has(normalized)) return true;
131
+ // Cobre variações do Baileys/proto para eventos internos de distribuição de chave.
132
+ if (normalized.includes('keydistribution')) return true;
133
+ if (normalized.includes('senderkeydistribution')) return true;
134
+ return false;
135
+ };
136
+
137
+ const isAlbumMediaMessageType = (type) => {
138
+ const normalized = String(type || '')
139
+ .trim()
140
+ .toLowerCase();
141
+ if (!normalized) return false;
142
+ if (normalized === 'album') return true;
143
+ return normalized.startsWith('album');
144
+ };
145
+
146
+ export const clampStickerFocusMessageCooldownMinutes = (value, fallback = DEFAULT_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES) => normalizeMinutes(value, fallback, MIN_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES, MAX_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES);
147
+ export const clampStickerFocusMessageAllowance = (value, fallback = DEFAULT_STICKER_FOCUS_MESSAGE_ALLOWANCE) => normalizeMinutes(value, fallback, MIN_STICKER_FOCUS_MESSAGE_ALLOWANCE, MAX_STICKER_FOCUS_MESSAGE_ALLOWANCE);
148
+
149
+ export const clampStickerFocusChatWindowMinutes = (value, fallback = DEFAULT_STICKER_FOCUS_CHAT_WINDOW_MINUTES) => normalizeMinutes(value, fallback, MIN_STICKER_FOCUS_CHAT_WINDOW_MINUTES, MAX_STICKER_FOCUS_CHAT_WINDOW_MINUTES);
150
+
151
+ export const minutesToMs = (minutes) => Math.max(0, Math.floor(Number(minutes) || 0) * 60 * 1000);
152
+
153
+ export const resolveStickerFocusState = (groupConfig = {}, now = Date.now()) => {
154
+ const rawCooldown = groupConfig?.stickerFocusMessageCooldownMinutes ?? groupConfig?.stickerFocusTextCooldownMinutes;
155
+ const messageCooldownMinutes = clampStickerFocusMessageCooldownMinutes(rawCooldown);
156
+ const rawAllowance = groupConfig?.stickerFocusMessageAllowance ?? groupConfig?.stickerFocusMessageAllowanceCount;
157
+ const messageAllowanceCount = clampStickerFocusMessageAllowance(rawAllowance);
158
+ const messageCooldownMs = minutesToMs(messageCooldownMinutes);
159
+ const chatWindowUntilMs = parseTimestampMs(groupConfig?.stickerFocusChatWindowUntilMs);
160
+ const safeNow = Number.isFinite(now) ? now : Date.now();
161
+ const chatWindowRemainingMs = Math.max(0, chatWindowUntilMs - safeNow);
162
+
163
+ return {
164
+ enabled: Boolean(groupConfig?.stickerFocusEnabled),
165
+ messageAllowanceCount,
166
+ messageCooldownMinutes,
167
+ messageCooldownMs,
168
+ // backward compatibility for old reads
169
+ textAllowanceCount: messageAllowanceCount,
170
+ textCooldownMinutes: messageCooldownMinutes,
171
+ textCooldownMs: messageCooldownMs,
172
+ chatWindowUntilMs,
173
+ chatWindowRemainingMs,
174
+ isChatWindowOpen: chatWindowRemainingMs > 0,
175
+ };
176
+ };
177
+
178
+ export const resolveStickerFocusMessageClassification = ({ messageInfo, extractedText, mediaEntries = [] }) => {
179
+ if (messageInfo?.messageStubType !== undefined && messageInfo?.messageStubType !== null) {
180
+ return {
181
+ isThrottleCandidate: false,
182
+ messageType: 'system_stub',
183
+ reason: 'stub',
184
+ };
185
+ }
186
+
187
+ if (hasStickerPackPayload(messageInfo?.message)) {
188
+ return {
189
+ isThrottleCandidate: false,
190
+ messageType: 'sticker_pack',
191
+ reason: 'sticker_pack_payload',
192
+ };
193
+ }
194
+
195
+ const messageTypes = normalizeMessageTypes(mediaEntries);
196
+ const filteredTypes = messageTypes.filter((type) => !isIgnoredSystemMessageType(type));
197
+ const primaryType = filteredTypes[0] || messageTypes[0] || 'unknown';
198
+ const nonThrottledType = filteredTypes.find((type) => NON_THROTTLED_MESSAGE_TYPES.has(type) || isAlbumMediaMessageType(type));
199
+
200
+ if (nonThrottledType) {
201
+ return {
202
+ isThrottleCandidate: false,
203
+ messageType: nonThrottledType || primaryType,
204
+ reason: 'sticker_flow_media',
205
+ };
206
+ }
207
+
208
+ if (filteredTypes.length > 0) {
209
+ return {
210
+ isThrottleCandidate: true,
211
+ messageType: primaryType,
212
+ reason: 'message_type',
213
+ };
214
+ }
215
+
216
+ if (!isPlaceholderOnlyText(extractedText) && hasExplicitTextPayload(messageInfo?.message)) {
217
+ return {
218
+ isThrottleCandidate: true,
219
+ messageType: 'text',
220
+ reason: 'explicit_text_payload',
221
+ };
222
+ }
223
+
224
+ return {
225
+ isThrottleCandidate: false,
226
+ messageType: primaryType,
227
+ reason: 'non_human_or_empty',
228
+ };
229
+ };
230
+
231
+ const normalizeAllowanceHistory = (historyValue) => {
232
+ if (Array.isArray(historyValue)) {
233
+ return historyValue
234
+ .map((entry) => Number(entry))
235
+ .filter((entry) => Number.isFinite(entry) && entry > 0)
236
+ .map((entry) => Math.floor(entry))
237
+ .sort((a, b) => a - b);
238
+ }
239
+
240
+ const singleTimestamp = Number(historyValue);
241
+ if (Number.isFinite(singleTimestamp) && singleTimestamp > 0) {
242
+ return [Math.floor(singleTimestamp)];
243
+ }
244
+
245
+ return [];
246
+ };
247
+
248
+ export const canSendMessageInStickerFocus = ({ groupId, senderJid, messageCooldownMs, messageAllowanceCount = DEFAULT_STICKER_FOCUS_MESSAGE_ALLOWANCE, now = Date.now() }) => {
249
+ const senderKey = buildSenderKey({ groupId, senderJid });
250
+ if (!senderKey) {
251
+ return {
252
+ allowed: true,
253
+ remainingMs: 0,
254
+ usageCount: 0,
255
+ allowanceCount: clampStickerFocusMessageAllowance(messageAllowanceCount),
256
+ };
257
+ }
258
+
259
+ const normalizedCooldownMs = Math.max(0, Math.floor(Number(messageCooldownMs) || 0));
260
+ const normalizedAllowanceCount = clampStickerFocusMessageAllowance(messageAllowanceCount);
261
+ if (normalizedCooldownMs <= 0) {
262
+ const rawHistory = normalizeAllowanceHistory(sharedMessageAllowance.get(senderKey));
263
+ return {
264
+ allowed: true,
265
+ remainingMs: 0,
266
+ usageCount: rawHistory.length,
267
+ allowanceCount: normalizedAllowanceCount,
268
+ };
269
+ }
270
+
271
+ const safeNow = Number.isFinite(now) ? now : Date.now();
272
+ const history = normalizeAllowanceHistory(sharedMessageAllowance.get(senderKey)).filter((timestamp) => safeNow - timestamp < normalizedCooldownMs);
273
+ if (history.length > 0) {
274
+ sharedMessageAllowance.set(senderKey, history);
275
+ } else {
276
+ sharedMessageAllowance.delete(senderKey);
277
+ }
278
+
279
+ if (history.length < normalizedAllowanceCount) {
280
+ return {
281
+ allowed: true,
282
+ remainingMs: 0,
283
+ usageCount: history.length,
284
+ allowanceCount: normalizedAllowanceCount,
285
+ };
286
+ }
287
+
288
+ const oldestTimestamp = history[0] || safeNow;
289
+ return {
290
+ allowed: false,
291
+ remainingMs: Math.max(0, normalizedCooldownMs - (safeNow - oldestTimestamp)),
292
+ usageCount: history.length,
293
+ allowanceCount: normalizedAllowanceCount,
294
+ };
295
+ };
296
+
297
+ export const registerMessageUsageInStickerFocus = ({ groupId, senderJid, messageCooldownMs = minutesToMs(DEFAULT_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES), messageAllowanceCount = DEFAULT_STICKER_FOCUS_MESSAGE_ALLOWANCE, now = Date.now() }) => {
298
+ const senderKey = buildSenderKey({ groupId, senderJid });
299
+ if (!senderKey) return;
300
+ const normalizedCooldownMs = Math.max(0, Math.floor(Number(messageCooldownMs) || 0));
301
+ const normalizedAllowanceCount = clampStickerFocusMessageAllowance(messageAllowanceCount);
302
+ const safeNow = Number.isFinite(now) ? now : Date.now();
303
+ const history = normalizeAllowanceHistory(sharedMessageAllowance.get(senderKey));
304
+ const recentHistory = normalizedCooldownMs > 0 ? history.filter((timestamp) => safeNow - timestamp < normalizedCooldownMs) : history;
305
+ recentHistory.push(safeNow);
306
+ const trimmedHistory = recentHistory.slice(-normalizedAllowanceCount);
307
+ sharedMessageAllowance.set(senderKey, trimmedHistory);
308
+ };
309
+
310
+ export const shouldSendStickerFocusWarning = ({ groupId, senderJid, now = Date.now() }) => {
311
+ const senderKey = buildSenderKey({ groupId, senderJid });
312
+ if (!senderKey) return true;
313
+ const safeNow = Number.isFinite(now) ? now : Date.now();
314
+ const lastWarningAt = Number(sharedWarningThrottle.get(senderKey) || 0);
315
+ if (!lastWarningAt || safeNow - lastWarningAt >= STICKER_FOCUS_WARNING_COOLDOWN_MS) {
316
+ sharedWarningThrottle.set(senderKey, safeNow);
317
+ return true;
318
+ }
319
+ return false;
320
+ };
321
+
322
+ // Backward compatibility aliases
323
+ export const MIN_STICKER_FOCUS_TEXT_COOLDOWN_MINUTES = MIN_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES;
324
+ export const MAX_STICKER_FOCUS_TEXT_COOLDOWN_MINUTES = MAX_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES;
325
+ export const DEFAULT_STICKER_FOCUS_TEXT_COOLDOWN_MINUTES = DEFAULT_STICKER_FOCUS_MESSAGE_COOLDOWN_MINUTES;
326
+ export const clampStickerFocusTextCooldownMinutes = clampStickerFocusMessageCooldownMinutes;
327
+ export const MIN_STICKER_FOCUS_TEXT_ALLOWANCE = MIN_STICKER_FOCUS_MESSAGE_ALLOWANCE;
328
+ export const MAX_STICKER_FOCUS_TEXT_ALLOWANCE = MAX_STICKER_FOCUS_MESSAGE_ALLOWANCE;
329
+ export const DEFAULT_STICKER_FOCUS_TEXT_ALLOWANCE = DEFAULT_STICKER_FOCUS_MESSAGE_ALLOWANCE;
330
+ export const clampStickerFocusTextAllowance = clampStickerFocusMessageAllowance;
331
+ export const isPlainTextMessageForStickerFocus = ({ messageInfo, extractedText, mediaEntries = [] }) => resolveStickerFocusMessageClassification({ messageInfo, extractedText, mediaEntries }).messageType === 'text';
332
+ export const canSendTextInStickerFocus = ({ groupId, senderJid, textCooldownMs, textAllowanceCount, now = Date.now() }) =>
333
+ canSendMessageInStickerFocus({
334
+ groupId,
335
+ senderJid,
336
+ messageCooldownMs: textCooldownMs,
337
+ messageAllowanceCount: textAllowanceCount,
338
+ now,
339
+ });
340
+ export const registerTextUsageInStickerFocus = ({ groupId, senderJid, textCooldownMs, textAllowanceCount, now = Date.now() }) =>
341
+ registerMessageUsageInStickerFocus({
342
+ groupId,
343
+ senderJid,
344
+ messageCooldownMs: textCooldownMs,
345
+ messageAllowanceCount: textAllowanceCount,
346
+ now,
347
+ });