@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,653 @@
1
+ import { createCanvas } from 'canvas';
2
+ import { exec } from 'node:child_process';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { URL } from 'node:url';
6
+ import { promisify } from 'node:util';
7
+ import logger from '#logger';
8
+
9
+ import { v4 as uuidv4 } from 'uuid';
10
+
11
+ import { convertToWebp } from './convertToWebp.js';
12
+ import { addStickerMetadata } from './addStickerMetadata.js';
13
+ import { getJidUser } from '../../config/index.js';
14
+ import { sendAndStore } from '../../services/messaging/messagePersistenceService.js';
15
+ import { addStickerToAutoPack } from '../stickerPackModule/autoPackCollectorRuntime.js';
16
+ import { getStickerUsageText } from './stickerConfigRuntime.js';
17
+
18
+ /**
19
+ * Constantes limitadoras
20
+ */
21
+ const MAX_CHARACTERS = 80;
22
+ const MAX_LINES = 4;
23
+
24
+ const TEMP_DIR = path.join(process.cwd(), 'temp', 'stickers');
25
+ const BLINK_FPS = 15;
26
+ const BLINK_DURATION_MS = 5000;
27
+ const BLINK_FREQ_HZ = 5;
28
+ const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
29
+ const AUTO_PACK_NOTICE_ENABLED = process.env.STICKER_PACK_AUTO_COLLECT_NOTIFY !== 'false';
30
+ const AUTO_PACK_MAX_ITEMS = Math.max(1, Number(process.env.STICKER_PACK_MAX_ITEMS) || 30);
31
+ const STICKER_WEB_PATH = normalizeBasePath(process.env.STICKER_WEB_PATH, '/stickers');
32
+ const STICKER_WEB_ORIGIN = resolveStickerWebOrigin();
33
+
34
+ function normalizeBasePath(value, fallback) {
35
+ const raw = String(value || '').trim() || fallback;
36
+ const withLeadingSlash = raw.startsWith('/') ? raw : `/${raw}`;
37
+ const withoutTrailingSlash = withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/') ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
38
+ return withoutTrailingSlash || fallback;
39
+ }
40
+
41
+ function normalizeOrigin(value) {
42
+ const raw = String(value || '').trim();
43
+ if (!raw) return null;
44
+
45
+ try {
46
+ const parsed = new URL(raw);
47
+ if (!parsed.protocol || !parsed.host) return null;
48
+ return parsed.origin;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function resolveStickerWebOrigin() {
55
+ const candidates = [process.env.STICKER_WEB_ORIGIN, process.env.APP_BASE_URL, process.env.PUBLIC_BASE_URL, process.env.SITE_URL, process.env.WEB_URL, process.env.BASE_URL, process.env.STICKER_DEFAULT_PACK_NAME];
56
+
57
+ for (const candidate of candidates) {
58
+ const normalized = normalizeOrigin(candidate);
59
+ if (normalized) return normalized;
60
+ }
61
+
62
+ return 'https://omnizap.shop';
63
+ }
64
+
65
+ function buildPackWebUrl(packKey) {
66
+ const normalizedPackKey = String(packKey || '').trim();
67
+ if (!normalizedPackKey) return null;
68
+
69
+ return `${STICKER_WEB_ORIGIN}${STICKER_WEB_PATH}/${encodeURIComponent(normalizedPackKey)}`;
70
+ }
71
+
72
+ function isPackPubliclyVisible(pack) {
73
+ const visibility = String(pack?.visibility || '')
74
+ .trim()
75
+ .toLowerCase();
76
+ const status = String(pack?.status || 'published')
77
+ .trim()
78
+ .toLowerCase();
79
+ const packStatus = String(pack?.pack_status || 'ready')
80
+ .trim()
81
+ .toLowerCase();
82
+ return (visibility === 'public' || visibility === 'unlisted') && status === 'published' && packStatus === 'ready';
83
+ }
84
+
85
+ const execProm = promisify(exec);
86
+
87
+ const COLOR_ALIASES = {
88
+ branco: 'white',
89
+ white: 'white',
90
+ preto: 'black',
91
+ black: 'black',
92
+ vermelho: 'red',
93
+ red: 'red',
94
+ verde: 'green',
95
+ green: 'green',
96
+ azul: 'blue',
97
+ blue: 'blue',
98
+ amarelo: 'yellow',
99
+ yellow: 'yellow',
100
+ rosa: 'pink',
101
+ pink: 'pink',
102
+ roxo: 'purple',
103
+ purple: 'purple',
104
+ laranja: 'orange',
105
+ orange: 'orange',
106
+ };
107
+
108
+ /**
109
+ * Extrai uma cor no formato "-cor" no final do texto e retorna o texto limpo.
110
+ *
111
+ * @param {string} rawText
112
+ * @param {string} fallbackColor
113
+ * @returns {{ text: string, color: string }}
114
+ */
115
+ function parseColorFlag(rawText, fallbackColor) {
116
+ const trimmed = rawText.trim();
117
+ if (!trimmed) return { text: rawText, color: fallbackColor };
118
+
119
+ const match = trimmed.match(/(?:^|\s)-([a-zA-Z]+)\s*$/);
120
+ if (!match) return { text: rawText, color: fallbackColor };
121
+
122
+ const colorKey = match[1].toLowerCase();
123
+ const mapped = COLOR_ALIASES[colorKey];
124
+ if (!mapped) return { text: rawText, color: fallbackColor };
125
+
126
+ const cleanedText = trimmed.slice(0, match.index).trimEnd();
127
+ return { text: cleanedText, color: mapped };
128
+ }
129
+
130
+ function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX) {
131
+ if (!result || result.status === 'skipped') {
132
+ return null;
133
+ }
134
+
135
+ const pack = result.pack || {};
136
+ const packName = pack.name || 'Minhas Figurinhas';
137
+ const packIdentifier = pack.pack_key || pack.id || '<pack>';
138
+ const packWebUrl = isPackPubliclyVisible(pack) ? buildPackWebUrl(pack.pack_key) : null;
139
+ const profileUrl = `${STICKER_WEB_ORIGIN}${STICKER_WEB_PATH}/profile`;
140
+ const packCommandTarget = String(packIdentifier || '').trim() || '<pack>';
141
+ const itemCount = Array.isArray(pack.items) ? pack.items.length : Number(pack.sticker_count || 0);
142
+ const countLabel = itemCount > 0 ? ` (${itemCount}/${AUTO_PACK_MAX_ITEMS})` : '';
143
+
144
+ if (result.status === 'duplicate') {
145
+ const duplicateLines = [`ℹ️ Essa figurinha já estava no pack automático *${packName}*.`, `Use *${commandPrefix}pack info ${packIdentifier}* para ver o pack ou *${commandPrefix}pack send ${packIdentifier}* para enviar.`];
146
+ if (packWebUrl) {
147
+ duplicateLines.push(`🌐 Link do pack no site: ${packWebUrl}`);
148
+ } else {
149
+ duplicateLines.push(`🔒 Pack privado/não publicado. Abra no painel: ${profileUrl}`);
150
+ }
151
+ return duplicateLines.join('\n');
152
+ }
153
+
154
+ const savedLines = [`✅ Figurinha adicionada ao pack *${packName}*${countLabel}.`, '', `📋 Gerencie seus packs com *${commandPrefix}pack list*.`, `🚀 Envie agora com *${commandPrefix}pack send ${packCommandTarget}*.`];
155
+ if (packWebUrl) {
156
+ savedLines.push(`🌐 Veja no site: ${packWebUrl}`);
157
+ } else {
158
+ savedLines.push(`🔒 Pack privado/não publicado. Gerencie em: ${profileUrl}`);
159
+ }
160
+ return savedLines.join('\n');
161
+ }
162
+
163
+ async function notifyAutoPackCollection({ sock, remoteJid, messageInfo, expirationMessage, result, commandPrefix }) {
164
+ if (!AUTO_PACK_NOTICE_ENABLED) return;
165
+
166
+ const noticeText = buildAutoPackNoticeText(result, commandPrefix);
167
+ if (!noticeText) return;
168
+
169
+ await sendAndStore(
170
+ sock,
171
+ remoteJid,
172
+ { text: noticeText },
173
+ {
174
+ quoted: messageInfo,
175
+ ephemeralExpiration: expirationMessage,
176
+ },
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Desenha texto centralizado no canvas com quebra de linha e ajuste de fonte.
182
+ *
183
+ * @param {CanvasRenderingContext2D} ctx
184
+ * @param {string} text
185
+ * @param {string} color
186
+ * @param {{ glow?: boolean }} [options]
187
+ */
188
+ function drawTextOnCanvas(ctx, text, color, { glow = false } = {}) {
189
+ const width = ctx.canvas.width;
190
+ const height = ctx.canvas.height;
191
+
192
+ const maxWidth = 460;
193
+
194
+ ctx.fillStyle = color ? color : 'black';
195
+ ctx.textAlign = 'center';
196
+ ctx.textBaseline = 'middle';
197
+
198
+ const charCount = text.replace(/\s+/g, '').length;
199
+ let fontSize = 56;
200
+ if (charCount <= 6) {
201
+ fontSize = 96;
202
+ } else if (charCount <= 10) {
203
+ fontSize = 80;
204
+ } else if (charCount <= 16) {
205
+ fontSize = 68;
206
+ }
207
+ ctx.font = `bold ${fontSize}px Arial`;
208
+
209
+ const wrapText = () => {
210
+ const words = text.split(' ');
211
+ const lines = [];
212
+ let currentLine = '';
213
+
214
+ const pushLine = () => {
215
+ if (currentLine) lines.push(currentLine.trim());
216
+ currentLine = '';
217
+ };
218
+
219
+ for (const word of words) {
220
+ const testLine = currentLine + word + ' ';
221
+ const testWidth = ctx.measureText(testLine).width;
222
+
223
+ if (testWidth <= maxWidth) {
224
+ currentLine = testLine;
225
+ continue;
226
+ }
227
+
228
+ if (currentLine) {
229
+ pushLine();
230
+ }
231
+
232
+ if (ctx.measureText(word).width <= maxWidth) {
233
+ currentLine = word + ' ';
234
+ continue;
235
+ }
236
+
237
+ let chunk = '';
238
+ for (const ch of word) {
239
+ const chunkTest = chunk + ch;
240
+ if (ctx.measureText(chunkTest).width > maxWidth && chunk) {
241
+ lines.push(chunk);
242
+ chunk = ch;
243
+ } else {
244
+ chunk = chunkTest;
245
+ }
246
+ }
247
+ if (chunk) lines.push(chunk);
248
+ }
249
+
250
+ pushLine();
251
+ return lines;
252
+ };
253
+
254
+ let lines = wrapText();
255
+ while (lines.length * fontSize > height - 40 || lines.some((line) => ctx.measureText(line).width > maxWidth)) {
256
+ fontSize -= 4;
257
+ ctx.font = `bold ${fontSize}px Arial`;
258
+ lines = wrapText();
259
+ }
260
+
261
+ const startY = height / 2 - (lines.length * fontSize) / 2;
262
+
263
+ lines.forEach((line, i) => {
264
+ if (glow) {
265
+ ctx.shadowColor = 'transparent';
266
+ ctx.shadowBlur = 0;
267
+ ctx.strokeStyle = 'rgba(0, 0, 0, 0.35)';
268
+ ctx.lineWidth = 3;
269
+ ctx.strokeText(line, width / 2, startY + i * fontSize);
270
+ }
271
+ ctx.fillText(line, width / 2, startY + i * fontSize);
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Gera uma imagem PNG (512x512) a partir de um texto.
277
+ *
278
+ * @param {string} text
279
+ * @param {string} outputDir
280
+ * @param {string} fileName (sem extensão)
281
+ * @returns {Promise<string>} Caminho do PNG gerado
282
+ */
283
+ /**
284
+ * Gera uma imagem PNG (512x512) a partir de um texto.
285
+ *
286
+ * @param {string} text
287
+ * @param {string} outputDir
288
+ * @param {string} fileName (sem extensão)
289
+ * @param {string} color
290
+ * @returns {Promise<string>} Caminho do PNG gerado
291
+ */
292
+ export async function generateTextImage(text, outputDir, fileName, color) {
293
+ try {
294
+ const width = 512;
295
+ const height = 512;
296
+
297
+ const canvas = createCanvas(width, height);
298
+ const ctx = canvas.getContext('2d');
299
+
300
+ ctx.clearRect(0, 0, width, height);
301
+ drawTextOnCanvas(ctx, text, color);
302
+
303
+ const buffer = canvas.toBuffer('image/png');
304
+ const outputPath = path.join(outputDir, `${fileName}.png`);
305
+
306
+ await fs.writeFile(outputPath, buffer);
307
+
308
+ logger.info(`Imagem de texto gerada em: ${outputPath}`);
309
+ return outputPath;
310
+ } catch (error) {
311
+ logger.error(`generateTextImage Erro ao gerar imagem: ${error.message}`);
312
+ throw error;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Gera um WebP animado com texto piscante.
318
+ *
319
+ * @param {string} text
320
+ * @param {string} outputDir
321
+ * @param {string} fileName (sem extensão)
322
+ * @param {string} [color='white']
323
+ * @returns {Promise<string>} Caminho do WebP gerado
324
+ */
325
+ async function generateBlinkingTextWebp(text, outputDir, fileName, color = 'white') {
326
+ const width = 512;
327
+ const height = 512;
328
+
329
+ const frameCount = Math.max(6, Math.round((BLINK_DURATION_MS / 1000) * BLINK_FPS));
330
+ const frameBaseName = `${fileName}_frame`;
331
+ const framePaths = [];
332
+
333
+ for (let i = 0; i < frameCount; i += 1) {
334
+ const frameIndex = String(i).padStart(3, '0');
335
+ const framePath = path.join(outputDir, `${frameBaseName}_${frameIndex}.png`);
336
+ framePaths.push(framePath);
337
+
338
+ const canvas = createCanvas(width, height);
339
+ const ctx = canvas.getContext('2d');
340
+ ctx.clearRect(0, 0, width, height);
341
+
342
+ const framesPerHalfCycle = Math.max(1, Math.round(BLINK_FPS / (BLINK_FREQ_HZ * 2)));
343
+ const alpha = Math.floor(i / framesPerHalfCycle) % 2 === 0 ? 1 : 0;
344
+ ctx.globalAlpha = alpha;
345
+ drawTextOnCanvas(ctx, text, color, { glow: true });
346
+ ctx.globalAlpha = 1;
347
+
348
+ const buffer = canvas.toBuffer('image/png');
349
+ await fs.writeFile(framePath, buffer);
350
+ }
351
+
352
+ const outputPath = path.join(outputDir, `${fileName}.webp`);
353
+ const frameDurationMs = Math.max(40, Math.round(1000 / BLINK_FPS));
354
+ const framesArg = framePaths.map((framePath) => `"${framePath}"`).join(' ');
355
+ const img2webpCommand = `img2webp -lossless -loop 0 -d ${frameDurationMs} ${framesArg} -o "${outputPath}"`;
356
+
357
+ try {
358
+ const img2webpResult = await execProm(img2webpCommand, { timeout: 20000 });
359
+ if (img2webpResult && img2webpResult.stderr) {
360
+ logger.debug(`img2webp stderr: ${img2webpResult.stderr}`);
361
+ }
362
+ await fs.access(outputPath);
363
+ } catch (error) {
364
+ logger.error(`generateBlinkingTextWebp Erro ao converter frames: ${error.message}`);
365
+ throw error;
366
+ } finally {
367
+ for (const framePath of framePaths) {
368
+ await fs.unlink(framePath).catch(() => {});
369
+ }
370
+ }
371
+
372
+ return outputPath;
373
+ }
374
+
375
+ /**
376
+ * Processa texto simples e envia sticker de texto estático.
377
+ *
378
+ * @param {object} params
379
+ * @param {object} params.sock
380
+ * @param {object} params.messageInfo
381
+ * @param {string} params.remoteJid
382
+ * @param {string} params.senderJid
383
+ * @param {string} params.senderName
384
+ * @param {string} params.text
385
+ * @param {number} params.expirationMessage
386
+ * @param {string} [params.extraText]
387
+ * @param {string} [params.color='black']
388
+ * @param {string} [params.commandPrefix='/']
389
+ * @returns {Promise<void>}
390
+ */
391
+ export async function processTextSticker({ sock, messageInfo, remoteJid, senderJid, senderName, text, expirationMessage, extraText = '', color = 'black', commandPrefix = DEFAULT_COMMAND_PREFIX, commandName = 'stickertext' }) {
392
+ const stickerText = text.trim();
393
+ const isWhiteCommand =
394
+ String(commandName || '')
395
+ .trim()
396
+ .toLowerCase() === 'stickertextwhite';
397
+ const fallbackUsageAlias = isWhiteCommand ? 'stw' : 'st';
398
+ const usageText = getStickerUsageText(commandName, { commandPrefix }) || `Use *${commandPrefix}${fallbackUsageAlias}* com o texto da figurinha.`;
399
+
400
+ if (!stickerText) {
401
+ await sendAndStore(
402
+ sock,
403
+ remoteJid,
404
+ {
405
+ text: `❌ Você precisa informar um texto para criar a figurinha.\n\n${usageText}`,
406
+ },
407
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
408
+ );
409
+
410
+ return;
411
+ }
412
+
413
+ if (stickerText.length > MAX_CHARACTERS) {
414
+ await sendAndStore(
415
+ sock,
416
+ remoteJid,
417
+ {
418
+ text: `❌ Texto muito longo: *${stickerText.length}* caracteres.\n` + `Limite atual: *${MAX_CHARACTERS}* caracteres.`,
419
+ },
420
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
421
+ );
422
+
423
+ return;
424
+ }
425
+
426
+ const stickerLines = stickerText.split(/\r?\n/);
427
+
428
+ if (stickerLines.length > MAX_LINES) {
429
+ await sendAndStore(
430
+ sock,
431
+ remoteJid,
432
+ {
433
+ text: `❌ Texto com muitas linhas: *${stickerLines.length}*.\n` + `Limite atual: *${MAX_LINES}* linhas.`,
434
+ },
435
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
436
+ );
437
+
438
+ return;
439
+ }
440
+
441
+ const uniqueId = uuidv4();
442
+ const userId = getJidUser(senderJid);
443
+ const sanitizedUserId = (userId || 'anon').replace(/[^a-zA-Z0-9.-]/g, '_');
444
+
445
+ let imagePath = null;
446
+ let webpPath = null;
447
+ let stickerPath = null;
448
+
449
+ try {
450
+ const userDir = path.join(TEMP_DIR, sanitizedUserId);
451
+ await fs.mkdir(userDir, { recursive: true });
452
+
453
+ imagePath = await generateTextImage(text, userDir, `text_${uniqueId}`, color);
454
+
455
+ webpPath = await convertToWebp(imagePath, 'image', sanitizedUserId, uniqueId);
456
+
457
+ const { packName, packAuthor } = (() => {
458
+ if (!extraText) {
459
+ return { packName: 'OmniZap Text', packAuthor: senderName };
460
+ }
461
+
462
+ const idx = extraText.indexOf('/');
463
+ return idx !== -1
464
+ ? {
465
+ packName: extraText.slice(0, idx).trim(),
466
+ packAuthor: extraText.slice(idx + 1).trim(),
467
+ }
468
+ : { packName: extraText.trim(), packAuthor: senderName };
469
+ })();
470
+
471
+ stickerPath = await addStickerMetadata(webpPath, packName, packAuthor, {
472
+ senderName,
473
+ userId,
474
+ });
475
+
476
+ const stickerBuffer = await fs.readFile(stickerPath);
477
+
478
+ await sendAndStore(sock, remoteJid, { sticker: stickerBuffer }, { ephemeralExpiration: expirationMessage });
479
+
480
+ setImmediate(() => {
481
+ addStickerToAutoPack({
482
+ ownerJid: senderJid,
483
+ senderName,
484
+ stickerBuffer,
485
+ })
486
+ .then((collectResult) =>
487
+ notifyAutoPackCollection({
488
+ sock,
489
+ remoteJid,
490
+ messageInfo,
491
+ expirationMessage,
492
+ result: collectResult,
493
+ commandPrefix,
494
+ }),
495
+ )
496
+ .catch((collectError) => {
497
+ logger.warn(`processTextSticker Falha ao coletar figurinha automática: ${collectError.message}`);
498
+ });
499
+ });
500
+ } catch (error) {
501
+ logger.error(`processTextSticker Erro: ${error.message}`, { error });
502
+ await sendAndStore(
503
+ sock,
504
+ remoteJid,
505
+ {
506
+ text: '*❌ Não foi possível criar a figurinha de texto.*\n\n' + `Tente novamente com outro texto ou use *${commandPrefix}${fallbackUsageAlias}* com uma frase menor.`,
507
+ },
508
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
509
+ );
510
+ } finally {
511
+ const files = [imagePath, webpPath, stickerPath].filter(Boolean);
512
+ for (const file of files) {
513
+ await fs.unlink(file).catch(() => {});
514
+ }
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Processa texto e envia sticker animado com efeito de pisca-pisca.
520
+ *
521
+ * @param {object} params
522
+ * @param {object} params.sock
523
+ * @param {object} params.messageInfo
524
+ * @param {string} params.remoteJid
525
+ * @param {string} params.senderJid
526
+ * @param {string} params.senderName
527
+ * @param {string} params.text
528
+ * @param {number} params.expirationMessage
529
+ * @param {string} [params.extraText]
530
+ * @param {string} [params.color='white']
531
+ * @param {string} [params.commandPrefix='/']
532
+ * @returns {Promise<void>}
533
+ */
534
+ export async function processBlinkingTextSticker({ sock, messageInfo, remoteJid, senderJid, senderName, text, expirationMessage, extraText = '', color = 'white', commandPrefix = DEFAULT_COMMAND_PREFIX, commandName = 'stickertextblink' }) {
535
+ const parsed = parseColorFlag(text, color);
536
+ const stickerText = parsed.text.trim();
537
+ const resolvedColor = parsed.color;
538
+ const usageText = getStickerUsageText(commandName, { commandPrefix }) || `Use *${commandPrefix}stb* com o texto da figurinha piscante.`;
539
+
540
+ if (!stickerText) {
541
+ await sendAndStore(
542
+ sock,
543
+ remoteJid,
544
+ {
545
+ text: `❌ Você precisa informar um texto para criar a figurinha piscante.\n\n${usageText}`,
546
+ },
547
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
548
+ );
549
+
550
+ return;
551
+ }
552
+
553
+ if (stickerText.length > MAX_CHARACTERS) {
554
+ await sendAndStore(
555
+ sock,
556
+ remoteJid,
557
+ {
558
+ text: `❌ Texto muito longo: *${stickerText.length}* caracteres.\n` + `Limite atual: *${MAX_CHARACTERS}* caracteres.`,
559
+ },
560
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
561
+ );
562
+
563
+ return;
564
+ }
565
+
566
+ const stickerLines = stickerText.split(/\r?\n/);
567
+
568
+ if (stickerLines.length > MAX_LINES) {
569
+ await sendAndStore(
570
+ sock,
571
+ remoteJid,
572
+ {
573
+ text: `❌ Texto com muitas linhas: *${stickerLines.length}*.\n` + `Limite atual: *${MAX_LINES}* linhas.`,
574
+ },
575
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
576
+ );
577
+
578
+ return;
579
+ }
580
+
581
+ const uniqueId = uuidv4();
582
+ const userId = getJidUser(senderJid);
583
+ const sanitizedUserId = (userId || 'anon').replace(/[^a-zA-Z0-9.-]/g, '_');
584
+
585
+ let webpPath = null;
586
+ let stickerPath = null;
587
+
588
+ try {
589
+ const userDir = path.join(TEMP_DIR, sanitizedUserId);
590
+ await fs.mkdir(userDir, { recursive: true });
591
+
592
+ webpPath = await generateBlinkingTextWebp(stickerText, userDir, `text_blink_${uniqueId}`, resolvedColor);
593
+
594
+ const { packName, packAuthor } = (() => {
595
+ if (!extraText) {
596
+ return { packName: 'OmniZap Blink', packAuthor: senderName };
597
+ }
598
+
599
+ const idx = extraText.indexOf('/');
600
+ return idx !== -1
601
+ ? {
602
+ packName: extraText.slice(0, idx).trim(),
603
+ packAuthor: extraText.slice(idx + 1).trim(),
604
+ }
605
+ : { packName: extraText.trim(), packAuthor: senderName };
606
+ })();
607
+
608
+ stickerPath = await addStickerMetadata(webpPath, packName, packAuthor, {
609
+ senderName,
610
+ userId,
611
+ });
612
+
613
+ const stickerBuffer = await fs.readFile(stickerPath);
614
+
615
+ await sendAndStore(sock, remoteJid, { sticker: stickerBuffer }, { ephemeralExpiration: expirationMessage });
616
+
617
+ setImmediate(() => {
618
+ addStickerToAutoPack({
619
+ ownerJid: senderJid,
620
+ senderName,
621
+ stickerBuffer,
622
+ })
623
+ .then((collectResult) =>
624
+ notifyAutoPackCollection({
625
+ sock,
626
+ remoteJid,
627
+ messageInfo,
628
+ expirationMessage,
629
+ result: collectResult,
630
+ commandPrefix,
631
+ }),
632
+ )
633
+ .catch((collectError) => {
634
+ logger.warn(`processBlinkingTextSticker Falha ao coletar figurinha automática: ${collectError.message}`);
635
+ });
636
+ });
637
+ } catch (error) {
638
+ logger.error(`processBlinkingTextSticker Erro: ${error.message}`, { error });
639
+ await sendAndStore(
640
+ sock,
641
+ remoteJid,
642
+ {
643
+ text: '*❌ Não foi possível criar a figurinha piscante.*\n\n' + `Tente novamente com outro texto ou use *${commandPrefix}stb* com uma frase menor.`,
644
+ },
645
+ { quoted: messageInfo, ephemeralExpiration: expirationMessage },
646
+ );
647
+ } finally {
648
+ const files = [webpPath, stickerPath].filter(Boolean);
649
+ for (const file of files) {
650
+ await fs.unlink(file).catch(() => {});
651
+ }
652
+ }
653
+ }