@omnizap-system/omnizap 2.6.0 → 2.6.2

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 (261) hide show
  1. package/.env.example +58 -13
  2. package/.github/workflows/ci.yml +5 -5
  3. package/.github/workflows/codeql.yml +1 -1
  4. package/.github/workflows/db-migration-check.yml +2 -2
  5. package/.github/workflows/dependency-review.yml +1 -1
  6. package/.github/workflows/deploy.yml +2 -2
  7. package/.github/workflows/release.yml +2 -2
  8. package/.github/workflows/security-attest-provenance.yml +2 -2
  9. package/.github/workflows/security-gitleaks.yml +13 -4
  10. package/.github/workflows/security-runner-hardening.yml +2 -2
  11. package/.github/workflows/security-scorecard.yml +1 -1
  12. package/.github/workflows/security-zap-baseline.yml +1 -1
  13. package/.github/workflows/security-zap-full-scan.yml +2 -1
  14. package/.github/workflows/security-zizmor.yml +1 -1
  15. package/.github/workflows/wiki-sync.yml +1 -1
  16. package/.gitleaksignore +9 -0
  17. package/CODE_OF_CONDUCT.md +2 -2
  18. package/GEMINI.md +64 -0
  19. package/README.md +52 -82
  20. package/SECURITY.md +1 -1
  21. package/app/config/index.js +2 -0
  22. package/app/configParts/adminIdentity.js +5 -5
  23. package/app/configParts/baileysConfig.js +230 -58
  24. package/app/configParts/groupUtils.js +5 -0
  25. package/app/configParts/messagePersistenceService.js +145 -4
  26. package/app/configParts/sessionConfig.js +157 -0
  27. package/app/connection/baileysCompatibility.test.js +1 -1
  28. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  29. package/app/connection/socketController.js +660 -158
  30. package/app/connection/socketController.multiSession.test.js +108 -0
  31. package/app/controllers/messageController.js +1 -1
  32. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  33. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  34. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  35. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  36. package/app/controllers/messageProcessingPipeline.js +93 -13
  37. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  38. package/app/modules/adminModule/AGENT.md +1 -1
  39. package/app/modules/adminModule/commandConfig.json +3318 -1347
  40. package/app/modules/adminModule/groupCommandHandlers.js +858 -15
  41. package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
  42. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  43. package/app/modules/aiModule/AGENT.md +47 -30
  44. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  45. package/app/modules/aiModule/catCommand.js +135 -27
  46. package/app/modules/aiModule/commandConfig.json +114 -28
  47. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  48. package/app/modules/gameModule/AGENT.md +1 -1
  49. package/app/modules/gameModule/commandConfig.json +29 -0
  50. package/app/modules/menuModule/AGENT.md +1 -1
  51. package/app/modules/menuModule/commandConfig.json +45 -10
  52. package/app/modules/menuModule/menuCatalogService.js +190 -0
  53. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  54. package/app/modules/menuModule/menuDynamicService.js +511 -0
  55. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  56. package/app/modules/menuModule/menus.js +36 -5
  57. package/app/modules/playModule/AGENT.md +10 -5
  58. package/app/modules/playModule/commandConfig.json +140 -12
  59. package/app/modules/playModule/playCommand.js +1 -1417
  60. package/app/modules/playModule/playCommandConstants.js +80 -0
  61. package/app/modules/playModule/playCommandCore.js +361 -0
  62. package/app/modules/playModule/playCommandHandlers.js +41 -0
  63. package/app/modules/playModule/playCommandMediaClient.js +1872 -0
  64. package/app/modules/playModule/playConfigRuntime.js +245 -4
  65. package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
  66. package/app/modules/quoteModule/AGENT.md +1 -1
  67. package/app/modules/quoteModule/commandConfig.json +29 -0
  68. package/app/modules/quoteModule/quoteCommand.js +3 -2
  69. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  70. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  71. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
  72. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
  73. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
  74. package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
  75. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
  76. package/app/modules/statsModule/AGENT.md +1 -1
  77. package/app/modules/statsModule/commandConfig.json +58 -0
  78. package/app/modules/statsModule/rankingCommon.js +5 -4
  79. package/app/modules/stickerModule/AGENT.md +1 -1
  80. package/app/modules/stickerModule/addStickerMetadata.js +4 -3
  81. package/app/modules/stickerModule/commandConfig.json +145 -0
  82. package/app/modules/stickerModule/stickerCommand.js +1 -1
  83. package/app/modules/stickerPackModule/AGENT.md +1 -1
  84. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  85. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  86. package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
  87. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
  88. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
  89. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
  90. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
  91. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
  92. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
  93. package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
  94. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
  95. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  96. package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
  97. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
  98. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  99. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  100. package/app/modules/systemMetricsModule/pingCommand.js +6 -5
  101. package/app/modules/tiktokModule/AGENT.md +1 -1
  102. package/app/modules/tiktokModule/commandConfig.json +29 -0
  103. package/app/modules/tiktokModule/tiktokCommand.js +2 -1
  104. package/app/modules/userModule/AGENT.md +1 -1
  105. package/app/modules/userModule/commandConfig.json +29 -0
  106. package/app/modules/userModule/userCommand.js +72 -23
  107. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  108. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  109. package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
  110. package/app/observability/metrics.js +136 -0
  111. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  112. package/app/services/ai/conversationRouterService.js +4 -3
  113. package/app/services/ai/geminiService.js +132 -7
  114. package/app/services/ai/geminiService.test.js +59 -2
  115. package/app/services/ai/globalModuleAiHelpService.js +3 -2
  116. package/app/services/ai/messageCommandExecutionService.js +2 -1
  117. package/app/services/ai/moduleAiHelpCoreService.js +45 -14
  118. package/app/services/ai/moduleToolExecutorService.js +3 -2
  119. package/app/services/ai/moduleToolRegistryService.js +2 -1
  120. package/app/services/ai/toolCandidateSelectorService.js +6 -5
  121. package/app/services/auth/googleWebLinkService.js +3 -2
  122. package/app/services/auth/whatsappLoginLinkService.js +3 -2
  123. package/app/services/external/pokeApiService.js +4 -3
  124. package/app/services/group/groupMetadataService.js +24 -1
  125. package/app/services/infra/dbWriteQueue.js +57 -26
  126. package/app/services/infra/featureFlagService.js +2 -1
  127. package/app/services/messaging/captchaService.js +3 -2
  128. package/app/services/messaging/newsBroadcastService.js +846 -29
  129. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  130. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  131. package/app/services/multiSession/groupOwnershipService.js +890 -0
  132. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  133. package/app/services/multiSession/sessionRegistryService.js +293 -0
  134. package/app/services/sticker/stickerFocusService.js +11 -10
  135. package/app/store/aiPromptStore.js +36 -19
  136. package/app/store/conversationSessionStore.js +7 -6
  137. package/app/store/groupConfigStore.js +41 -5
  138. package/app/store/premiumUserStore.js +21 -7
  139. package/app/utils/antiLink/antiLinkModule.js +352 -16
  140. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  141. package/app/workers/aiLearningWorker.js +6 -5
  142. package/app/workers/commandConfigEnrichmentWorker.js +4 -3
  143. package/database/index.js +14 -8
  144. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  145. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  146. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  147. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  148. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  149. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  150. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  151. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  152. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  153. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  154. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  155. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  156. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  157. package/database/schema.sql +102 -1
  158. package/docker-compose.yml +4 -1
  159. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  160. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
  161. package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
  162. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  163. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
  164. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  165. package/docs/security/omnizap-static-security-headers.conf +25 -0
  166. package/docs/wiki/Home.md +1 -1
  167. package/ecosystem.prod.config.cjs +32 -12
  168. package/index.js +57 -23
  169. package/observability/alert-rules.yml +20 -0
  170. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  171. package/observability/mysql-setup.sql +4 -4
  172. package/observability/system-admin-observability.md +26 -0
  173. package/package.json +20 -6
  174. package/public/apple-touch-icon.png +0 -0
  175. package/public/comandos/commands-catalog.json +2853 -3326
  176. package/public/favicon-16x16.png +0 -0
  177. package/public/favicon-32x32.png +0 -0
  178. package/public/favicon.ico +0 -0
  179. package/public/js/apps/apiDocsApp.js +3 -2
  180. package/public/js/apps/commandsReactApp.js +280 -99
  181. package/public/js/apps/createPackApp.js +11 -10
  182. package/public/js/apps/homeReactApp.js +181 -130
  183. package/public/js/apps/loginReactApp.js +1 -1
  184. package/public/js/apps/stickersApp.js +263 -110
  185. package/public/js/apps/termsReactApp.js +73 -24
  186. package/public/js/apps/userApp.js +4 -3
  187. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  188. package/public/js/apps/userReactApp.js +355 -280
  189. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  190. package/public/pages/api-docs.html +1 -1
  191. package/public/pages/aup.html +2 -2
  192. package/public/pages/dpa.html +3 -3
  193. package/public/pages/licenca.html +4 -4
  194. package/public/pages/login.html +1 -1
  195. package/public/pages/notice-and-takedown.html +2 -2
  196. package/public/pages/politica-de-privacidade.html +6 -6
  197. package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
  198. package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
  199. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
  200. package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
  201. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
  202. package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
  203. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
  204. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
  205. package/public/pages/stickers-admin.html +1 -1
  206. package/public/pages/stickers-create.html +1 -1
  207. package/public/pages/stickers.html +6 -6
  208. package/public/pages/suboperadores.html +2 -2
  209. package/public/pages/termos-de-uso-texto-integral.html +6 -6
  210. package/public/pages/termos-de-uso.html +3 -3
  211. package/public/pages/user-password-reset.html +4 -5
  212. package/public/pages/user-systemadm.html +9 -463
  213. package/public/pages/user.html +2 -2
  214. package/scripts/clear-whatsapp-session.sh +123 -0
  215. package/scripts/core-ai-mode.mjs +163 -0
  216. package/scripts/deploy.sh +11 -1
  217. package/scripts/email-broadcast-terms-update.mjs +2 -1
  218. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  219. package/scripts/generate-commands-catalog.mjs +166 -2
  220. package/scripts/generate-module-agents.mjs +2 -1
  221. package/scripts/generate-seo-satellite-pages.mjs +5 -4
  222. package/scripts/github-deploy-notify.mjs +2 -1
  223. package/scripts/github-release-notify.mjs +25 -10
  224. package/scripts/new-whatsapp-session.sh +317 -0
  225. package/scripts/release.sh +2 -19
  226. package/scripts/security-smoketest.mjs +6 -5
  227. package/scripts/security-web-surface-check.mjs +218 -0
  228. package/scripts/sticker-catalog-loadtest.mjs +5 -4
  229. package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
  230. package/server/auth/jwt/webJwtService.js +1 -1
  231. package/server/auth/stickerCatalogAuthContext.js +2 -1
  232. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
  233. package/server/auth/userPassword/userPasswordAuthService.js +2 -1
  234. package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
  235. package/server/auth/webAccount/webAccountHandlers.js +9 -10
  236. package/server/controllers/admin/adminPanelHandlers.js +267 -16
  237. package/server/controllers/admin/systemAdminController.js +267 -0
  238. package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
  239. package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
  240. package/server/controllers/sticker/stickerCatalogController.js +23 -36
  241. package/server/controllers/system/contactController.js +9 -17
  242. package/server/controllers/system/githubController.js +3 -2
  243. package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
  244. package/server/controllers/system/systemController.js +254 -1
  245. package/server/controllers/system/systemMetricsController.js +2 -1
  246. package/server/controllers/userController.js +6 -0
  247. package/server/email/emailTemplateService.js +5 -3
  248. package/server/http/httpServer.js +11 -6
  249. package/server/middleware/rateLimit.js +2 -1
  250. package/server/middleware/securityHeaders.js +20 -1
  251. package/server/routes/admin/systemAdminRouter.js +6 -0
  252. package/server/routes/indexRouter.js +30 -6
  253. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  254. package/server/routes/static/staticPageRouter.js +27 -1
  255. package/server/utils/publicContact.js +31 -0
  256. package/utils/time/timeModule.js +135 -0
  257. package/utils/time/timeModule.test.js +65 -0
  258. package/utils/whatsapp/contactEnv.js +39 -0
  259. package/vite.config.mjs +7 -1
  260. package/public/assets/images/brand-icon-192.png +0 -0
  261. package/scripts/sync-readme-snapshot.mjs +0 -133
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import { pool } from '../../../database/index.js';
2
3
  import { getJidUser, isGroupJid, normalizeJid } from '../../config/index.js';
3
4
  import logger from '#logger';
@@ -504,7 +505,7 @@ const loadPocketItems = async (pocketName) => {
504
505
  };
505
506
 
506
507
  const getShopCatalog = async ({ forceRefresh = false } = {}) => {
507
- const now = Date.now();
508
+ const now = __timeNowMs();
508
509
  if (!forceRefresh && dynamicShopCache.items && dynamicShopCache.expiresAt > now) {
509
510
  return {
510
511
  items: dynamicShopCache.items,
@@ -544,7 +545,7 @@ const getShopCatalog = async ({ forceRefresh = false } = {}) => {
544
545
  dynamicShopCache.items = fallbackItems;
545
546
  dynamicShopCache.index = fallbackIndex;
546
547
  dynamicShopCache.aliasMap = aliasMap;
547
- dynamicShopCache.expiresAt = Date.now() + Math.min(SHOP_REFRESH_MS, 10 * 60 * 1000);
548
+ dynamicShopCache.expiresAt = __timeNowMs() + Math.min(SHOP_REFRESH_MS, 10 * 60 * 1000);
548
549
 
549
550
  return { items: fallbackItems, index: fallbackIndex, aliasMap };
550
551
  }
@@ -616,7 +617,7 @@ const resolveTravelEncounterPool = async (locationAreaKey) => {
616
617
  if (!key) return [];
617
618
 
618
619
  const cached = areaEncounterCache.get(key);
619
- if (cached && cached.expiresAt > Date.now()) {
620
+ if (cached && cached.expiresAt > __timeNowMs()) {
620
621
  return cached.pool;
621
622
  }
622
623
 
@@ -628,7 +629,7 @@ const resolveTravelEncounterPool = async (locationAreaKey) => {
628
629
  .slice(0, 40);
629
630
  areaEncounterCache.set(key, {
630
631
  pool,
631
- expiresAt: Date.now() + SHOP_REFRESH_MS,
632
+ expiresAt: __timeNowMs() + SHOP_REFRESH_MS,
632
633
  });
633
634
  return pool;
634
635
  } catch (error) {
@@ -690,7 +691,7 @@ const formatMissionRewardSummary = (reward, label) => {
690
691
  };
691
692
 
692
693
  const ensureMissionStateForUpdate = async ({ ownerJid, connection }) => {
693
- const refs = resolveMissionRefs(new Date());
694
+ const refs = resolveMissionRefs(__timeNow());
694
695
  let row = await getMissionProgressByOwnerForUpdate(ownerJid, connection);
695
696
 
696
697
  if (!row) {
@@ -792,7 +793,7 @@ const applyMissionEvent = async ({ ownerJid, eventKey, connection }) => {
792
793
  reward: DAILY_MISSION_REWARD,
793
794
  connection,
794
795
  });
795
- dailyClaimedAt = new Date();
796
+ dailyClaimedAt = __timeNow();
796
797
  notices.push(formatMissionRewardSummary(DAILY_MISSION_REWARD, 'diária'));
797
798
  }
798
799
 
@@ -803,7 +804,7 @@ const applyMissionEvent = async ({ ownerJid, eventKey, connection }) => {
803
804
  reward: WEEKLY_MISSION_REWARD,
804
805
  connection,
805
806
  });
806
- weeklyClaimedAt = new Date();
807
+ weeklyClaimedAt = __timeNow();
807
808
  notices.push(formatMissionRewardSummary(WEEKLY_MISSION_REWARD, 'semanal'));
808
809
  }
809
810
 
@@ -826,9 +827,9 @@ const applyMissionEvent = async ({ ownerJid, eventKey, connection }) => {
826
827
  const buildBattleChatKey = (chatJid, ownerJid) => `${chatJid}::${ownerJid}`;
827
828
  const extractSourceChatFromBattleKey = (battleChatKey) => String(battleChatKey || '').split('::')[0] || null;
828
829
 
829
- const nowPlusTtlDate = () => new Date(Date.now() + BATTLE_TTL_MS);
830
- const nowPlusRaidTtlDate = () => new Date(Date.now() + RAID_TTL_MS);
831
- const nowPlusPvpTtlDate = () => new Date(Date.now() + PVP_TTL_MS);
830
+ const nowPlusTtlDate = () => new Date(__timeNowMs() + BATTLE_TTL_MS);
831
+ const nowPlusRaidTtlDate = () => new Date(__timeNowMs() + RAID_TTL_MS);
832
+ const nowPlusPvpTtlDate = () => new Date(__timeNowMs() + PVP_TTL_MS);
832
833
 
833
834
  const parseBattleSnapshot = (battleState) => {
834
835
  const snapshot = battleState?.enemy_snapshot_json;
@@ -844,9 +845,9 @@ const toDateSafe = (value) => {
844
845
  return date;
845
846
  };
846
847
 
847
- const toDurationSeconds = (startedAt, endedAt = new Date()) => {
848
+ const toDurationSeconds = (startedAt, endedAt = __timeNow()) => {
848
849
  const start = toDateSafe(startedAt);
849
- const end = toDateSafe(endedAt) || new Date();
850
+ const end = toDateSafe(endedAt) || __timeNow();
850
851
  if (!start) return 0;
851
852
  const delta = (end.getTime() - start.getTime()) / 1000;
852
853
  if (!Number.isFinite(delta) || delta < 0) return 0;
@@ -864,7 +865,7 @@ const recordBattleDurationFromSnapshot = ({ snapshot, outcome }) => {
864
865
  };
865
866
 
866
867
  const markSessionSample = (ownerJid) => {
867
- const now = Date.now();
868
+ const now = __timeNowMs();
868
869
  const tracker = sessionTrackerMap.get(ownerJid);
869
870
  if (!tracker || now - tracker.lastAt > SESSION_IDLE_MS) {
870
871
  sessionTrackerMap.set(ownerJid, {
@@ -879,14 +880,14 @@ const markSessionSample = (ownerJid) => {
879
880
  recordRpgSessionDuration(durationSec);
880
881
  };
881
882
 
882
- const toUtcDateOnly = (value = new Date()) => {
883
+ const toUtcDateOnly = (value = __timeNow()) => {
883
884
  const date = value instanceof Date ? value : new Date(value);
884
885
  if (Number.isNaN(date.getTime())) return null;
885
886
  return date.toISOString().slice(0, 10);
886
887
  };
887
888
 
888
889
  const getDateOnlyOffset = (days = 0) => {
889
- const now = new Date();
890
+ const now = __timeNow();
890
891
  const atMidnightUtc = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
891
892
  atMidnightUtc.setUTCDate(atMidnightUtc.getUTCDate() + days);
892
893
  return toUtcDateOnly(atMidnightUtc);
@@ -894,14 +895,14 @@ const getDateOnlyOffset = (days = 0) => {
894
895
 
895
896
  const toDateFromDateOnly = (dateOnly, plusDays = 0) => {
896
897
  const source = String(dateOnly || '').trim();
897
- if (!source) return new Date();
898
+ if (!source) return __timeNow();
898
899
  const date = new Date(`${source}T00:00:00.000Z`);
899
- if (Number.isNaN(date.getTime())) return new Date();
900
+ if (Number.isNaN(date.getTime())) return __timeNow();
900
901
  date.setUTCDate(date.getUTCDate() + plusDays);
901
902
  return date;
902
903
  };
903
904
 
904
- const getCurrentMissionRefs = () => resolveMissionRefs(new Date());
905
+ const getCurrentMissionRefs = () => resolveMissionRefs(__timeNow());
905
906
 
906
907
  const getCurrentWeekRefDate = () => getCurrentMissionRefs().weeklyRefDate;
907
908
 
@@ -1245,7 +1246,7 @@ const resolveOpponentJidFromArgs = async ({ actionArgs = [], mentionedJids = []
1245
1246
  const getCooldownSecondsLeft = (ownerJid) => {
1246
1247
  const lastAt = playerCooldownMap.get(ownerJid);
1247
1248
  if (!lastAt) return 0;
1248
- const diff = Date.now() - lastAt;
1249
+ const diff = __timeNowMs() - lastAt;
1249
1250
  if (diff >= COOLDOWN_MS) return 0;
1250
1251
  return Math.max(1, Math.ceil((COOLDOWN_MS - diff) / 1000));
1251
1252
  };
@@ -1253,7 +1254,7 @@ const getCooldownSecondsLeft = (ownerJid) => {
1253
1254
  const getPvpCooldownSecondsLeft = (ownerJid) => {
1254
1255
  const lastAt = pvpCooldownMap.get(ownerJid);
1255
1256
  if (!lastAt) return 0;
1256
- const diff = Date.now() - lastAt;
1257
+ const diff = __timeNowMs() - lastAt;
1257
1258
  if (diff >= PVP_CHALLENGE_COOLDOWN_MS) return 0;
1258
1259
  return Math.max(1, Math.ceil((PVP_CHALLENGE_COOLDOWN_MS - diff) / 1000));
1259
1260
  };
@@ -1263,11 +1264,11 @@ const shouldApplyCooldown = (action) => {
1263
1264
  };
1264
1265
 
1265
1266
  const touchCooldown = (ownerJid) => {
1266
- playerCooldownMap.set(ownerJid, Date.now());
1267
+ playerCooldownMap.set(ownerJid, __timeNowMs());
1267
1268
  };
1268
1269
 
1269
1270
  const touchPvpCooldown = (ownerJid) => {
1270
- pvpCooldownMap.set(ownerJid, Date.now());
1271
+ pvpCooldownMap.set(ownerJid, __timeNowMs());
1271
1272
  };
1272
1273
 
1273
1274
  const loadPokemonDisplayData = async (pokemonRow) => {
@@ -2077,7 +2078,7 @@ const handleProfile = async ({ ownerJid, chatJid, commandPrefix }) => {
2077
2078
  text,
2078
2079
  profileCanvas: {
2079
2080
  trainerLabel: toMentionLabel(ownerJid),
2080
- generatedAtLabel: toDateLabel(Date.now()),
2081
+ generatedAtLabel: toDateLabel(__timeNowMs()),
2081
2082
  activePokemon: {
2082
2083
  displayName: activeDisplay?.displayName || activeDisplay?.name || null,
2083
2084
  name: activeDisplay?.name || null,
@@ -2273,7 +2274,7 @@ const handleExplore = async ({ ownerJid, chatJid, commandPrefix }) => {
2273
2274
 
2274
2275
  const battleSnapshot = {
2275
2276
  turn: 1,
2276
- startedAt: new Date().toISOString(),
2277
+ startedAt: __timeNowIso(),
2277
2278
  mode: 'wild',
2278
2279
  biome: biome
2279
2280
  ? {
@@ -2397,7 +2398,7 @@ const handleGym = async ({ ownerJid, chatJid, commandPrefix }) => {
2397
2398
 
2398
2399
  const battleSnapshot = {
2399
2400
  turn: 1,
2400
- startedAt: new Date().toISOString(),
2401
+ startedAt: __timeNowIso(),
2401
2402
  mode: 'gym',
2402
2403
  biome: biome
2403
2404
  ? {
@@ -2786,7 +2787,7 @@ const requireInventoryItem = async ({ ownerJid, itemKey, connection }) => {
2786
2787
  const getEconomyRescueSecondsLeft = (ownerJid) => {
2787
2788
  const lastAt = economyRescueMap.get(ownerJid);
2788
2789
  if (!lastAt) return 0;
2789
- const diff = Date.now() - lastAt;
2790
+ const diff = __timeNowMs() - lastAt;
2790
2791
  if (diff >= ECONOMY_RESCUE_COOLDOWN_MS) return 0;
2791
2792
  return Math.max(1, Math.ceil((ECONOMY_RESCUE_COOLDOWN_MS - diff) / 1000));
2792
2793
  };
@@ -2813,7 +2814,7 @@ const tryApplyEconomyRescue = async ({ ownerJid, player, activePokemon = null, c
2813
2814
  const nextGold = currentGold + ECONOMY_RESCUE_GOLD;
2814
2815
  await updatePlayerGoldOnly({ jid: ownerJid, gold: nextGold }, connection);
2815
2816
  await addInventoryItem({ ownerJid, itemKey: 'potion', quantity: ECONOMY_RESCUE_POTION_QTY }, connection);
2816
- economyRescueMap.set(ownerJid, Date.now());
2817
+ economyRescueMap.set(ownerJid, __timeNowMs());
2817
2818
 
2818
2819
  logger.info('Auxílio econômico aplicado para evitar travamento no início.', {
2819
2820
  ownerJid,
@@ -4421,7 +4422,7 @@ const toRaidView = (raidRow) => {
4421
4422
  const isDateExpired = (value) => {
4422
4423
  const date = toDateSafe(value);
4423
4424
  if (!date) return false;
4424
- return date.getTime() <= Date.now();
4425
+ return date.getTime() <= __timeNowMs();
4425
4426
  };
4426
4427
 
4427
4428
  const formatParticipantRows = (participants = []) =>
@@ -4441,7 +4442,7 @@ const resolveRaidRewards = ({ bossLevel, totalDamage, participantDamage }) => {
4441
4442
 
4442
4443
  const buildPvpSnapshotState = ({ challengerJid, challengerPokemonId, challengerSnapshot, opponentJid, opponentPokemonId, opponentSnapshot, turnJid }) => {
4443
4444
  return {
4444
- startedAt: new Date().toISOString(),
4445
+ startedAt: __timeNowIso(),
4445
4446
  turn: 1,
4446
4447
  players: {
4447
4448
  [challengerJid]: {
@@ -4919,7 +4920,7 @@ const handleRaid = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [] })
4919
4920
  currentHp: bossMaxHp,
4920
4921
  };
4921
4922
 
4922
- const startedAt = new Date();
4923
+ const startedAt = __timeNow();
4923
4924
  const endsAt = nowPlusRaidTtlDate();
4924
4925
  await upsertRaidState(
4925
4926
  {
@@ -5435,7 +5436,7 @@ const handlePvpQueue = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [
5435
5436
  {
5436
5437
  chatJid,
5437
5438
  ownerJid: canonicalOwnerJid,
5438
- expiresAt: new Date(Date.now() + PVP_QUEUE_TTL_MS),
5439
+ expiresAt: new Date(__timeNowMs() + PVP_QUEUE_TTL_MS),
5439
5440
  },
5440
5441
  connection,
5441
5442
  );
@@ -5661,7 +5662,7 @@ const handlePvp = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [], me
5661
5662
  {
5662
5663
  id: challenge.id,
5663
5664
  status: 'active',
5664
- startedAt: new Date(),
5665
+ startedAt: __timeNow(),
5665
5666
  expiresAt: nowPlusPvpTtlDate(),
5666
5667
  },
5667
5668
  connection,
@@ -5721,7 +5722,7 @@ const handlePvp = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [], me
5721
5722
  status: 'finished',
5722
5723
  winnerJid: opponentJid,
5723
5724
  turnJid: null,
5724
- expiresAt: new Date(),
5725
+ expiresAt: __timeNow(),
5725
5726
  },
5726
5727
  connection,
5727
5728
  );
@@ -5797,7 +5798,7 @@ const handlePvp = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [], me
5797
5798
  status: 'finished',
5798
5799
  winnerJid: toInt(me?.pokemon?.currentHp, 0) > 0 ? canonicalOwnerJid : opponentJid,
5799
5800
  turnJid: null,
5800
- expiresAt: new Date(),
5801
+ expiresAt: __timeNow(),
5801
5802
  },
5802
5803
  connection,
5803
5804
  );
@@ -5951,7 +5952,7 @@ const handlePvp = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [], me
5951
5952
  winnerJid,
5952
5953
  turnJid: null,
5953
5954
  battleSnapshot: nextSnapshot,
5954
- expiresAt: new Date(),
5955
+ expiresAt: __timeNow(),
5955
5956
  },
5956
5957
  connection,
5957
5958
  );
@@ -6207,7 +6208,7 @@ const handleTrade = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [],
6207
6208
  receiverJid,
6208
6209
  proposerOffer,
6209
6210
  receiverOffer,
6210
- expiresAt: new Date(Date.now() + TRADE_TTL_MS),
6211
+ expiresAt: new Date(__timeNowMs() + TRADE_TTL_MS),
6211
6212
  },
6212
6213
  connection,
6213
6214
  );
@@ -6284,7 +6285,7 @@ const handleTrade = async ({ ownerJid, chatJid, commandPrefix, actionArgs = [],
6284
6285
  {
6285
6286
  id: offer.id,
6286
6287
  status: 'accepted',
6287
- acceptedAt: new Date(),
6288
+ acceptedAt: __timeNow(),
6288
6289
  },
6289
6290
  connection,
6290
6291
  );
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import axios from 'axios';
2
3
  import { createCanvas, loadImage } from 'canvas';
3
4
  import logger from '#logger';
@@ -75,7 +76,7 @@ const drawRoundRect = (ctx, x, y, width, height, radius, fillStyle) => {
75
76
 
76
77
  const cleanupImageCache = () => {
77
78
  if (imageCache.size <= IMAGE_CACHE_LIMIT) return;
78
- const now = Date.now();
79
+ const now = __timeNowMs();
79
80
  for (const [key, value] of imageCache.entries()) {
80
81
  if (!value || value.expiresAt <= now) imageCache.delete(key);
81
82
  }
@@ -90,7 +91,7 @@ const resolveImage = async (url) => {
90
91
  if (!normalized) return null;
91
92
 
92
93
  const cached = imageCache.get(normalized);
93
- if (cached && cached.expiresAt > Date.now()) return cached.image;
94
+ if (cached && cached.expiresAt > __timeNowMs()) return cached.image;
94
95
 
95
96
  try {
96
97
  const response = await axios.get(normalized, {
@@ -101,7 +102,7 @@ const resolveImage = async (url) => {
101
102
  const image = await loadImage(Buffer.from(response.data));
102
103
  imageCache.set(normalized, {
103
104
  image,
104
- expiresAt: Date.now() + IMAGE_CACHE_TTL_MS,
105
+ expiresAt: __timeNowMs() + IMAGE_CACHE_TTL_MS,
105
106
  });
106
107
  cleanupImageCache();
107
108
  return image;
@@ -7,7 +7,7 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
7
7
  - arquivo_base: `app/modules/statsModule/commandConfig.json`
8
8
  - schema_version: `2.0.0`
9
9
  - module_enabled: `true`
10
- - generated_at: `2026-03-11T02:35:17.177Z`
10
+ - generated_at: `2026-03-17T04:04:14.195Z`
11
11
 
12
12
  ## Escopo do Modulo
13
13
 
@@ -335,6 +335,35 @@
335
335
  "schema": "legacy_v1_and_v2",
336
336
  "legacy_name": "classificacao",
337
337
  "legacy_fields_present": ["descricao", "metodos_de_uso", "permissao_necessaria", "local_de_uso", "informacoes_coletadas", "argumentos", "pre_condicoes", "dependencias_externas", "efeitos_colaterais", "observabilidade", "privacidade", "acesso", "limite_uso_por_plano"]
338
+ },
339
+ "user_experience": {
340
+ "resumo_usuario": "Mostra os 5 membros mais ativos do grupo. Não requer Premium.",
341
+ "quando_usar": ["Quando quiser ver quem participa mais no grupo.", "Antes de uma discussão ou decisão, para identificar os participantes mais ativos.", "Para comparar participação entre diferentes períodos."],
342
+ "exemplos_reais": [
343
+ {
344
+ "situacao": "Quero ver quem são os 5 membros mais ativos no grupo agora.",
345
+ "comando": "<prefix>classificacao",
346
+ "resposta_esperada": "Top 5 ativos no grupo: 1) @usuario1, 2) @usuario2, 3) @usuario3, 4) @usuario4, 5) @usuario5.",
347
+ "variacao": "<prefix>rank."
348
+ },
349
+ {
350
+ "situacao": "Quero o mesmo resultado usando outro atalho.",
351
+ "comando": "<prefix>rank",
352
+ "resposta_esperada": "Top 5 ativos no grupo: 1) @usuario1, 2) @usuario2, 3) @usuario3, 4) @usuario4, 5) @usuario5.",
353
+ "variacao": "<prefix>classificacao."
354
+ },
355
+ {
356
+ "situacao": "Outra forma rápida de ver o top 5.",
357
+ "comando": "<prefix>top5",
358
+ "resposta_esperada": "Top 5 ativos no grupo: 1) @usuario1, 2) @usuario2, 3) @usuario3, 4) @usuario4, 5) @usuario5.",
359
+ "variacao": "<prefix>classificacao."
360
+ }
361
+ ],
362
+ "resposta_esperada": ["A lista com os 5 membros mais ativos no grupo é exibida."],
363
+ "erros_comuns_usuario": ["Não está em um grupo ao enviar o comando.", "Ainda não fez login com Google no app.", "Formato do comando incorreto; use um dos métodos de uso: <prefix>classificacao, <prefix>rank, <prefix>top5.", "O grupo não possui participantes ativos suficientes."],
364
+ "passos_se_der_erro": ["Confira se você está dentro de um grupo.", "Verifique se você está logado com Google.", "Envie o comando correto: <prefix>classificacao, <prefix>rank ou <prefix>top5.", "Tente novamente em poucos segundos.", "Se o erro persistir, peça para o admin revisar as permissões."],
365
+ "resumo_usuario_origem": "auto_ia_assistida",
366
+ "resumo_usuario_revisao_pendente": true
338
367
  }
339
368
  },
340
369
  {
@@ -531,6 +560,35 @@
531
560
  "schema": "legacy_v1_and_v2",
532
561
  "legacy_name": "classificacaoglobal",
533
562
  "legacy_fields_present": ["descricao", "metodos_de_uso", "permissao_necessaria", "local_de_uso", "informacoes_coletadas", "argumentos", "pre_condicoes", "dependencias_externas", "efeitos_colaterais", "observabilidade", "privacidade", "acesso", "limite_uso_por_plano"]
563
+ },
564
+ "user_experience": {
565
+ "resumo_usuario": "Mostra o top 5 global de atividade. Use para ver quem lidera a atividade mundial e comparar com seu desempenho.",
566
+ "quando_usar": ["Quero saber quem está no topo da atividade global agora.", "Quero comparar meu desempenho com o ranking global.", "Preciso de uma visão rápida do ranking mundial sem dados locais."],
567
+ "exemplos_reais": [
568
+ {
569
+ "situacao": "Quero ver o ranking global rapidamente.",
570
+ "comando": "<prefix>classificacaoglobal",
571
+ "resposta_esperada": "Top 5 global de atividade exibidos com nomes e pontos. Ex.: 1) Nome - 980 pts, 2) Nome2 - 950 pts, ...",
572
+ "variacao": "<prefix>globalrank."
573
+ },
574
+ {
575
+ "situacao": "Quero usar a outra forma de comando para o ranking.",
576
+ "comando": "<prefix>globalrank",
577
+ "resposta_esperada": "Top 5 global de atividade exibidos com nomes e pontos.",
578
+ "variacao": "<prefix>classificacaoglobal."
579
+ },
580
+ {
581
+ "situacao": "Não estou logado com Google e tento ver o ranking.",
582
+ "comando": "<prefix>classificacaoglobal",
583
+ "resposta_esperada": "Você precisa estar logado com sua conta Google para ver o ranking global.",
584
+ "variacao": "Sem login Google."
585
+ }
586
+ ],
587
+ "resposta_esperada": ["Top 5 global de atividade exibidos com nomes e pontos.", "Se não houver dados, será exibida mensagem informando a ausência de resultados para o ranking global."],
588
+ "erros_comuns_usuario": ["Esquecer o prefixo no início do comando.", "Digitar o comando com espaços extras ou caracteres não reconhecidos.", "Tentar usar sem estar logado com Google (googleLogin requerido).", "Usar uma forma de comando incorreta ou digitá-lo com erro de grafia."],
589
+ "passos_se_der_erro": ["Confira se está logado com a conta Google ligada.", "Digite novamente um dos comandos válidos: <prefix>classificacaoglobal ou <prefix>globalrank.", "Verifique se não há espaço extra ou caracteres estranhos no comando.", "Se o problema persistir, peça ajuda e informe a mensagem de erro e o comando utilizado."],
590
+ "resumo_usuario_origem": "auto_ia_assistida",
591
+ "resumo_usuario_revisao_pendente": true
534
592
  }
535
593
  }
536
594
  ],
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import { createCanvas, loadImage } from 'canvas';
2
3
  import { executeQuery } from '../../../database/index.js';
3
4
  import { getJidUser, getProfilePicBuffer, normalizeJid } from '../../config/index.js';
@@ -179,17 +180,17 @@ const getCachedProfilePic = (jid) => {
179
180
  const entry = PROFILE_PIC_CACHE.get(jid);
180
181
  if (!entry) return null;
181
182
  const lastAccess = entry.lastAccess || entry.createdAt || 0;
182
- if (Date.now() - lastAccess > PROFILE_CACHE_TTL_MS) {
183
+ if (__timeNowMs() - lastAccess > PROFILE_CACHE_TTL_MS) {
183
184
  PROFILE_PIC_CACHE.delete(jid);
184
185
  return null;
185
186
  }
186
- entry.lastAccess = Date.now();
187
+ entry.lastAccess = __timeNowMs();
187
188
  return entry.buffer || null;
188
189
  };
189
190
 
190
191
  const setCachedProfilePic = (jid, buffer) => {
191
192
  if (!jid || !buffer) return;
192
- PROFILE_PIC_CACHE.set(jid, { buffer, createdAt: Date.now(), lastAccess: Date.now() });
193
+ PROFILE_PIC_CACHE.set(jid, { buffer, createdAt: __timeNowMs(), lastAccess: __timeNowMs() });
193
194
  if (PROFILE_PIC_CACHE.size > PROFILE_CACHE_LIMIT) {
194
195
  const oldestKey = Array.from(PROFILE_PIC_CACHE.entries()).sort((a, b) => (a[1].lastAccess || a[1].createdAt || 0) - (b[1].lastAccess || b[1].createdAt || 0))[0]?.[0];
195
196
  if (oldestKey) PROFILE_PIC_CACHE.delete(oldestKey);
@@ -1292,7 +1293,7 @@ export const renderRankingImage = async ({ sock, remoteJid, rows, totalMessages,
1292
1293
  }
1293
1294
 
1294
1295
  const footerY = height - 36;
1295
- const updatedAt = formatDate(new Date());
1296
+ const updatedAt = formatDate(__timeNow());
1296
1297
  ctx.fillStyle = 'rgba(148, 163, 184, 0.7)';
1297
1298
  ctx.font = uiFont(15, 500);
1298
1299
  ctx.textAlign = 'left';
@@ -7,7 +7,7 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
7
7
  - arquivo_base: `app/modules/stickerModule/commandConfig.json`
8
8
  - schema_version: `2.0.0`
9
9
  - module_enabled: `true`
10
- - generated_at: `2026-03-11T02:35:17.177Z`
10
+ - generated_at: `2026-03-17T04:04:14.195Z`
11
11
 
12
12
  ## Escopo do Modulo
13
13
 
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import fs from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import { spawn } from 'node:child_process';
@@ -154,11 +155,11 @@ function normalizeMetadataText(value, fallback = '') {
154
155
  */
155
156
  export async function addStickerMetadata(stickerPath, packName, packAuthor, replaceContext = {}) {
156
157
  const { senderName = '', userId = '' } = replaceContext;
157
- const now = new Date();
158
+ const now = __timeNow();
158
159
  const pad = (n) => n.toString().padStart(2, '0');
159
160
  const dataAtual = `${pad(now.getDate())}/${pad(now.getMonth() + 1)}/${now.getFullYear()}`;
160
161
  const horaAtual = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
161
- const baseSenderName = normalizeMetadataText(senderName, 'OmniZap System');
162
+ const baseSenderName = normalizeMetadataText(senderName, 'Omnizap');
162
163
  const resolvedUserId = String(getJidUser(userId) || userId || '').trim();
163
164
 
164
165
  function doReplaces(str) {
@@ -169,7 +170,7 @@ export async function addStickerMetadata(stickerPath, packName, packAuthor, repl
169
170
  .replace(/#id/gi, resolvedUserId);
170
171
  }
171
172
 
172
- const finalPackName = normalizeMetadataText(doReplaces(packName), 'OmniZap System');
173
+ const finalPackName = normalizeMetadataText(doReplaces(packName), 'Omnizap');
173
174
  const finalPackAuthor = normalizeMetadataText(doReplaces(packAuthor), baseSenderName);
174
175
 
175
176
  logger.info(`addStickerMetadata Adicionando metadados ao sticker. Nome: "${finalPackName}", Autor: "${finalPackAuthor}"`);