@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,758 @@
1
+ import { withTimeout } from '../../http/httpRequestUtils.js';
2
+
3
+ export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger, getSystemMetrics, getActiveSocket, resolveSocketReadyState, resolveActiveSocketBotJid, resolveCatalogBotPhone, fetchPrometheusSummary, metricsEndpoint, systemSummaryCache, systemSummaryCacheSeconds, readmeSummaryCache, readmeSummaryCacheSeconds, readmeMessageTypeSampleLimit, readmeCommandPrefix, buildMenuCaption, buildStickerMenu, buildMediaMenu, buildQuoteMenu, buildAnimeMenu, buildAiMenu, buildStatsMenu, buildAdminMenu, profilePictureUrlFromActiveSocket, normalizeJid, getJidUser, globalRankCache, globalRankRefreshSeconds, marketplaceGlobalStatsCache, marketplaceGlobalStatsCacheSeconds }) => {
4
+ let globalRankRefreshTimer = null;
5
+
6
+ const buildSystemSummarySnapshot = async () => {
7
+ const system = getSystemMetrics();
8
+ const activeSocket = getActiveSocket();
9
+ let prometheus = null;
10
+ let prometheusError = null;
11
+ let platformError = null;
12
+ let usageError = null;
13
+
14
+ const socketReadyState = resolveSocketReadyState(activeSocket);
15
+ const botJid = resolveActiveSocketBotJid(activeSocket) || null;
16
+ const botPhone = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '') || null;
17
+ const botConnected = socketReadyState === 1 || (socketReadyState === null && Boolean(botJid));
18
+ const botConnectionStatus = botConnected ? 'online' : socketReadyState === 0 ? 'connecting' : 'offline';
19
+
20
+ let platform = {
21
+ total_users: null,
22
+ total_groups: null,
23
+ total_chats: null,
24
+ };
25
+ let usage = {
26
+ total_messages: null,
27
+ total_commands: null,
28
+ total_commands_source: tables.MESSAGE_ANALYSIS_EVENT,
29
+ };
30
+
31
+ try {
32
+ prometheus = await fetchPrometheusSummary();
33
+ } catch (error) {
34
+ prometheusError = error?.message || 'Falha ao consultar /metrics';
35
+ }
36
+
37
+ try {
38
+ const [chatTotalsRows, groupsMetadataTotalsRows, lidMapTotalsRows] = await Promise.all([
39
+ executeQuery(
40
+ `SELECT
41
+ COUNT(*) AS total_chats,
42
+ SUM(CASE WHEN id LIKE '%@g.us' THEN 1 ELSE 0 END) AS total_groups
43
+ FROM ${tables.CHATS}`,
44
+ ),
45
+ executeQuery(`SELECT COUNT(*) AS total_groups FROM ${tables.GROUPS_METADATA}`),
46
+ executeQuery(`SELECT COUNT(*) AS total_users FROM ${tables.LID_MAP}`),
47
+ ]);
48
+
49
+ const chatsTotals = chatTotalsRows?.[0] || {};
50
+ const groupsMetadataTotals = groupsMetadataTotalsRows?.[0] || {};
51
+ const lidMapTotals = lidMapTotalsRows?.[0] || {};
52
+ const totalGroupsFromChats = Number(chatsTotals?.total_groups || 0);
53
+ const totalGroupsFromMetadata = Number(groupsMetadataTotals?.total_groups || 0);
54
+ const totalUsersFromLidMap = Number(lidMapTotals?.total_users || 0);
55
+
56
+ platform = {
57
+ total_users: totalUsersFromLidMap,
58
+ total_users_source: 'lid_map',
59
+ total_groups: Math.max(totalGroupsFromChats, totalGroupsFromMetadata),
60
+ total_chats: Number(chatsTotals?.total_chats || 0),
61
+ };
62
+ } catch (error) {
63
+ platformError = error?.message || 'Falha ao consultar totais de usuários/grupos';
64
+ }
65
+
66
+ try {
67
+ const [messageTotalsRows, commandTotalsRows] = await Promise.all([
68
+ executeQuery(`SELECT COUNT(*) AS total_messages FROM ${tables.MESSAGES}`),
69
+ executeQuery(
70
+ `SELECT COUNT(*) AS total_commands
71
+ FROM ${tables.MESSAGE_ANALYSIS_EVENT}
72
+ WHERE is_command = 1
73
+ AND COALESCE(is_from_bot, 0) = 0`,
74
+ ),
75
+ ]);
76
+ const messageTotals = messageTotalsRows?.[0] || {};
77
+ const commandTotals = commandTotalsRows?.[0] || {};
78
+ usage = {
79
+ ...usage,
80
+ total_messages: Number(messageTotals?.total_messages || 0),
81
+ total_commands: Number(commandTotals?.total_commands || 0),
82
+ };
83
+ } catch (error) {
84
+ usageError = error?.message || 'Falha ao consultar totais de mensagens/comandos';
85
+ }
86
+
87
+ const hostCpuPercent = Number(system.usoCpuPercentual);
88
+ const hostMemoryPercent = Number(system.usoMemoriaPercentual);
89
+ const statusReasons = [];
90
+ if (!botConnected) statusReasons.push('bot_disconnected');
91
+ if (!prometheus) statusReasons.push('metrics_unavailable');
92
+ if (Number.isFinite(hostCpuPercent) && hostCpuPercent >= 90) statusReasons.push('host_cpu_high');
93
+ if (Number.isFinite(hostMemoryPercent) && hostMemoryPercent >= 90) statusReasons.push('host_memory_high');
94
+ const systemStatus = statusReasons.length ? 'degraded' : 'online';
95
+
96
+ return {
97
+ data: {
98
+ system_status: systemStatus,
99
+ status_reasons: statusReasons,
100
+ bot: {
101
+ connected: botConnected,
102
+ connection_status: botConnectionStatus,
103
+ jid: botJid,
104
+ phone: botPhone,
105
+ ready_state: socketReadyState,
106
+ },
107
+ platform,
108
+ usage,
109
+ host: {
110
+ cpu_percent: system.usoCpuPercentual,
111
+ memory_percent: system.usoMemoriaPercentual,
112
+ memory_used: system.memoriaUsada,
113
+ memory_total: system.memoriaTotal,
114
+ uptime: system.uptimeSistema,
115
+ },
116
+ process: {
117
+ uptime: prometheus?.process_uptime || system.uptime,
118
+ node_version: system.versaoNode,
119
+ },
120
+ observability: {
121
+ lag_p99_ms: prometheus?.lag_p99_ms ?? null,
122
+ db_total: prometheus?.db_total ?? null,
123
+ db_slow: prometheus?.db_slow ?? null,
124
+ http_5xx_total: prometheus?.http_5xx_total ?? null,
125
+ http_latency_p95_ms: prometheus?.http_latency_p95_ms ?? null,
126
+ queue_peak: prometheus?.queue_peak ?? null,
127
+ },
128
+ updated_at: new Date().toISOString(),
129
+ },
130
+ meta: {
131
+ metrics_endpoint: metricsEndpoint,
132
+ metrics_ok: Boolean(prometheus),
133
+ metrics_error: prometheusError,
134
+ platform_error: platformError,
135
+ usage_error: usageError,
136
+ },
137
+ };
138
+ };
139
+
140
+ const getSystemSummaryCached = async () => {
141
+ const now = Date.now();
142
+ const hasValue = Boolean(systemSummaryCache.value);
143
+
144
+ if (hasValue && now < systemSummaryCache.expiresAt) {
145
+ return systemSummaryCache.value;
146
+ }
147
+
148
+ if (!systemSummaryCache.pending) {
149
+ systemSummaryCache.pending = withTimeout(buildSystemSummarySnapshot(), 5000)
150
+ .then((payload) => {
151
+ systemSummaryCache.value = payload;
152
+ systemSummaryCache.expiresAt = Date.now() + systemSummaryCacheSeconds * 1000;
153
+ return payload;
154
+ })
155
+ .finally(() => {
156
+ systemSummaryCache.pending = null;
157
+ });
158
+ }
159
+
160
+ if (hasValue) return systemSummaryCache.value;
161
+ return systemSummaryCache.pending;
162
+ };
163
+
164
+ const parseMessageTypeFromRaw = (rawMessage) => {
165
+ try {
166
+ const message = JSON.parse(rawMessage || '{}')?.message || {};
167
+ if (message.conversation || message.extendedTextMessage) return 'texto';
168
+ if (message.imageMessage) return 'imagem';
169
+ if (message.videoMessage) return 'video';
170
+ if (message.audioMessage) return 'audio';
171
+ if (message.stickerMessage) return 'figurinha';
172
+ if (message.documentMessage) return 'documento';
173
+ if (message.locationMessage) return 'localizacao';
174
+ if (message.reactionMessage) return 'reacao';
175
+ return 'outros';
176
+ } catch {
177
+ return 'outros';
178
+ }
179
+ };
180
+
181
+ const formatPtBrInteger = (value) => Number(value || 0).toLocaleString('pt-BR');
182
+
183
+ const extractCommandsFromMenuLine = (line, commandPrefix) => {
184
+ const normalizedLine = String(line || '').trim();
185
+ if (!normalizedLine.startsWith('→')) return [];
186
+
187
+ const commandParts = normalizedLine
188
+ .replace(/^→\s*/, '')
189
+ .split('|')
190
+ .map((part) => part.trim())
191
+ .filter((part) => part.startsWith(commandPrefix));
192
+
193
+ return commandParts
194
+ .map((part) => {
195
+ const normalized = part.replace(/\s{2,}.*/, '').trim();
196
+ const withoutPrefix = normalized.slice(commandPrefix.length).trim();
197
+ if (!withoutPrefix) return '';
198
+
199
+ const tokens = withoutPrefix.split(/\s+/).filter(Boolean);
200
+ const selectedTokens = [];
201
+ for (const token of tokens) {
202
+ if (!/^[a-z0-9_-]+$/i.test(token)) break;
203
+ selectedTokens.push(token);
204
+ }
205
+ if (!selectedTokens.length) return '';
206
+ return `${commandPrefix}${selectedTokens.join(' ')}`;
207
+ })
208
+ .filter(Boolean);
209
+ };
210
+
211
+ const collectAvailableMenuCommands = (commandPrefix = readmeCommandPrefix) => {
212
+ const sections = [buildMenuCaption('OmniZap', commandPrefix), buildStickerMenu(commandPrefix), buildMediaMenu(commandPrefix), buildQuoteMenu(commandPrefix), buildAnimeMenu(commandPrefix), buildAiMenu(commandPrefix), buildStatsMenu(commandPrefix), buildAdminMenu(commandPrefix)];
213
+
214
+ const commands = new Set();
215
+ for (const section of sections) {
216
+ for (const line of String(section || '').split('\n')) {
217
+ const extracted = extractCommandsFromMenuLine(line, commandPrefix);
218
+ for (const command of extracted) {
219
+ commands.add(command);
220
+ }
221
+ }
222
+ }
223
+
224
+ return Array.from(commands).sort((left, right) => left.localeCompare(right, 'pt-BR'));
225
+ };
226
+
227
+ const renderReadmeSnapshotMarkdown = ({ generatedAt, totals, topMessageTypes, commands }) => {
228
+ const typeRows = topMessageTypes.length ? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`) : ['| `outros` | 0 |'];
229
+
230
+ const commandInline = commands.length ? commands.map((command) => `\`${command}\``).join(' · ') : 'Nenhum comando identificado no menu atual.';
231
+
232
+ return ['### Snapshot do Sistema', '', `> Atualizado em \`${generatedAt}\` | cache \`${readmeSummaryCacheSeconds}s\``, '', '| Métrica | Valor |', '| --- | ---: |', `| Usuários (lid_map) | ${formatPtBrInteger(totals.total_users)} |`, `| Grupos | ${formatPtBrInteger(totals.total_groups)} |`, `| Packs | ${formatPtBrInteger(totals.total_packs)} |`, `| Stickers | ${formatPtBrInteger(totals.total_stickers)} |`, `| Mensagens registradas | ${formatPtBrInteger(totals.total_messages)} |`, '', `#### Tipos de mensagem mais usados (amostra: ${formatPtBrInteger(totals.message_types_sample_size)})`, '| Tipo | Total |', '| --- | ---: |', ...typeRows, '', `<details><summary>Comandos disponíveis (${formatPtBrInteger(commands.length)})</summary>`, '', commandInline, '', '</details>', ''].join('\n');
233
+ };
234
+
235
+ const buildReadmeSummarySnapshot = async () => {
236
+ const [lidMapTotalsRows, chatsTotalsRows, groupsMetadataTotalsRows, packTotalsRows, stickerTotalsRows, messageTotalsRows, messageTypeRows] = await Promise.all([
237
+ executeQuery(`SELECT COUNT(*) AS total_users FROM ${tables.LID_MAP}`),
238
+ executeQuery(
239
+ `SELECT
240
+ COUNT(*) AS total_chats,
241
+ SUM(CASE WHEN id LIKE '%@g.us' THEN 1 ELSE 0 END) AS total_groups
242
+ FROM ${tables.CHATS}`,
243
+ ),
244
+ executeQuery(`SELECT COUNT(*) AS total_groups FROM ${tables.GROUPS_METADATA}`),
245
+ executeQuery(`SELECT COUNT(*) AS total_packs FROM ${tables.STICKER_PACK} WHERE deleted_at IS NULL`),
246
+ executeQuery(`SELECT COUNT(*) AS total_stickers FROM ${tables.STICKER_ASSET}`),
247
+ executeQuery(`SELECT COUNT(*) AS total_messages FROM ${tables.MESSAGES}`),
248
+ executeQuery(
249
+ `SELECT raw_message
250
+ FROM ${tables.MESSAGES}
251
+ WHERE raw_message IS NOT NULL
252
+ ORDER BY id DESC
253
+ LIMIT ${readmeMessageTypeSampleLimit}`,
254
+ ),
255
+ ]);
256
+
257
+ const lidMapTotals = lidMapTotalsRows?.[0] || {};
258
+ const chatsTotals = chatsTotalsRows?.[0] || {};
259
+ const groupsMetadataTotals = groupsMetadataTotalsRows?.[0] || {};
260
+ const packTotals = packTotalsRows?.[0] || {};
261
+ const stickerTotals = stickerTotalsRows?.[0] || {};
262
+ const messageTotals = messageTotalsRows?.[0] || {};
263
+
264
+ const totalGroupsFromChats = Number(chatsTotals?.total_groups || 0);
265
+ const totalGroupsFromMetadata = Number(groupsMetadataTotals?.total_groups || 0);
266
+ const totalGroups = Math.max(totalGroupsFromChats, totalGroupsFromMetadata);
267
+ const totalMessages = Number(messageTotals?.total_messages || 0);
268
+
269
+ const typeCounts = new Map();
270
+ const sampledMessages = Array.isArray(messageTypeRows) ? messageTypeRows.length : 0;
271
+ for (const row of messageTypeRows || []) {
272
+ const type = parseMessageTypeFromRaw(row?.raw_message);
273
+ typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
274
+ }
275
+ const topMessageTypes = Array.from(typeCounts.entries())
276
+ .map(([type, total]) => ({ type, total: Number(total || 0) }))
277
+ .sort((left, right) => Number(right.total || 0) - Number(left.total || 0))
278
+ .slice(0, 8);
279
+
280
+ const commands = collectAvailableMenuCommands(readmeCommandPrefix);
281
+ const generatedAt = new Date().toISOString();
282
+
283
+ const totals = {
284
+ total_users: Number(lidMapTotals?.total_users || 0),
285
+ total_groups: totalGroups,
286
+ total_groups_from_chats: totalGroupsFromChats,
287
+ total_groups_from_metadata: totalGroupsFromMetadata,
288
+ total_chats: Number(chatsTotals?.total_chats || 0),
289
+ total_packs: Number(packTotals?.total_packs || 0),
290
+ total_stickers: Number(stickerTotals?.total_stickers || 0),
291
+ total_messages: totalMessages,
292
+ message_types_sample_size: sampledMessages,
293
+ message_types_total_coverage_percent: totalMessages > 0 ? Number(((sampledMessages / totalMessages) * 100).toFixed(2)) : 0,
294
+ };
295
+
296
+ const markdown = renderReadmeSnapshotMarkdown({
297
+ generatedAt,
298
+ totals,
299
+ topMessageTypes,
300
+ commands,
301
+ });
302
+
303
+ return {
304
+ data: {
305
+ generated_at: generatedAt,
306
+ cache_seconds: readmeSummaryCacheSeconds,
307
+ command_prefix: readmeCommandPrefix,
308
+ totals,
309
+ top_message_types: topMessageTypes,
310
+ commands,
311
+ markdown,
312
+ },
313
+ };
314
+ };
315
+
316
+ const getReadmeSummaryCached = async () => {
317
+ const now = Date.now();
318
+ const hasValue = Boolean(readmeSummaryCache.value);
319
+
320
+ if (hasValue && now < readmeSummaryCache.expiresAt) {
321
+ return readmeSummaryCache.value;
322
+ }
323
+
324
+ if (!readmeSummaryCache.pending) {
325
+ readmeSummaryCache.pending = withTimeout(buildReadmeSummarySnapshot(), 7000)
326
+ .then((payload) => {
327
+ readmeSummaryCache.value = payload;
328
+ readmeSummaryCache.expiresAt = Date.now() + readmeSummaryCacheSeconds * 1000;
329
+ return payload;
330
+ })
331
+ .finally(() => {
332
+ readmeSummaryCache.pending = null;
333
+ });
334
+ }
335
+
336
+ if (hasValue) return readmeSummaryCache.value;
337
+ return readmeSummaryCache.pending;
338
+ };
339
+
340
+ const resolveBotUserCandidates = (activeSocket) => {
341
+ const candidates = new Set();
342
+ const botJidFromSocket = resolveActiveSocketBotJid(activeSocket);
343
+ const botUserFromSocket = getJidUser(botJidFromSocket || '');
344
+ if (botUserFromSocket) candidates.add(String(botUserFromSocket).trim());
345
+ const botPhoneFromCatalog = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
346
+ if (botPhoneFromCatalog) candidates.add(botPhoneFromCatalog);
347
+
348
+ const envCandidates = [process.env.WHATSAPP_BOT_NUMBER, process.env.BOT_NUMBER, process.env.PHONE_NUMBER, process.env.BOT_PHONE_NUMBER];
349
+
350
+ for (const candidate of envCandidates) {
351
+ const digits = String(candidate || '').replace(/\D+/g, '');
352
+ if (digits) candidates.add(digits);
353
+ }
354
+
355
+ return Array.from(candidates).filter((value) => value.length >= 8);
356
+ };
357
+
358
+ const isSenderFromAnyBotUser = (senderId, botUsers) => {
359
+ const normalizedSender = String(senderId || '').trim();
360
+ if (!normalizedSender) return false;
361
+ return botUsers.some((botUser) => {
362
+ const safe = String(botUser || '').trim();
363
+ if (!safe) return false;
364
+ return normalizedSender === `${safe}@s.whatsapp.net` || normalizedSender.startsWith(`${safe}:`) || normalizedSender.startsWith(`${safe}@`);
365
+ });
366
+ };
367
+
368
+ const sanitizeRankingPayloadByBot = (payload, botUsers) => {
369
+ const sourceRows = Array.isArray(payload?.rows) ? payload.rows : [];
370
+ const filteredRows = sourceRows.filter((row) => !isSenderFromAnyBotUser(row?.sender_id, botUsers));
371
+ const normalizedRows = filteredRows.slice(0, Number(payload?.limit || 5)).map((row, index) => ({
372
+ ...row,
373
+ position: index + 1,
374
+ }));
375
+ const totalMessages = Number(payload?.total_messages || 0);
376
+ const topTotal = normalizedRows.reduce((acc, row) => acc + Number(row?.total_messages || 0), 0);
377
+ const topShare = totalMessages > 0 ? Number(((topTotal / totalMessages) * 100).toFixed(2)) : 0;
378
+
379
+ return {
380
+ ...payload,
381
+ rows: normalizedRows,
382
+ top_share_percent: topShare,
383
+ };
384
+ };
385
+
386
+ const extractPushNameFromRaw = (rawMessage) => {
387
+ try {
388
+ const parsed = JSON.parse(rawMessage || '{}');
389
+ const direct = String(parsed?.pushName || '').trim();
390
+ if (direct) return direct;
391
+
392
+ const nested = String(parsed?.message?.extendedTextMessage?.contextInfo?.participantName || '').trim();
393
+ if (nested) return nested;
394
+ } catch {
395
+ return '';
396
+ }
397
+ return '';
398
+ };
399
+
400
+ const resolveRankingDisplayName = async (senderId) => {
401
+ if (!senderId) return 'Desconhecido';
402
+ const fallback = `@${String(getJidUser(senderId) || senderId).trim()}`;
403
+ try {
404
+ const rows = await executeQuery(
405
+ `SELECT raw_message FROM ${tables.MESSAGES}
406
+ WHERE sender_id = ?
407
+ AND raw_message IS NOT NULL
408
+ ORDER BY id DESC
409
+ LIMIT 12`,
410
+ [senderId],
411
+ );
412
+ for (const row of rows) {
413
+ const name = extractPushNameFromRaw(row?.raw_message);
414
+ if (name) return name;
415
+ }
416
+ } catch {
417
+ return fallback;
418
+ }
419
+ return fallback;
420
+ };
421
+
422
+ const resolveRankingAvatarUrl = async (senderId) => {
423
+ if (!senderId) return null;
424
+ const normalized = normalizeJid(senderId) || senderId;
425
+ try {
426
+ return await profilePictureUrlFromActiveSocket(normalized, 'image');
427
+ } catch {
428
+ return null;
429
+ }
430
+ };
431
+
432
+ const buildGlobalRankingSummary = async () => {
433
+ const limit = 5;
434
+ const queryLimit = 12;
435
+ const sampleLimit = 50000;
436
+ const activeSocket = getActiveSocket();
437
+ const botUsers = resolveBotUserCandidates(activeSocket);
438
+
439
+ const whereClauses = ['sender_id IS NOT NULL'];
440
+ const params = [];
441
+ for (const botUser of botUsers) {
442
+ whereClauses.push('sender_id <> ?');
443
+ params.push(`${botUser}@s.whatsapp.net`);
444
+ whereClauses.push('sender_id NOT LIKE ?');
445
+ whereClauses.push('sender_id NOT LIKE ?');
446
+ params.push(`${botUser}@%`, `${botUser}:%`);
447
+ }
448
+
449
+ const where = whereClauses.join(' AND ');
450
+ const recentScopeSql = `SELECT id, sender_id, timestamp, raw_message FROM ${tables.MESSAGES} WHERE ${where} ORDER BY id DESC LIMIT ${sampleLimit}`;
451
+
452
+ const [totalRow] = await executeQuery(`SELECT COUNT(*) AS total FROM (${recentScopeSql}) recent_scope`, params);
453
+ const totalMessages = Number(totalRow?.total || 0);
454
+
455
+ const rows = await executeQuery(
456
+ `SELECT
457
+ recent_scope.sender_id,
458
+ CONCAT('@', SUBSTRING_INDEX(recent_scope.sender_id, '@', 1)) AS display_name,
459
+ COUNT(*) AS total_messages,
460
+ MAX(
461
+ CASE
462
+ WHEN recent_scope.timestamp > 1000000000000 THEN FROM_UNIXTIME(recent_scope.timestamp / 1000)
463
+ WHEN recent_scope.timestamp > 1000000000 THEN FROM_UNIXTIME(recent_scope.timestamp)
464
+ ELSE recent_scope.timestamp
465
+ END
466
+ ) AS last_message
467
+ FROM (${recentScopeSql}) recent_scope
468
+ GROUP BY recent_scope.sender_id
469
+ ORDER BY total_messages DESC
470
+ LIMIT ${queryLimit}`,
471
+ params,
472
+ );
473
+
474
+ const typeRows = await executeQuery(
475
+ `SELECT recent_scope.raw_message
476
+ FROM (${recentScopeSql}) recent_scope
477
+ WHERE recent_scope.raw_message IS NOT NULL
478
+ ORDER BY recent_scope.id DESC
479
+ LIMIT 300`,
480
+ params,
481
+ );
482
+
483
+ const typeCounts = new Map();
484
+ for (const row of typeRows) {
485
+ const type = parseMessageTypeFromRaw(row?.raw_message);
486
+ typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
487
+ }
488
+ const sortedTypes = Array.from(typeCounts.entries()).sort((left, right) => right[1] - left[1]);
489
+ const topType = sortedTypes[0]?.[0] || null;
490
+ const topTypeCount = Number(sortedTypes[0]?.[1] || 0);
491
+
492
+ const topTotal = rows.reduce((acc, row) => acc + Number(row?.total_messages || 0), 0);
493
+ const topShare = totalMessages > 0 ? Number(((topTotal / totalMessages) * 100).toFixed(2)) : 0;
494
+
495
+ const rowsWithoutBot = rows.filter((row) => !isSenderFromAnyBotUser(row?.sender_id, botUsers)).slice(0, limit);
496
+
497
+ const rowsEnriched = await Promise.all(
498
+ rowsWithoutBot.map(async (row, index) => {
499
+ const total = Number(row?.total_messages || 0);
500
+ const percent = totalMessages > 0 ? Number(((total / totalMessages) * 100).toFixed(2)) : 0;
501
+ const senderId = row?.sender_id || null;
502
+ const displayName = await resolveRankingDisplayName(senderId);
503
+ const avatarUrl = await resolveRankingAvatarUrl(senderId);
504
+ return {
505
+ position: index + 1,
506
+ sender_id: senderId,
507
+ mention_id: senderId,
508
+ display_name: displayName || row?.display_name || senderId || 'Desconhecido',
509
+ avatar_url: avatarUrl,
510
+ total_messages: total,
511
+ percent_of_total: percent,
512
+ last_message: row?.last_message ? new Date(row.last_message).toISOString() : null,
513
+ };
514
+ }),
515
+ );
516
+
517
+ return {
518
+ limit,
519
+ sample_limit: sampleLimit,
520
+ total_messages: totalMessages,
521
+ top_share_percent: topShare,
522
+ top_type: topType,
523
+ top_type_count: topTypeCount,
524
+ rows: rowsEnriched,
525
+ updated_at: new Date().toISOString(),
526
+ };
527
+ };
528
+
529
+ const getGlobalRankingSummaryCached = async () => {
530
+ const now = Date.now();
531
+ const hasValue = Boolean(globalRankCache.value);
532
+
533
+ if (hasValue && now < globalRankCache.expiresAt) {
534
+ return globalRankCache.value;
535
+ }
536
+
537
+ if (!globalRankCache.pending) {
538
+ globalRankCache.pending = withTimeout(buildGlobalRankingSummary(), 5000)
539
+ .then((data) => {
540
+ globalRankCache.value = data;
541
+ globalRankCache.expiresAt = Date.now() + globalRankRefreshSeconds * 1000;
542
+ return data;
543
+ })
544
+ .finally(() => {
545
+ globalRankCache.pending = null;
546
+ });
547
+ }
548
+
549
+ if (hasValue) {
550
+ return globalRankCache.value;
551
+ }
552
+
553
+ return globalRankCache.pending;
554
+ };
555
+
556
+ const scheduleGlobalRankingPreload = () => {
557
+ if (globalRankRefreshTimer) return;
558
+
559
+ getGlobalRankingSummaryCached().catch((error) => {
560
+ logger.warn('Falha no preload inicial do ranking global.', {
561
+ action: 'global_ranking_preload_init_error',
562
+ error: error?.message,
563
+ });
564
+ });
565
+
566
+ globalRankRefreshTimer = setInterval(() => {
567
+ globalRankCache.expiresAt = 0;
568
+ getGlobalRankingSummaryCached().catch((error) => {
569
+ logger.warn('Falha ao atualizar cache do ranking global em background.', {
570
+ action: 'global_ranking_preload_refresh_error',
571
+ error: error?.message,
572
+ });
573
+ });
574
+ }, globalRankRefreshSeconds * 1000);
575
+
576
+ if (typeof globalRankRefreshTimer?.unref === 'function') {
577
+ globalRankRefreshTimer.unref();
578
+ }
579
+ };
580
+
581
+ const buildLastSevenUtcDateKeys = () => {
582
+ const now = new Date();
583
+ const todayUtc = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
584
+ return Array.from({ length: 7 }).map((_, index) => {
585
+ const date = new Date(todayUtc - (6 - index) * 24 * 60 * 60 * 1000);
586
+ return date.toISOString().slice(0, 10);
587
+ });
588
+ };
589
+
590
+ const toUtcDayKey = (value) => {
591
+ if (!value) return '';
592
+ if (value instanceof Date) return value.toISOString().slice(0, 10);
593
+ const text = String(value).trim();
594
+ if (/^\d{4}-\d{2}-\d{2}/.test(text)) return text.slice(0, 10);
595
+ const parsed = Date.parse(text);
596
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString().slice(0, 10) : '';
597
+ };
598
+
599
+ const mapRowsByDayKey = (rows, valueField = 'total') => {
600
+ const map = new Map();
601
+ (Array.isArray(rows) ? rows : []).forEach((row) => {
602
+ const dayKey = toUtcDayKey(row?.day_key);
603
+ if (!dayKey) return;
604
+ map.set(dayKey, Number(row?.[valueField] || 0));
605
+ });
606
+ return map;
607
+ };
608
+
609
+ const buildMarketplaceGlobalStatsSnapshot = async () => {
610
+ const visiblePublishedVisibility = ['public', 'unlisted'];
611
+ const placeholders = visiblePublishedVisibility.map(() => '?').join(', ');
612
+ const dayKeys = buildLastSevenUtcDateKeys();
613
+ const dayFilterSql = `UTC_DATE() - INTERVAL 6 DAY`;
614
+
615
+ const [packTotalsRows, stickerTotalsRows, stickersWithoutPackRows, engagementTotalsRows, dailyPacksRows, dailyStickersRows, dailyInteractionRows] = await Promise.all([
616
+ executeQuery(
617
+ `SELECT
618
+ COUNT(*) AS total_packs,
619
+ COUNT(DISTINCT publisher) AS creators_total,
620
+ SUM(CASE WHEN created_at >= (UTC_TIMESTAMP() - INTERVAL 7 DAY) THEN 1 ELSE 0 END) AS packs_last_7_days
621
+ FROM ${tables.STICKER_PACK}
622
+ WHERE deleted_at IS NULL
623
+ AND status = 'published'
624
+ AND COALESCE(pack_status, 'ready') = 'ready'
625
+ AND visibility IN (${placeholders})`,
626
+ visiblePublishedVisibility,
627
+ ),
628
+ executeQuery(`SELECT COUNT(*) AS total_stickers FROM ${tables.STICKER_ASSET}`),
629
+ executeQuery(
630
+ `SELECT COUNT(*) AS stickers_without_pack
631
+ FROM ${tables.STICKER_ASSET} a
632
+ LEFT JOIN ${tables.STICKER_PACK_ITEM} i ON i.sticker_id = a.id
633
+ WHERE i.sticker_id IS NULL`,
634
+ ),
635
+ executeQuery(
636
+ `SELECT
637
+ COALESCE(SUM(e.open_count), 0) AS total_clicks,
638
+ COALESCE(SUM(e.like_count), 0) AS total_likes
639
+ FROM ${tables.STICKER_PACK_ENGAGEMENT} e
640
+ INNER JOIN ${tables.STICKER_PACK} p ON p.id = e.pack_id
641
+ WHERE p.deleted_at IS NULL
642
+ AND p.status = 'published'
643
+ AND COALESCE(p.pack_status, 'ready') = 'ready'
644
+ AND p.visibility IN (${placeholders})`,
645
+ visiblePublishedVisibility,
646
+ ),
647
+ executeQuery(
648
+ `SELECT DATE(created_at) AS day_key, COUNT(*) AS total
649
+ FROM ${tables.STICKER_PACK}
650
+ WHERE deleted_at IS NULL
651
+ AND status = 'published'
652
+ AND COALESCE(pack_status, 'ready') = 'ready'
653
+ AND visibility IN (${placeholders})
654
+ AND created_at >= (${dayFilterSql})
655
+ GROUP BY DATE(created_at)`,
656
+ visiblePublishedVisibility,
657
+ ),
658
+ executeQuery(
659
+ `SELECT DATE(created_at) AS day_key, COUNT(*) AS total
660
+ FROM ${tables.STICKER_ASSET}
661
+ WHERE created_at >= (${dayFilterSql})
662
+ GROUP BY DATE(created_at)`,
663
+ ),
664
+ executeQuery(
665
+ `SELECT DATE(ev.created_at) AS day_key, ev.interaction, COUNT(*) AS total
666
+ FROM ${tables.STICKER_PACK_INTERACTION_EVENT} ev
667
+ INNER JOIN ${tables.STICKER_PACK} p ON p.id = ev.pack_id
668
+ WHERE ev.created_at >= (${dayFilterSql})
669
+ AND ev.interaction IN ('open', 'like')
670
+ AND p.deleted_at IS NULL
671
+ AND p.status = 'published'
672
+ AND COALESCE(p.pack_status, 'ready') = 'ready'
673
+ AND p.visibility IN (${placeholders})
674
+ GROUP BY DATE(ev.created_at), ev.interaction`,
675
+ visiblePublishedVisibility,
676
+ ),
677
+ ]);
678
+
679
+ const packTotals = packTotalsRows?.[0] || {};
680
+ const stickerTotals = stickerTotalsRows?.[0] || {};
681
+ const stickersWithoutPack = stickersWithoutPackRows?.[0] || {};
682
+ const engagementTotals = engagementTotalsRows?.[0] || {};
683
+
684
+ const dailyPacksByDay = mapRowsByDayKey(dailyPacksRows, 'total');
685
+ const dailyStickersByDay = mapRowsByDayKey(dailyStickersRows, 'total');
686
+ const dailyOpensByDay = new Map();
687
+ const dailyLikesByDay = new Map();
688
+ (Array.isArray(dailyInteractionRows) ? dailyInteractionRows : []).forEach((row) => {
689
+ const dayKey = toUtcDayKey(row?.day_key);
690
+ const interaction = String(row?.interaction || '')
691
+ .trim()
692
+ .toLowerCase();
693
+ const total = Number(row?.total || 0);
694
+ if (!dayKey) return;
695
+ if (interaction === 'open') dailyOpensByDay.set(dayKey, total);
696
+ if (interaction === 'like') dailyLikesByDay.set(dayKey, total);
697
+ });
698
+
699
+ const seriesLast7Days = dayKeys.map((day) => ({
700
+ date: day,
701
+ packs_published: Number(dailyPacksByDay.get(day) || 0),
702
+ stickers_created: Number(dailyStickersByDay.get(day) || 0),
703
+ clicks: Number(dailyOpensByDay.get(day) || 0),
704
+ likes: Number(dailyLikesByDay.get(day) || 0),
705
+ }));
706
+
707
+ const likesLast7Days = seriesLast7Days.reduce((acc, row) => acc + Number(row.likes || 0), 0);
708
+ const clicksLast7Days = seriesLast7Days.reduce((acc, row) => acc + Number(row.clicks || 0), 0);
709
+
710
+ return {
711
+ total_packs: Number(packTotals?.total_packs || 0),
712
+ total_stickers: Number(stickerTotals?.total_stickers || 0),
713
+ total_clicks: Number(engagementTotals?.total_clicks || 0),
714
+ total_likes: Number(engagementTotals?.total_likes || 0),
715
+ packs_last_7_days: Number(packTotals?.packs_last_7_days || 0),
716
+ stickers_without_pack: Number(stickersWithoutPack?.stickers_without_pack || 0),
717
+ creators_total: Number(packTotals?.creators_total || 0),
718
+ clicks_last_7_days: Number(clicksLast7Days || 0),
719
+ likes_last_7_days: Number(likesLast7Days || 0),
720
+ series_last_7_days: seriesLast7Days,
721
+ updated_at: new Date().toISOString(),
722
+ };
723
+ };
724
+
725
+ const getMarketplaceGlobalStatsCached = async () => {
726
+ const now = Date.now();
727
+ const hasValue = Boolean(marketplaceGlobalStatsCache.value);
728
+ if (hasValue && now < marketplaceGlobalStatsCache.expiresAt) {
729
+ return marketplaceGlobalStatsCache.value;
730
+ }
731
+
732
+ if (!marketplaceGlobalStatsCache.pending) {
733
+ marketplaceGlobalStatsCache.pending = withTimeout(buildMarketplaceGlobalStatsSnapshot(), 5000)
734
+ .then((data) => {
735
+ marketplaceGlobalStatsCache.value = data;
736
+ marketplaceGlobalStatsCache.expiresAt = Date.now() + marketplaceGlobalStatsCacheSeconds * 1000;
737
+ return data;
738
+ })
739
+ .finally(() => {
740
+ marketplaceGlobalStatsCache.pending = null;
741
+ });
742
+ }
743
+
744
+ if (hasValue) return marketplaceGlobalStatsCache.value;
745
+ return marketplaceGlobalStatsCache.pending;
746
+ };
747
+
748
+ return {
749
+ withTimeout,
750
+ getSystemSummaryCached,
751
+ getReadmeSummaryCached,
752
+ resolveBotUserCandidates,
753
+ sanitizeRankingPayloadByBot,
754
+ getGlobalRankingSummaryCached,
755
+ scheduleGlobalRankingPreload,
756
+ getMarketplaceGlobalStatsCached,
757
+ };
758
+ };