@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,700 @@
1
+ import { URL } from 'node:url';
2
+ import { isUserAdmin, updateGroupParticipants } from '../../config/index.js';
3
+ import { getJidUser, isLidJid, isSameJidUser, isWhatsAppJid, normalizeJid } from '../../config/index.js';
4
+ import groupConfigStore from '../../store/groupConfigStore.js';
5
+ import logger from '#logger';
6
+ import { sendAndStore } from '../../services/messaging/messagePersistenceService.js';
7
+ import { extractSenderInfoFromMessage, resolveUserId } from '../../config/index.js';
8
+
9
+ /**
10
+ * Base de redes conhecidas e seus domínios oficiais para permitir por categoria.
11
+ * @type {Record<string, string[]>}
12
+ */
13
+ export const KNOWN_NETWORKS = {
14
+ youtube: ['youtube.com', 'youtu.be', 'music.youtube.com', 'm.youtube.com', 'shorts.youtube.com', 'youtube-nocookie.com'],
15
+ instagram: ['instagram.com', 'instagr.am'],
16
+ facebook: ['facebook.com', 'fb.com', 'fb.watch', 'm.facebook.com', 'l.facebook.com'],
17
+ tiktok: ['tiktok.com', 'vm.tiktok.com', 'vt.tiktok.com'],
18
+ twitter: ['twitter.com', 'x.com', 't.co', 'mobile.twitter.com'],
19
+ linkedin: ['linkedin.com', 'lnkd.in'],
20
+ twitch: ['twitch.tv', 'clips.twitch.tv'],
21
+ discord: ['discord.com', 'discord.gg', 'discordapp.com', 'discordapp.net'],
22
+ whatsapp: ['chat.whatsapp.com', 'wa.me'],
23
+ telegram: ['t.me', 'telegram.me', 'telesco.pe'],
24
+ reddit: ['reddit.com', 'redd.it'],
25
+ pinterest: ['pinterest.com', 'pin.it'],
26
+ snapchat: ['snapchat.com', 'snap.com'],
27
+ kwai: ['kwai.com', 'kw.ai'],
28
+ likee: ['likee.video'],
29
+ vimeo: ['vimeo.com', 'player.vimeo.com'],
30
+ dailymotion: ['dailymotion.com', 'dai.ly'],
31
+ rumble: ['rumble.com'],
32
+ kick: ['kick.com'],
33
+ soundcloud: ['soundcloud.com'],
34
+ spotify: ['spotify.com', 'open.spotify.com'],
35
+ deezer: ['deezer.com', 'deezer.page.link'],
36
+ applemusic: ['music.apple.com'],
37
+ shazam: ['shazam.com'],
38
+ bandcamp: ['bandcamp.com'],
39
+ amazonmusic: ['music.amazon.com'],
40
+ imdb: ['imdb.com'],
41
+ letterboxd: ['letterboxd.com'],
42
+ goodreads: ['goodreads.com'],
43
+ medium: ['medium.com'],
44
+ substack: ['substack.com'],
45
+ behance: ['behance.net'],
46
+ dribbble: ['dribbble.com'],
47
+ deviantart: ['deviantart.com'],
48
+ artstation: ['artstation.com'],
49
+ figma: ['figma.com', 'figma.io'],
50
+ github: ['github.com', 'gist.github.com', 'github.io'],
51
+ gitlab: ['gitlab.com'],
52
+ bitbucket: ['bitbucket.org'],
53
+ npm: ['npmjs.com'],
54
+ pypi: ['pypi.org'],
55
+ stackoverflow: ['stackoverflow.com', 'stackexchange.com'],
56
+ quora: ['quora.com'],
57
+ stackshare: ['stackshare.io'],
58
+ producthunt: ['producthunt.com'],
59
+ hackernews: ['news.ycombinator.com'],
60
+ google: ['google.com', 'goo.gl', 'g.co', 'maps.google.com'],
61
+ maps: ['google.com', 'maps.google.com', 'goo.gl', 'g.page'],
62
+ playstore: ['play.google.com'],
63
+ appstore: ['apps.apple.com'],
64
+ steam: ['steamcommunity.com', 'store.steampowered.com', 'steamdb.info'],
65
+ epicgames: ['epicgames.com'],
66
+ discordbots: ['top.gg', 'discords.com', 'discordbotlist.com'],
67
+ cloudflare: ['cloudflare.com', 'pages.dev', 'workers.dev'],
68
+ heroku: ['heroku.com', 'herokuapp.com'],
69
+ vercel: ['vercel.app', 'vercel.com'],
70
+ netlify: ['netlify.app', 'netlify.com'],
71
+ firebase: ['firebase.google.com', 'web.app'],
72
+ hostinger: ['hostinger.com'],
73
+ wix: ['wix.com', 'wixsite.com'],
74
+ squarespace: ['squarespace.com'],
75
+ wordpress: ['wordpress.com', 'wordpress.org'],
76
+ blogger: ['blogger.com', 'blogspot.com'],
77
+ tumblr: ['tumblr.com'],
78
+ weibo: ['weibo.com'],
79
+ vk: ['vk.com'],
80
+ okru: ['ok.ru'],
81
+ line: ['line.me'],
82
+ wechat: ['wechat.com', 'weixin.qq.com', 'we.chat'],
83
+ qq: ['qq.com'],
84
+ signal: ['signal.org'],
85
+ skype: ['skype.com'],
86
+ slack: ['slack.com'],
87
+ zoom: ['zoom.us', 'zoom.com'],
88
+ meet: ['meet.google.com'],
89
+ teams: ['microsoft.com', 'teams.microsoft.com'],
90
+ canva: ['canva.com'],
91
+ notion: ['notion.so', 'notion.site'],
92
+ trello: ['trello.com'],
93
+ asana: ['asana.com'],
94
+ monday: ['monday.com'],
95
+ clickup: ['clickup.com'],
96
+ airtable: ['airtable.com'],
97
+ coursera: ['coursera.org'],
98
+ udemy: ['udemy.com'],
99
+ udacity: ['udacity.com'],
100
+ edx: ['edx.org'],
101
+ khanacademy: ['khanacademy.org'],
102
+ duolingo: ['duolingo.com'],
103
+ roblox: ['roblox.com'],
104
+ minecraft: ['minecraft.net', 'minecraft.net.br'],
105
+ valorant: ['valorant.com'],
106
+ riot: ['riotgames.com'],
107
+ leagueoflegends: ['leagueoflegends.com'],
108
+ dota2: ['dota2.com'],
109
+ csgo: ['counter-strike.net'],
110
+ };
111
+
112
+ /**
113
+ * Delimitadores básicos para tokenização manual (sem regex).
114
+ */
115
+ const WHITESPACE_CHARS = new Set([' ', '\n', '\r', '\t', '\f', '\v']);
116
+ const EDGE_PUNCTUATION_CHARS = new Set([',', '!', '?', ';', ':', ')', '(', '[', ']', '{', '}', '<', '>', '"', "'", '`', '…']);
117
+ const TOKEN_SEPARATOR_CHARS = new Set([',', ';', '|']);
118
+ const HOST_TERMINATORS = new Set(['/', '?', '#', ':', '\\', ',', ';']);
119
+ const URL_HINTS = ['https://', 'http://', 'www.'];
120
+ const STRICT_TLD_SUFFIXES = new Set(['com', 'net', 'org', 'edu', 'gov', 'mil', 'io', 'me', 'tv', 'co', 'cc', 'gg', 'gl', 'ly', 'so', 'br', 'us', 'uk', 'eu', 'de', 'fr', 'es', 'pt', 'it', 'nl', 'be', 'ch', 'at', 'se', 'no', 'fi', 'dk', 'ie', 'pl', 'cz', 'sk', 'hu', 'ro', 'bg', 'gr', 'ru', 'ua', 'tr', 'il', 'ae', 'sa', 'qa', 'eg', 'ma', 'tn', 'dz', 'za', 'ng', 'ke', 'gh', 'in', 'pk', 'bd', 'lk', 'cn', 'jp', 'kr', 'tw', 'hk', 'sg', 'my', 'th', 'vn', 'ph', 'id', 'au', 'nz', 'ca', 'mx', 'ar', 'cl', 'pe', 'uy', 'py', 'bo', 'ec', 've', 'do', 'cu', 'pa', 'cr', 'gt', 'hn', 'ni', 'sv', 'pr', 'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br', 'jus.br', 'mil.br', 'co.uk', 'org.uk', 'gov.uk', 'ac.uk', 'co.jp', 'ne.jp', 'or.jp', 'go.jp', 'ac.jp', 'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'com.mx', 'com.ar', 'com.co', 'com.pe', 'com.tr', 'com.sg', 'com.my', 'com.ph', 'co.in', 'firm.in', 'net.in', 'org.in', 'gen.in', 'ind.in', 'co.id', 'or.id', 'go.id', 'web.id', 'co.za', 'org.za', 'net.za', 'com.ng', 'com.gh', 'com.eg', 'com.sa', 'com.qa', 'com.ae', 'page.link', 'g.page']);
121
+ const EXTRA_TLD_SUFFIXES = new Set(['ai', 'app', 'dev', 'xyz', 'site', 'online', 'store', 'shop', 'blog', 'tech', 'cloud', 'digital', 'live', 'media', 'news', 'one', 'top', 'club', 'vip', 'fun', 'games', 'game', 'space', 'world', 'today', 'agency', 'email', 'center', 'company', 'group', 'solutions', 'systems', 'services', 'network', 'social', 'design', 'studio', 'photo', 'video', 'audio', 'music', 'art', 'wiki', 'finance', 'capital', 'money', 'loans', 'insurance', 'legal', 'law', 'health', 'care', 'clinic', 'dental', 'academy', 'school', 'college', 'university', 'education', 'training', 'support', 'chat', 'forum', 'community', 'events', 'travel', 'tours', 'hotel', 'homes', 'house', 'auto', 'cars', 'bike', 'food', 'restaurant', 'cafe', 'bar', 'pizza', 'delivery', 'fashion', 'beauty', 'style', 'fit', 'fitness', 'sports', 'download']);
122
+ const ANY_TLD_SUFFIXES = new Set([...STRICT_TLD_SUFFIXES, ...EXTRA_TLD_SUFFIXES]);
123
+
124
+ /**
125
+ * Tokeniza texto por espaço/quebra de linha sem regex.
126
+ * @param {string} text
127
+ * @returns {string[]}
128
+ */
129
+ const tokenizeText = (text) => {
130
+ if (!text) return [];
131
+ const tokens = [];
132
+ let currentToken = '';
133
+
134
+ for (const char of text) {
135
+ if (WHITESPACE_CHARS.has(char)) {
136
+ if (currentToken) {
137
+ tokens.push(currentToken);
138
+ currentToken = '';
139
+ }
140
+ continue;
141
+ }
142
+ currentToken += char;
143
+ }
144
+
145
+ if (currentToken) tokens.push(currentToken);
146
+ return tokens;
147
+ };
148
+
149
+ /**
150
+ * Divide tokens compostos (site.com,site2.com|site3.com) sem regex.
151
+ * @param {string} token
152
+ * @returns {string[]}
153
+ */
154
+ const splitCompositeToken = (token) => {
155
+ const parts = [];
156
+ let currentPart = '';
157
+
158
+ for (let i = 0; i < token.length; i += 1) {
159
+ const char = token[i];
160
+ const isArrowSeparator = char === '>' && i > 0 && token[i - 1] === '-';
161
+
162
+ if (TOKEN_SEPARATOR_CHARS.has(char) || isArrowSeparator) {
163
+ if (isArrowSeparator && currentPart.endsWith('-')) {
164
+ currentPart = currentPart.slice(0, -1);
165
+ }
166
+ if (currentPart) {
167
+ parts.push(currentPart);
168
+ currentPart = '';
169
+ }
170
+ continue;
171
+ }
172
+ currentPart += char;
173
+ }
174
+
175
+ if (currentPart) parts.push(currentPart);
176
+ return parts;
177
+ };
178
+
179
+ /**
180
+ * Remove pontuação comum das bordas do token.
181
+ * @param {string} token
182
+ * @returns {string}
183
+ */
184
+ const stripEdgePunctuation = (token) => {
185
+ let start = 0;
186
+ let end = token.length;
187
+
188
+ while (start < end && EDGE_PUNCTUATION_CHARS.has(token[start])) start += 1;
189
+ while (end > start && EDGE_PUNCTUATION_CHARS.has(token[end - 1])) end -= 1;
190
+
191
+ return token.slice(start, end);
192
+ };
193
+
194
+ const isAsciiLetter = (char) => char >= 'a' && char <= 'z';
195
+ const isDigit = (char) => char >= '0' && char <= '9';
196
+ const isDomainLabelChar = (char) => isAsciiLetter(char) || isDigit(char) || char === '-';
197
+
198
+ /**
199
+ * Normaliza host removendo "www." e pontos nas bordas.
200
+ * @param {string} host
201
+ * @returns {string}
202
+ */
203
+ const normalizeHost = (host) => {
204
+ if (!host) return '';
205
+ let normalized = host.toLowerCase();
206
+
207
+ while (normalized.startsWith('.')) {
208
+ normalized = normalized.slice(1);
209
+ }
210
+ while (normalized.endsWith('.')) {
211
+ normalized = normalized.slice(0, -1);
212
+ }
213
+ while (normalized.startsWith('www.')) {
214
+ normalized = normalized.slice(4);
215
+ }
216
+
217
+ return normalized;
218
+ };
219
+
220
+ /**
221
+ * Retorna quantos labels formam o TLD conhecido (1, 2 ou 3).
222
+ * @param {string[]} labels
223
+ * @param {Set<string>} suffixSet
224
+ * @returns {number}
225
+ */
226
+ const getKnownTldLabelCount = (labels, suffixSet) => {
227
+ if (labels.length >= 3) {
228
+ const lastThree = labels.slice(-3).join('.');
229
+ if (suffixSet.has(lastThree)) return 3;
230
+ }
231
+ if (labels.length >= 2) {
232
+ const lastTwo = labels.slice(-2).join('.');
233
+ if (suffixSet.has(lastTwo)) return 2;
234
+ }
235
+ const lastOne = labels[labels.length - 1];
236
+ if (suffixSet.has(lastOne)) return 1;
237
+ return 0;
238
+ };
239
+
240
+ /**
241
+ * Conta labels de TLD somente na lista strict.
242
+ * @param {string[]} labels
243
+ * @returns {number}
244
+ */
245
+ const getStrictTldLabelCount = (labels) => getKnownTldLabelCount(labels, STRICT_TLD_SUFFIXES);
246
+
247
+ /**
248
+ * Conta labels de TLD aceitando strict + extra.
249
+ * @param {string[]} labels
250
+ * @returns {number}
251
+ */
252
+ const getAnyTldLabelCount = (labels) => getKnownTldLabelCount(labels, ANY_TLD_SUFFIXES);
253
+
254
+ /**
255
+ * Extrai o root registrável com base nos TLDs strict.
256
+ * @param {string} domain
257
+ * @returns {string}
258
+ */
259
+ const getStrictRegistrableRootDomain = (domain) => {
260
+ const labels = domain.split('.');
261
+ const tldLabelCount = getStrictTldLabelCount(labels);
262
+ if (tldLabelCount === 0 || labels.length <= tldLabelCount) return '';
263
+ return labels.slice(-(tldLabelCount + 1)).join('.');
264
+ };
265
+
266
+ /**
267
+ * Valida a estrutura do domínio sem regex.
268
+ * @param {string} domain
269
+ * @returns {boolean}
270
+ */
271
+ const isValidDomainStructure = (domain) => {
272
+ if (!domain || domain.length > 253 || !domain.includes('.')) return false;
273
+ const labels = domain.split('.');
274
+
275
+ for (const label of labels) {
276
+ if (!label || label.length > 63) return false;
277
+ if (label[0] === '-' || label[label.length - 1] === '-') return false;
278
+
279
+ for (const char of label) {
280
+ if (!isDomainLabelChar(char)) return false;
281
+ }
282
+ }
283
+
284
+ return true;
285
+ };
286
+
287
+ /**
288
+ * Verifica se o domínio termina com TLD/sufixo conhecido.
289
+ * @param {string} domain
290
+ * @returns {boolean}
291
+ */
292
+ const hasStrictKnownTldSuffix = (domain) => {
293
+ const labels = domain.split('.');
294
+ return getStrictTldLabelCount(labels) > 0;
295
+ };
296
+
297
+ /**
298
+ * Verifica se o domínio termina com TLD/sufixo conhecido (strict + extra).
299
+ * @param {string} domain
300
+ * @returns {boolean}
301
+ */
302
+ const hasAnyKnownTldSuffix = (domain) => {
303
+ const labels = domain.split('.');
304
+ return getAnyTldLabelCount(labels) > 0;
305
+ };
306
+
307
+ const KNOWN_NETWORK_EXACT_DOMAINS = new Set();
308
+ const KNOWN_NETWORK_SUBDOMAIN_ROOTS = new Set();
309
+
310
+ for (const domains of Object.values(KNOWN_NETWORKS)) {
311
+ for (const domain of domains) {
312
+ const normalizedDomain = domain.toLowerCase();
313
+ KNOWN_NETWORK_EXACT_DOMAINS.add(normalizedDomain);
314
+
315
+ const rootDomain = getStrictRegistrableRootDomain(normalizedDomain);
316
+ if (rootDomain) {
317
+ KNOWN_NETWORK_SUBDOMAIN_ROOTS.add(rootDomain);
318
+ }
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Verifica domínios oficiais já mapeados em KNOWN_NETWORKS.
324
+ * @param {string} domain
325
+ * @returns {boolean}
326
+ */
327
+ const isKnownNetworkDomain = (domain) => {
328
+ const normalizedDomain = domain.toLowerCase();
329
+ if (KNOWN_NETWORK_EXACT_DOMAINS.has(normalizedDomain)) return true;
330
+
331
+ const rootDomain = getStrictRegistrableRootDomain(normalizedDomain);
332
+ if (rootDomain && rootDomain !== normalizedDomain && KNOWN_NETWORK_SUBDOMAIN_ROOTS.has(rootDomain)) {
333
+ return true;
334
+ }
335
+
336
+ // Fallback para sufixos fora da lista de TLDs (ex.: goo.gl), sem varrer array inteiro.
337
+ let dotIndex = normalizedDomain.indexOf('.');
338
+ while (dotIndex !== -1) {
339
+ const parentDomain = normalizedDomain.slice(dotIndex + 1);
340
+ if (KNOWN_NETWORK_EXACT_DOMAINS.has(parentDomain)) return true;
341
+ dotIndex = normalizedDomain.indexOf('.', dotIndex + 1);
342
+ }
343
+
344
+ return false;
345
+ };
346
+
347
+ /**
348
+ * Busca o primeiro indicativo de URL no token.
349
+ * @param {string} token
350
+ * @returns {number}
351
+ */
352
+ const findUrlHintIndex = (token) => {
353
+ let firstIndex = -1;
354
+
355
+ for (const hint of URL_HINTS) {
356
+ const index = token.indexOf(hint);
357
+ if (index !== -1 && (firstIndex === -1 || index < firstIndex)) {
358
+ firstIndex = index;
359
+ }
360
+ }
361
+
362
+ return firstIndex;
363
+ };
364
+
365
+ /**
366
+ * Extrai host de token com http(s)/www usando URL nativo.
367
+ * @param {string} token
368
+ * @returns {string | null}
369
+ */
370
+ const extractDomainFromUrlToken = (token) => {
371
+ const lowerToken = token.toLowerCase();
372
+ const hintIndex = findUrlHintIndex(lowerToken);
373
+ if (hintIndex === -1) return null;
374
+ if (hintIndex > 0) {
375
+ const previousChar = lowerToken[hintIndex - 1];
376
+ if (previousChar === '@' || isAsciiLetter(previousChar) || isDigit(previousChar)) {
377
+ return null;
378
+ }
379
+ }
380
+
381
+ let urlCandidate = token.slice(hintIndex);
382
+ const normalizedCandidate = urlCandidate.toLowerCase();
383
+ if (!normalizedCandidate.startsWith('http://') && !normalizedCandidate.startsWith('https://')) {
384
+ urlCandidate = `https://${urlCandidate}`;
385
+ }
386
+
387
+ try {
388
+ const parsedUrl = new URL(urlCandidate);
389
+ const host = normalizeHost(parsedUrl.hostname);
390
+ if (!isValidDomainStructure(host)) return null;
391
+ if (!hasAnyKnownTldSuffix(host) && !isKnownNetworkDomain(host)) return null;
392
+ return host;
393
+ } catch {
394
+ return null;
395
+ }
396
+ };
397
+
398
+ /**
399
+ * Extrai host de token sem protocolo, validando domínio manualmente.
400
+ * @param {string} token
401
+ * @returns {string | null}
402
+ */
403
+ const extractDomainFromPlainToken = (token) => {
404
+ const lowerToken = token.toLowerCase();
405
+ if (lowerToken.includes('@')) return null;
406
+
407
+ let host = '';
408
+ for (const char of lowerToken) {
409
+ if (HOST_TERMINATORS.has(char)) break;
410
+ host += char;
411
+ }
412
+
413
+ host = normalizeHost(host);
414
+ if (!isValidDomainStructure(host)) return null;
415
+ if (!hasStrictKnownTldSuffix(host) && !isKnownNetworkDomain(host)) return null;
416
+
417
+ return host;
418
+ };
419
+
420
+ /**
421
+ * Extrai domínios válidos de um texto sem uso de regex.
422
+ * @param {string} text
423
+ * @returns {string[]}
424
+ */
425
+ export const extractDomainsNoRegex = (text) => {
426
+ const tokens = tokenizeText(text);
427
+ if (tokens.length === 0) return [];
428
+ const domains = new Set();
429
+
430
+ for (const token of tokens) {
431
+ const partialTokens = splitCompositeToken(token);
432
+ for (const partialToken of partialTokens) {
433
+ const cleanedToken = stripEdgePunctuation(partialToken);
434
+ if (!cleanedToken) continue;
435
+
436
+ const urlDomain = extractDomainFromUrlToken(cleanedToken);
437
+ if (urlDomain) {
438
+ domains.add(urlDomain);
439
+ continue;
440
+ }
441
+
442
+ const plainDomain = extractDomainFromPlainToken(cleanedToken);
443
+ if (plainDomain) domains.add(plainDomain);
444
+ }
445
+ }
446
+
447
+ return Array.from(domains);
448
+ };
449
+
450
+ /**
451
+ * Normaliza e remove duplicados da allowlist.
452
+ * @param {string[]} allowedDomains
453
+ * @returns {string[]}
454
+ */
455
+ const normalizeAllowedDomains = (allowedDomains = []) => {
456
+ const normalizedDomains = new Set();
457
+
458
+ for (const allowedDomain of allowedDomains) {
459
+ const normalizedDomain = normalizeHost(String(allowedDomain || ''));
460
+ if (normalizedDomain) normalizedDomains.add(normalizedDomain);
461
+ }
462
+
463
+ return Array.from(normalizedDomains);
464
+ };
465
+
466
+ /**
467
+ * Aceita o domínio exato ou subdomínios de um permitido já normalizado.
468
+ * @param {string} domain
469
+ * @param {string[]} normalizedAllowedDomains
470
+ * @returns {boolean}
471
+ */
472
+ const isDomainAllowed = (domain, normalizedAllowedDomains) => normalizedAllowedDomains.some((allowedDomain) => domain === allowedDomain || domain.endsWith(`.${allowedDomain}`));
473
+
474
+ /**
475
+ * Monta a lista final de domínios permitidos (redes conhecidas + personalizados).
476
+ * @param {string[]} allowedNetworks
477
+ * @param {string[]} allowedCustomDomains
478
+ * @returns {string[]}
479
+ */
480
+ const getAllowedDomains = (allowedNetworks = [], allowedCustomDomains = []) => {
481
+ const domains = [];
482
+ for (const network of allowedNetworks) {
483
+ if (KNOWN_NETWORKS[network]) {
484
+ domains.push(...KNOWN_NETWORKS[network]);
485
+ }
486
+ }
487
+ return [...domains, ...allowedCustomDomains];
488
+ };
489
+
490
+ /**
491
+ * Retorna true quando existir um link que não esteja na lista permitida.
492
+ * @param {string} text
493
+ * @param {string[]} normalizedAllowedDomains
494
+ * @returns {boolean}
495
+ */
496
+ export const isLinkDetected = (text, normalizedAllowedDomains = []) => {
497
+ const domains = extractDomainsNoRegex(text);
498
+ if (domains.length === 0) return false;
499
+ if (normalizedAllowedDomains.length === 0) return true;
500
+ return domains.some((domain) => !isDomainAllowed(domain, normalizedAllowedDomains));
501
+ };
502
+
503
+ const normalizeOptionalJid = (value) => {
504
+ if (typeof value !== 'string') return '';
505
+ return normalizeJid(value.trim());
506
+ };
507
+
508
+ const uniqueNormalizedJids = (values = []) => {
509
+ const unique = [];
510
+ const seen = new Set();
511
+ for (const value of values) {
512
+ const normalized = normalizeOptionalJid(value);
513
+ if (!normalized || seen.has(normalized)) continue;
514
+ seen.add(normalized);
515
+ unique.push(normalized);
516
+ }
517
+ return unique;
518
+ };
519
+
520
+ const isSameUserSafe = (jidA, jidB) => {
521
+ if (!jidA || !jidB) return false;
522
+ try {
523
+ return isSameJidUser(jidA, jidB);
524
+ } catch {
525
+ return false;
526
+ }
527
+ };
528
+
529
+ const isSenderBot = (botJid, candidates) => {
530
+ const normalizedBot = normalizeOptionalJid(botJid);
531
+ if (!normalizedBot) return false;
532
+ return candidates.some((candidate) => isSameUserSafe(candidate, normalizedBot));
533
+ };
534
+
535
+ const resolveSenderContextForAntiLink = async ({ messageInfo, senderJid, senderIdentity }) => {
536
+ const key = messageInfo?.key || {};
537
+ const senderInfo = extractSenderInfoFromMessage(messageInfo);
538
+ const resolvedByMessage = normalizeOptionalJid(await resolveUserId(senderInfo).catch(() => ''));
539
+ const identityRaw = typeof senderIdentity === 'string' ? normalizeOptionalJid(senderIdentity) : '';
540
+ const keyParticipant = normalizeOptionalJid(key?.participant);
541
+ const keyParticipantAlt = normalizeOptionalJid(key?.participantAlt);
542
+ const keyRemoteAlt = normalizeOptionalJid(key?.remoteJidAlt);
543
+ const identityParticipant = normalizeOptionalJid(senderIdentity?.participant);
544
+ const identityParticipantAlt = normalizeOptionalJid(senderIdentity?.participantAlt);
545
+ const identityJid = normalizeOptionalJid(senderIdentity?.jid);
546
+ const explicitSender = normalizeOptionalJid(senderJid);
547
+ const senderInfoJid = normalizeOptionalJid(senderInfo?.jid);
548
+ const senderInfoLid = normalizeOptionalJid(senderInfo?.lid);
549
+ const senderInfoAlt = normalizeOptionalJid(senderInfo?.participantAlt);
550
+
551
+ const senderCandidates = uniqueNormalizedJids([explicitSender, resolvedByMessage, keyParticipant, keyParticipantAlt, keyRemoteAlt, senderInfoJid, senderInfoLid, senderInfoAlt, identityParticipant, identityParticipantAlt, identityJid, identityRaw]);
552
+
553
+ const removalCandidates = [];
554
+ const lidCandidates = [];
555
+ const pnCandidates = [];
556
+ for (const candidate of senderCandidates) {
557
+ if (isLidJid(candidate)) lidCandidates.push(candidate);
558
+ else if (isWhatsAppJid(candidate)) pnCandidates.push(candidate);
559
+ }
560
+ removalCandidates.push(...lidCandidates, ...pnCandidates);
561
+
562
+ const mentionJid = pnCandidates[0] || removalCandidates[0] || senderCandidates[0] || '';
563
+ const primarySenderId = resolvedByMessage || explicitSender || senderInfoJid || senderInfoLid || senderCandidates[0] || '';
564
+
565
+ return {
566
+ senderInfo,
567
+ senderCandidates,
568
+ removalCandidates,
569
+ mentionJid,
570
+ primarySenderId,
571
+ };
572
+ };
573
+
574
+ const removeParticipantWithFallback = async (sock, remoteJid, candidates = []) => {
575
+ let lastError = null;
576
+ for (const candidate of candidates) {
577
+ try {
578
+ await updateGroupParticipants(sock, remoteJid, [candidate], 'remove');
579
+ return candidate;
580
+ } catch (error) {
581
+ lastError = error;
582
+ logger.debug('Falha ao remover participante com ID candidato. Tentando próximo.', {
583
+ action: 'antilink_remove_candidate_failed',
584
+ groupId: remoteJid,
585
+ participantId: candidate,
586
+ error: error?.message,
587
+ });
588
+ }
589
+ }
590
+ if (lastError) throw lastError;
591
+ return '';
592
+ };
593
+
594
+ /**
595
+ * Aplica a regra de antilink do grupo. Retorna true quando removeu e deve pular o restante.
596
+ * @param {Object} params
597
+ * @param {import('@whiskeysockets/baileys').WASocket} params.sock
598
+ * @param {Object} params.messageInfo
599
+ * @param {string} params.extractedText
600
+ * @param {string} params.remoteJid
601
+ * @param {string} params.senderJid
602
+ * @param {{participant?: string|null, participantAlt?: string|null, jid?: string|null}|string|null} [params.senderIdentity]
603
+ * @param {string} params.botJid
604
+ * @returns {Promise<boolean>}
605
+ */
606
+ export const handleAntiLink = async ({ sock, messageInfo, extractedText, remoteJid, senderJid, senderIdentity, botJid }) => {
607
+ const groupConfig = await groupConfigStore.getGroupConfig(remoteJid);
608
+ if (!groupConfig || !groupConfig.antilinkEnabled) return false;
609
+
610
+ const allowedDomains = getAllowedDomains(groupConfig.antilinkAllowedNetworks || [], groupConfig.antilinkAllowedDomains || []);
611
+ const normalizedAllowedDomains = normalizeAllowedDomains(allowedDomains);
612
+ if (!isLinkDetected(extractedText, normalizedAllowedDomains)) return false;
613
+
614
+ const senderContext = await resolveSenderContextForAntiLink({
615
+ messageInfo,
616
+ senderJid,
617
+ senderIdentity,
618
+ });
619
+ if (!senderContext.primarySenderId && senderContext.senderCandidates.length === 0) return false;
620
+
621
+ let isAdmin = await isUserAdmin(remoteJid, {
622
+ id: senderContext.primarySenderId || null,
623
+ jid: senderContext.senderInfo?.jid || senderContext.primarySenderId || null,
624
+ lid: senderContext.senderInfo?.lid || null,
625
+ participantAlt: senderContext.senderInfo?.participantAlt || null,
626
+ participant: messageInfo?.key?.participant || null,
627
+ remoteJidAlt: messageInfo?.key?.remoteJidAlt || null,
628
+ });
629
+
630
+ if (!isAdmin && senderContext.primarySenderId) {
631
+ isAdmin = await isUserAdmin(remoteJid, senderContext.primarySenderId);
632
+ }
633
+
634
+ const senderIsBot = isSenderBot(botJid, senderContext.senderCandidates);
635
+
636
+ if (!isAdmin && !senderIsBot) {
637
+ if (senderContext.removalCandidates.length === 0) {
638
+ logger.warn('Antilink detectou link, mas não encontrou ID válido para remoção.', {
639
+ action: 'antilink_no_removal_candidate',
640
+ groupId: remoteJid,
641
+ senderCandidates: senderContext.senderCandidates,
642
+ });
643
+ return false;
644
+ }
645
+
646
+ try {
647
+ const removedParticipantId = await removeParticipantWithFallback(sock, remoteJid, senderContext.removalCandidates);
648
+ if (!removedParticipantId) {
649
+ throw new Error('Nenhum candidato de participante pôde ser removido.');
650
+ }
651
+ const senderMention = senderContext.mentionJid || removedParticipantId || senderContext.primarySenderId;
652
+ const senderUser = getJidUser(senderMention);
653
+ await sendAndStore(sock, remoteJid, {
654
+ text: `🚫 @${senderUser || 'usuario'} foi removido por enviar um link.`,
655
+ mentions: senderMention ? [senderMention] : [],
656
+ });
657
+ await sendAndStore(sock, remoteJid, { delete: messageInfo.key });
658
+
659
+ logger.info(`Usuário ${removedParticipantId || senderContext.primarySenderId} removido do grupo ${remoteJid} por enviar link.`, {
660
+ action: 'antilink_remove',
661
+ groupId: remoteJid,
662
+ userId: removedParticipantId || senderContext.primarySenderId,
663
+ senderCandidates: senderContext.senderCandidates,
664
+ });
665
+
666
+ return true;
667
+ } catch (error) {
668
+ logger.error(`Falha ao remover usuário com antilink: ${error.message}`, {
669
+ action: 'antilink_error',
670
+ groupId: remoteJid,
671
+ userId: senderContext.primarySenderId,
672
+ senderCandidates: senderContext.senderCandidates,
673
+ error: error.stack,
674
+ });
675
+ }
676
+ } else if (isAdmin && !senderIsBot) {
677
+ try {
678
+ const senderMention = senderContext.mentionJid || senderContext.primarySenderId;
679
+ const senderUser = getJidUser(senderMention);
680
+ await sendAndStore(sock, remoteJid, {
681
+ text: `ⓘ @${senderUser || 'admin'} (admin) enviou um link.`,
682
+ mentions: senderMention ? [senderMention] : [],
683
+ });
684
+ logger.info(`Admin ${senderContext.primarySenderId} enviou um link no grupo ${remoteJid} (aviso enviado).`, {
685
+ action: 'antilink_admin_link_detected',
686
+ groupId: remoteJid,
687
+ userId: senderContext.primarySenderId,
688
+ });
689
+ } catch (error) {
690
+ logger.error(`Falha ao enviar aviso de link de admin: ${error.message}`, {
691
+ action: 'antilink_admin_warning_error',
692
+ groupId: remoteJid,
693
+ userId: senderContext.primarySenderId,
694
+ error: error.stack,
695
+ });
696
+ }
697
+ }
698
+
699
+ return false;
700
+ };