@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.
- package/.env.example +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +660 -158
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +93 -13
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +135 -27
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/js/apps/apiDocsApp.js +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
3
4
|
import { URL } from 'node:url';
|
|
4
5
|
|
|
5
6
|
import logger from '#logger';
|
|
7
|
+
import {
|
|
8
|
+
forceSystemAdminGroupFailover,
|
|
9
|
+
listSystemAdminAssignmentHistory,
|
|
10
|
+
listSystemAdminAssignments,
|
|
11
|
+
listSystemAdminSessions,
|
|
12
|
+
setSystemAdminGroupPin,
|
|
13
|
+
triggerSystemAdminManualRebalance,
|
|
14
|
+
} from '../system/systemController.js';
|
|
6
15
|
|
|
7
16
|
const parseEnvBool = (value, fallback) => {
|
|
8
17
|
if (value === undefined || value === null || value === '') return fallback;
|
|
@@ -25,6 +34,8 @@ const SYSTEM_ADMIN_API_BASE_PATH = normalizeBasePath(process.env.SYSTEM_ADMIN_AP
|
|
|
25
34
|
const SYSTEM_ADMIN_API_SESSION_PATH = `${SYSTEM_ADMIN_API_BASE_PATH}/session`;
|
|
26
35
|
const LEGACY_SYSTEM_ADMIN_API_BASE_PATH = `${LEGACY_STICKER_API_BASE_PATH}/admin`;
|
|
27
36
|
const LEGACY_SYSTEM_ADMIN_API_SESSION_PATH = `${LEGACY_SYSTEM_ADMIN_API_BASE_PATH}/session`;
|
|
37
|
+
const SYSTEM_ADMIN_MULTI_SESSION_API_PATH = `${SYSTEM_ADMIN_API_BASE_PATH}/multi-session`;
|
|
38
|
+
const LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH = `${LEGACY_SYSTEM_ADMIN_API_BASE_PATH}/multi-session`;
|
|
28
39
|
const STICKER_LOGIN_WEB_PATH = normalizeBasePath(process.env.STICKER_LOGIN_WEB_PATH, '/login');
|
|
29
40
|
const STICKER_WEB_PATH = normalizeBasePath(process.env.STICKER_WEB_PATH, '/stickers');
|
|
30
41
|
const STICKER_ADMIN_WEB_PATH = `${STICKER_WEB_PATH}/admin`;
|
|
@@ -37,6 +48,9 @@ const SITE_ORIGIN = String(process.env.SITE_ORIGIN || 'https://omnizap.shop')
|
|
|
37
48
|
|
|
38
49
|
const USER_SYSTEMADM_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'user-systemadm.html');
|
|
39
50
|
const LEGACY_STICKER_ADMIN_TEMPLATE_PATH = path.join(process.cwd(), 'public', 'pages', 'stickers-admin.html');
|
|
51
|
+
const SYSTEM_ADMIN_OPS_TOKEN = String(
|
|
52
|
+
process.env.SYSTEM_ADMIN_OPS_TOKEN || process.env.USER_INTERNAL_API_TOKEN || process.env.ADMIN_TOKEN || process.env.ADMIN_API_TOKEN || '',
|
|
53
|
+
).trim();
|
|
40
54
|
|
|
41
55
|
let stickerCatalogControllerPromise = null;
|
|
42
56
|
const loadStickerCatalogController = async () => {
|
|
@@ -108,6 +122,106 @@ const mapAdminApiPathToLegacy = (pathname) => {
|
|
|
108
122
|
return null;
|
|
109
123
|
};
|
|
110
124
|
|
|
125
|
+
const mapMultiSessionApiPath = (pathname) => {
|
|
126
|
+
if (hasPathPrefix(pathname, SYSTEM_ADMIN_MULTI_SESSION_API_PATH)) {
|
|
127
|
+
const suffix = pathname.slice(SYSTEM_ADMIN_MULTI_SESSION_API_PATH.length);
|
|
128
|
+
return suffix || '/';
|
|
129
|
+
}
|
|
130
|
+
if (hasPathPrefix(pathname, LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH)) {
|
|
131
|
+
const suffix = pathname.slice(LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH.length);
|
|
132
|
+
return suffix || '/';
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const constantTimeStringEqual = (left, right) => {
|
|
138
|
+
const leftBuffer = Buffer.from(String(left || ''), 'utf8');
|
|
139
|
+
const rightBuffer = Buffer.from(String(right || ''), 'utf8');
|
|
140
|
+
if (!leftBuffer.length || leftBuffer.length !== rightBuffer.length) return false;
|
|
141
|
+
try {
|
|
142
|
+
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const extractBearerToken = (req) => {
|
|
149
|
+
const authHeader = String(req?.headers?.authorization || '').trim();
|
|
150
|
+
if (!authHeader.toLowerCase().startsWith('bearer ')) return '';
|
|
151
|
+
return authHeader.slice(7).trim();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const resolveOpsTokenFromRequest = (req) =>
|
|
155
|
+
String(req?.headers?.['x-system-admin-token'] || req?.headers?.['x-internal-api-token'] || req?.headers?.['x-admin-token'] || '').trim() ||
|
|
156
|
+
extractBearerToken(req);
|
|
157
|
+
|
|
158
|
+
const hasValidOpsToken = (req) => {
|
|
159
|
+
if (!SYSTEM_ADMIN_OPS_TOKEN) return true;
|
|
160
|
+
const requestToken = resolveOpsTokenFromRequest(req);
|
|
161
|
+
if (!requestToken) return false;
|
|
162
|
+
return constantTimeStringEqual(requestToken, SYSTEM_ADMIN_OPS_TOKEN);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const readJsonBody = async (req, { maxBytes = 64 * 1024 } = {}) =>
|
|
166
|
+
new Promise((resolve, reject) => {
|
|
167
|
+
const chunks = [];
|
|
168
|
+
let total = 0;
|
|
169
|
+
|
|
170
|
+
req.on('data', (chunk) => {
|
|
171
|
+
total += chunk.length;
|
|
172
|
+
if (total > maxBytes) {
|
|
173
|
+
const error = new Error('Payload excedeu limite permitido.');
|
|
174
|
+
error.statusCode = 413;
|
|
175
|
+
reject(error);
|
|
176
|
+
req.destroy();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
chunks.push(chunk);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
req.on('end', () => {
|
|
183
|
+
const raw = Buffer.concat(chunks).toString('utf8').trim();
|
|
184
|
+
if (!raw) {
|
|
185
|
+
resolve({});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
resolve(JSON.parse(raw));
|
|
191
|
+
} catch {
|
|
192
|
+
const error = new Error('JSON invalido.');
|
|
193
|
+
error.statusCode = 400;
|
|
194
|
+
reject(error);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
req.on('error', (error) => reject(error));
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const parseBool = (value, fallback = false) => {
|
|
202
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
203
|
+
const normalized = String(value).trim().toLowerCase();
|
|
204
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
205
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
206
|
+
return fallback;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const parsePositiveInt = (value, fallback = 200, min = 1, max = 5000) => {
|
|
210
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
211
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
212
|
+
return Math.max(min, Math.min(max, parsed));
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const decodePathSegment = (value) => {
|
|
216
|
+
const raw = String(value || '').trim();
|
|
217
|
+
if (!raw) return '';
|
|
218
|
+
try {
|
|
219
|
+
return decodeURIComponent(raw);
|
|
220
|
+
} catch {
|
|
221
|
+
return raw;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
111
225
|
const renderUserSystemAdminHtml = async () => {
|
|
112
226
|
const template = await fs.readFile(USER_SYSTEMADM_TEMPLATE_PATH, 'utf8');
|
|
113
227
|
const dataAttributes = {
|
|
@@ -124,13 +238,147 @@ const renderUserSystemAdminHtml = async () => {
|
|
|
124
238
|
return html;
|
|
125
239
|
};
|
|
126
240
|
|
|
241
|
+
const requireSystemAdminOpsAccess = (req, res) => {
|
|
242
|
+
if (hasValidOpsToken(req)) return true;
|
|
243
|
+
sendJson(req, res, 401, { error: 'Nao autorizado para operacoes de system admin.' });
|
|
244
|
+
return false;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const normalizeMultiSessionSubPath = (value) => {
|
|
248
|
+
const raw = String(value || '/').trim();
|
|
249
|
+
if (!raw || raw === '/') return '/';
|
|
250
|
+
return `/${raw
|
|
251
|
+
.replace(/^\/+/g, '')
|
|
252
|
+
.replace(/\/+$/g, '')}`;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const handleMultiSessionOpsRequest = async (req, res, { pathname, url }) => {
|
|
256
|
+
if (!requireSystemAdminOpsAccess(req, res)) return true;
|
|
257
|
+
|
|
258
|
+
const subPath = normalizeMultiSessionSubPath(pathname);
|
|
259
|
+
const requestUrl = (() => {
|
|
260
|
+
try {
|
|
261
|
+
return new URL(String(url?.href || req.url || '/'), SITE_ORIGIN);
|
|
262
|
+
} catch {
|
|
263
|
+
return new URL(SITE_ORIGIN);
|
|
264
|
+
}
|
|
265
|
+
})();
|
|
266
|
+
|
|
267
|
+
if (subPath === '/sessions') {
|
|
268
|
+
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
269
|
+
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
const payload = await listSystemAdminSessions({
|
|
273
|
+
status: requestUrl.searchParams.get('status'),
|
|
274
|
+
limit: parsePositiveInt(requestUrl.searchParams.get('limit'), 200, 1, 5000),
|
|
275
|
+
});
|
|
276
|
+
sendJson(req, res, 200, payload);
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (subPath === '/assignments') {
|
|
281
|
+
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
282
|
+
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
const payload = await listSystemAdminAssignments({
|
|
286
|
+
groupJid: requestUrl.searchParams.get('group_jid'),
|
|
287
|
+
ownerSessionId: requestUrl.searchParams.get('owner_session_id'),
|
|
288
|
+
includeExpired: parseBool(requestUrl.searchParams.get('include_expired'), false),
|
|
289
|
+
limit: parsePositiveInt(requestUrl.searchParams.get('limit'), 200, 1, 5000),
|
|
290
|
+
});
|
|
291
|
+
sendJson(req, res, 200, payload);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (subPath === '/history') {
|
|
296
|
+
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
297
|
+
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
const payload = await listSystemAdminAssignmentHistory({
|
|
301
|
+
groupJid: requestUrl.searchParams.get('group_jid'),
|
|
302
|
+
limit: parsePositiveInt(requestUrl.searchParams.get('limit'), 100, 1, 5000),
|
|
303
|
+
});
|
|
304
|
+
sendJson(req, res, 200, payload);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (subPath === '/rebalance') {
|
|
309
|
+
if (!['POST'].includes(req.method || '')) {
|
|
310
|
+
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
const payload = await triggerSystemAdminManualRebalance();
|
|
314
|
+
sendJson(req, res, 200, payload);
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const groupActionMatch = subPath.match(/^\/groups\/([^/]+)\/(pin|unpin|failover)$/i);
|
|
319
|
+
if (groupActionMatch) {
|
|
320
|
+
if (!['POST'].includes(req.method || '')) {
|
|
321
|
+
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const groupJid = decodePathSegment(groupActionMatch[1]);
|
|
326
|
+
const action = String(groupActionMatch[2] || '').toLowerCase();
|
|
327
|
+
const body = await readJsonBody(req).catch((error) => {
|
|
328
|
+
const statusCode = Number(error?.statusCode || 400);
|
|
329
|
+
sendJson(req, res, statusCode, { error: error?.message || 'Falha ao interpretar payload JSON.' });
|
|
330
|
+
return null;
|
|
331
|
+
});
|
|
332
|
+
if (body === null) return true;
|
|
333
|
+
|
|
334
|
+
if (action === 'pin' || action === 'unpin') {
|
|
335
|
+
const pinned = action === 'pin';
|
|
336
|
+
const payload = await setSystemAdminGroupPin({
|
|
337
|
+
groupJid,
|
|
338
|
+
pinned,
|
|
339
|
+
sessionId: body?.session_id || body?.sessionId || null,
|
|
340
|
+
reason: body?.reason || null,
|
|
341
|
+
changedBy: 'system_admin_api',
|
|
342
|
+
metadata: body?.metadata || null,
|
|
343
|
+
});
|
|
344
|
+
sendJson(req, res, 200, payload);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (action === 'failover') {
|
|
349
|
+
const targetSessionId = String(body?.target_session_id || body?.targetSessionId || requestUrl.searchParams.get('target_session_id') || '')
|
|
350
|
+
.trim()
|
|
351
|
+
.slice(0, 64);
|
|
352
|
+
if (!targetSessionId) {
|
|
353
|
+
sendJson(req, res, 400, { error: 'target_session_id e obrigatorio.' });
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const payload = await forceSystemAdminGroupFailover({
|
|
358
|
+
groupJid,
|
|
359
|
+
targetSessionId,
|
|
360
|
+
reason: body?.reason || 'admin_force_failover',
|
|
361
|
+
changedBy: 'system_admin_api',
|
|
362
|
+
metadata: body?.metadata || null,
|
|
363
|
+
});
|
|
364
|
+
sendJson(req, res, 200, payload);
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
sendJson(req, res, 404, { error: 'Endpoint de operacao multi-session nao encontrado.' });
|
|
370
|
+
return true;
|
|
371
|
+
};
|
|
372
|
+
|
|
127
373
|
export const getSystemAdminRouteConfig = () => ({
|
|
128
374
|
webPath: USER_SYSTEMADM_WEB_PATH,
|
|
129
375
|
legacyWebPath: STICKER_ADMIN_WEB_PATH,
|
|
130
376
|
apiAdminBasePath: SYSTEM_ADMIN_API_BASE_PATH,
|
|
131
377
|
apiAdminSessionPath: SYSTEM_ADMIN_API_SESSION_PATH,
|
|
378
|
+
apiAdminMultiSessionPath: SYSTEM_ADMIN_MULTI_SESSION_API_PATH,
|
|
132
379
|
legacyApiAdminBasePath: LEGACY_SYSTEM_ADMIN_API_BASE_PATH,
|
|
133
380
|
legacyApiAdminSessionPath: LEGACY_SYSTEM_ADMIN_API_SESSION_PATH,
|
|
381
|
+
legacyApiAdminMultiSessionPath: LEGACY_SYSTEM_ADMIN_MULTI_SESSION_API_PATH,
|
|
134
382
|
});
|
|
135
383
|
|
|
136
384
|
export const maybeHandleSystemAdminRequest = async (req, res, { pathname, url }) => {
|
|
@@ -186,6 +434,25 @@ export const maybeHandleSystemAdminRequest = async (req, res, { pathname, url })
|
|
|
186
434
|
}
|
|
187
435
|
|
|
188
436
|
if (hasPathPrefix(pathname, SYSTEM_ADMIN_API_BASE_PATH) || hasPathPrefix(pathname, LEGACY_SYSTEM_ADMIN_API_BASE_PATH)) {
|
|
437
|
+
const multiSessionPath = mapMultiSessionApiPath(pathname);
|
|
438
|
+
if (multiSessionPath !== null) {
|
|
439
|
+
try {
|
|
440
|
+
return await handleMultiSessionOpsRequest(req, res, {
|
|
441
|
+
pathname: multiSessionPath,
|
|
442
|
+
url,
|
|
443
|
+
});
|
|
444
|
+
} catch (error) {
|
|
445
|
+
logger.error('Falha ao processar endpoint operacional multi-sessao.', {
|
|
446
|
+
action: 'system_admin_multi_session_endpoint_failed',
|
|
447
|
+
method: req.method,
|
|
448
|
+
path: pathname,
|
|
449
|
+
error: error?.message,
|
|
450
|
+
});
|
|
451
|
+
sendJson(req, res, 500, { error: 'Falha interna ao processar operacao multi-sessao.' });
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
189
456
|
const legacyPathname = mapAdminApiPathToLegacy(pathname);
|
|
190
457
|
if (!legacyPathname) return false;
|
|
191
458
|
|
|
@@ -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
|
|
|
3
4
|
export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStickerPacksForCatalog, logger, sendJson, toSiteAbsoluteUrl, isPackPubliclyVisible, buildPackWebUrl, config }) => {
|
|
@@ -52,7 +53,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
52
53
|
const buildStickersReactBundleUrl = () => appendAssetVersionQuery('/assets/js/stickers-react.bundle.js');
|
|
53
54
|
|
|
54
55
|
const buildCatalogDiscoveryLinksHtml = async () => {
|
|
55
|
-
if (SEO_DISCOVERY_CACHE.expiresAt >
|
|
56
|
+
if (SEO_DISCOVERY_CACHE.expiresAt > __timeNowMs() && SEO_DISCOVERY_CACHE.html) {
|
|
56
57
|
return SEO_DISCOVERY_CACHE.html;
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -67,7 +68,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
67
68
|
const links = (Array.isArray(packs) ? packs : []).filter((pack) => pack?.pack_key && isPackPubliclyVisible(pack)).slice(0, seoDiscoveryLinkLimit);
|
|
68
69
|
|
|
69
70
|
if (!links.length) {
|
|
70
|
-
SEO_DISCOVERY_CACHE.expiresAt =
|
|
71
|
+
SEO_DISCOVERY_CACHE.expiresAt = __timeNowMs() + seoDiscoveryCacheSeconds * 1000;
|
|
71
72
|
SEO_DISCOVERY_CACHE.html = '';
|
|
72
73
|
return '';
|
|
73
74
|
}
|
|
@@ -91,7 +92,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
91
92
|
</section>
|
|
92
93
|
</noscript>`;
|
|
93
94
|
|
|
94
|
-
SEO_DISCOVERY_CACHE.expiresAt =
|
|
95
|
+
SEO_DISCOVERY_CACHE.expiresAt = __timeNowMs() + seoDiscoveryCacheSeconds * 1000;
|
|
95
96
|
SEO_DISCOVERY_CACHE.html = html;
|
|
96
97
|
return html;
|
|
97
98
|
} catch (error) {
|
|
@@ -116,7 +117,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
116
117
|
__INITIAL_PACK_KEY__: escapeHtmlAttribute(initialPackKey || ''),
|
|
117
118
|
__CATALOG_STYLES_PATH__: escapeHtmlAttribute(buildCatalogStylesUrl()),
|
|
118
119
|
__CATALOG_SCRIPT_PATH__: escapeHtmlAttribute(buildCatalogScriptUrl()),
|
|
119
|
-
__CURRENT_YEAR__: String(
|
|
120
|
+
__CURRENT_YEAR__: String(__timeNow().getFullYear()),
|
|
120
121
|
};
|
|
121
122
|
|
|
122
123
|
let html = template;
|
|
@@ -150,7 +151,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
150
151
|
const coverUrl = toSiteAbsoluteUrl(packSummary?.cover_url || fallbackCoverUrl);
|
|
151
152
|
const publisher = truncateText(packSummary?.publisher || 'Criador OmniZap', 80);
|
|
152
153
|
const stickerCount = Math.max(0, Number(packSummary?.sticker_count || 0));
|
|
153
|
-
const updatedAt = packSummary?.updated_at || packSummary?.created_at ||
|
|
154
|
+
const updatedAt = packSummary?.updated_at || packSummary?.created_at || __timeNowIso();
|
|
154
155
|
const schemaJson = JSON.stringify(
|
|
155
156
|
{
|
|
156
157
|
'@context': 'https://schema.org',
|
|
@@ -243,7 +244,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
243
244
|
|
|
244
245
|
<meta property="og:type" content="website" />
|
|
245
246
|
<meta property="og:locale" content="pt_BR" />
|
|
246
|
-
<meta property="og:site_name" content="
|
|
247
|
+
<meta property="og:site_name" content="Omnizap" />
|
|
247
248
|
<meta property="og:title" content="${escapeHtmlAttribute(packName)}" />
|
|
248
249
|
<meta property="og:description" content="${escapeHtmlAttribute(packDescription)}" />
|
|
249
250
|
<meta property="og:url" content="${escapeHtmlAttribute(canonicalUrl)}" />
|
|
@@ -337,7 +338,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
337
338
|
__STICKER_LOGIN_WEB_PATH__: escapeHtmlAttribute(stickerLoginWebPath),
|
|
338
339
|
__STICKER_API_BASE_PATH__: escapeHtmlAttribute(stickerApiBasePath),
|
|
339
340
|
__PACK_COMMAND_PREFIX__: escapeHtmlAttribute(packCommandPrefix),
|
|
340
|
-
__CURRENT_YEAR__: String(
|
|
341
|
+
__CURRENT_YEAR__: String(__timeNow().getFullYear()),
|
|
341
342
|
};
|
|
342
343
|
|
|
343
344
|
let html = template;
|
|
@@ -348,7 +349,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
348
349
|
};
|
|
349
350
|
|
|
350
351
|
const buildSitemapXml = async () => {
|
|
351
|
-
if (SITEMAP_CACHE.expiresAt >
|
|
352
|
+
if (SITEMAP_CACHE.expiresAt > __timeNowMs() && SITEMAP_CACHE.xml) {
|
|
352
353
|
return SITEMAP_CACHE.xml;
|
|
353
354
|
}
|
|
354
355
|
|
|
@@ -441,7 +442,7 @@ export const createStickerCatalogSeoContext = ({ executeQuery, tables, listStick
|
|
|
441
442
|
.join('\n');
|
|
442
443
|
|
|
443
444
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${xmlItems}\n</urlset>\n`;
|
|
444
|
-
SITEMAP_CACHE.expiresAt =
|
|
445
|
+
SITEMAP_CACHE.expiresAt = __timeNowMs() + sitemapCacheSeconds * 1000;
|
|
445
446
|
SITEMAP_CACHE.xml = xml;
|
|
446
447
|
return xml;
|
|
447
448
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { withTimeout } from '../../http/httpRequestUtils.js';
|
|
2
3
|
|
|
3
4
|
export const createStickerCatalogNonCatalogHandlers = ({ sendJson, sendText, logger, getSystemSummaryCached, systemSummaryCache, systemSummaryCacheSeconds, getReadmeSummaryCached, readmeSummaryCache, readmeSummaryCacheSeconds, getGlobalRankingSummaryCached, globalRankRefreshSeconds, globalRankCache, sanitizeRankingPayloadByBot, getActiveSocket, resolveBotUserCandidates, getMarketplaceGlobalStatsCached, marketplaceGlobalStatsCacheSeconds, marketplaceGlobalStatsCache, githubRepoInfo, githubProjectCacheSeconds, fetchGitHubProjectSummary, buildSupportInfo, buildBotContactInfo, getMarketplaceStatsCached, resolveGoogleWebSessionFromRequest, mapGoogleSessionResponseData, isAuthenticatedGoogleSession, stickerWebGoogleClientId, homeBootstrapExposeContact, trackWebVisitMetric, resolveVisitPathFromReferrer, normalizeCatalogVisibility }) => {
|
|
@@ -18,7 +19,7 @@ export const createStickerCatalogNonCatalogHandlers = ({ sendJson, sendText, log
|
|
|
18
19
|
total_users: totalUsers,
|
|
19
20
|
total_messages: totalMessages,
|
|
20
21
|
total_commands: totalCommands,
|
|
21
|
-
updated_at:
|
|
22
|
+
updated_at: __timeNowIso(),
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
if (systemLatencyMs !== null) {
|
|
@@ -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 { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
@@ -66,6 +67,7 @@ export const stripWebpExtension = (value) =>
|
|
|
66
67
|
.replace(/\.webp$/i, '');
|
|
67
68
|
|
|
68
69
|
const clampInt = (value, fallback, min, max) => {
|
|
70
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
69
71
|
const parsed = Number(value);
|
|
70
72
|
if (!Number.isFinite(parsed)) return fallback;
|
|
71
73
|
return Math.max(min, Math.min(max, Math.floor(parsed)));
|
|
@@ -218,7 +220,7 @@ const getCacheBucket = (cacheMap, key) => {
|
|
|
218
220
|
|
|
219
221
|
const getCachedSnapshot = async ({ cacheMap, key, ttlSeconds, staleWhileRefresh = true, staleOnError = true, load }) => {
|
|
220
222
|
const bucket = getCacheBucket(cacheMap, key);
|
|
221
|
-
const now =
|
|
223
|
+
const now = __timeNowMs();
|
|
222
224
|
const hasValue = bucket.value !== null;
|
|
223
225
|
const hasFreshValue = hasValue && now < bucket.expiresAt;
|
|
224
226
|
|
|
@@ -231,7 +233,7 @@ const getCachedSnapshot = async ({ cacheMap, key, ttlSeconds, staleWhileRefresh
|
|
|
231
233
|
.then(load)
|
|
232
234
|
.then((value) => {
|
|
233
235
|
bucket.value = value;
|
|
234
|
-
bucket.expiresAt =
|
|
236
|
+
bucket.expiresAt = __timeNowMs() + ttlSeconds * 1000;
|
|
235
237
|
return value;
|
|
236
238
|
})
|
|
237
239
|
.finally(() => {
|
|
@@ -598,10 +600,10 @@ const buildPackPublishStateData = async (pack, { includeUploads = true, connecti
|
|
|
598
600
|
|
|
599
601
|
const maybeCleanupStaleDraftPacks = async () => {
|
|
600
602
|
if (staleDraftCleanupState.running) return;
|
|
601
|
-
if (
|
|
603
|
+
if (__timeNowMs() - staleDraftCleanupState.lastRunAt < WEB_DRAFT_CLEANUP_RUN_INTERVAL_MS) return;
|
|
602
604
|
|
|
603
605
|
staleDraftCleanupState.running = true;
|
|
604
|
-
staleDraftCleanupState.lastRunAt =
|
|
606
|
+
staleDraftCleanupState.lastRunAt = __timeNowMs();
|
|
605
607
|
|
|
606
608
|
try {
|
|
607
609
|
const rows = await executeQuery(
|
|
@@ -708,7 +710,7 @@ const saveWebPackEditToken = ({ packId, ownerJid }) => {
|
|
|
708
710
|
webPackEditTokenMap.set(token, {
|
|
709
711
|
packId,
|
|
710
712
|
ownerJid,
|
|
711
|
-
expiresAt:
|
|
713
|
+
expiresAt: __timeNowMs() + PACK_WEB_EDIT_TOKEN_TTL_MS,
|
|
712
714
|
});
|
|
713
715
|
return token;
|
|
714
716
|
};
|
|
@@ -718,7 +720,7 @@ const resolveWebPackEditToken = (token) => {
|
|
|
718
720
|
if (!normalized) return null;
|
|
719
721
|
const entry = webPackEditTokenMap.get(normalized);
|
|
720
722
|
if (!entry) return null;
|
|
721
|
-
if (entry.expiresAt <=
|
|
723
|
+
if (entry.expiresAt <= __timeNowMs()) {
|
|
722
724
|
webPackEditTokenMap.delete(normalized);
|
|
723
725
|
return null;
|
|
724
726
|
}
|
|
@@ -786,7 +788,7 @@ const isPreviewVariantRequested = (url) => {
|
|
|
786
788
|
const getStickerPreviewFromCache = (cacheKey) => {
|
|
787
789
|
const entry = STICKER_PREVIEW_CACHE.get(cacheKey);
|
|
788
790
|
if (!entry) return null;
|
|
789
|
-
if (entry.expiresAt <=
|
|
791
|
+
if (entry.expiresAt <= __timeNowMs()) {
|
|
790
792
|
STICKER_PREVIEW_CACHE.delete(cacheKey);
|
|
791
793
|
return null;
|
|
792
794
|
}
|
|
@@ -806,7 +808,7 @@ const saveStickerPreviewToCache = (cacheKey, buffer) => {
|
|
|
806
808
|
}
|
|
807
809
|
STICKER_PREVIEW_CACHE.set(cacheKey, {
|
|
808
810
|
buffer,
|
|
809
|
-
expiresAt:
|
|
811
|
+
expiresAt: __timeNowMs() + STICKER_PREVIEW_CACHE_TTL_MS,
|
|
810
812
|
});
|
|
811
813
|
};
|
|
812
814
|
|
|
@@ -963,8 +965,6 @@ const convertUploadMediaToWebp = async ({ ownerJid, buffer, mimetype }) => {
|
|
|
963
965
|
const PACK_TAG_MARKER_REGEX = /\[pack-tags:([^\]]+)\]/i;
|
|
964
966
|
const AUTO_PACK_MARKER_REGEX = /\[(?:auto-theme|auto-tag):[^\]]+\]/gi;
|
|
965
967
|
const AUTO_PACK_MARKER_TEST_REGEX = /\[(?:auto-theme|auto-tag):[^\]]+\]/i;
|
|
966
|
-
const AUTO_PACK_COLLECTOR_MARKER = '[auto-pack:collector]';
|
|
967
|
-
const AUTO_PACK_COLLECTOR_LEGACY_TEXT = 'coleção automática de figurinhas criadas pelo usuário.';
|
|
968
968
|
const AUTO_PACK_DESCRIPTION_PREFIX_REGEX = /^curadoria automática por tema\.\s*tema:\s*[^.]+\.?\s*(?:score\s*=\s*-?\d+(?:\.\d+)?\.?\s*)?/i;
|
|
969
969
|
const AUTO_PACK_SCORE_FRAGMENT_REGEX = /\bscore\s*=\s*-?\d+(?:\.\d+)?\.?/gi;
|
|
970
970
|
const normalizePackTag = (value) =>
|
|
@@ -1023,29 +1023,10 @@ const parsePackDescriptionMetadata = (description) => {
|
|
|
1023
1023
|
};
|
|
1024
1024
|
};
|
|
1025
1025
|
|
|
1026
|
-
const isCollectorAutoPack = (pack) => {
|
|
1027
|
-
if (!pack || typeof pack !== 'object') return false;
|
|
1028
|
-
const description = String(pack.description || '').toLowerCase();
|
|
1029
|
-
return description.includes(AUTO_PACK_COLLECTOR_MARKER) || description.includes(AUTO_PACK_COLLECTOR_LEGACY_TEXT);
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
const isThemeCurationAutoPack = (pack) => {
|
|
1033
|
-
if (!pack || typeof pack !== 'object') return false;
|
|
1034
|
-
const name = String(pack.name || '').trim();
|
|
1035
|
-
if (/^\[auto\]/i.test(name)) return true;
|
|
1036
|
-
|
|
1037
|
-
const description = String(pack.description || '').toLowerCase();
|
|
1038
|
-
if (description.includes('[auto-theme:') || description.includes('[auto-tag:')) return true;
|
|
1039
|
-
|
|
1040
|
-
return Boolean(String(pack.pack_theme_key || '').trim());
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
1026
|
const shouldHidePackFromMyProfileDefault = (pack, { includeAutoPacks = false } = {}) => {
|
|
1044
1027
|
if (!pack || typeof pack !== 'object') return false;
|
|
1045
1028
|
if (includeAutoPacks) return false;
|
|
1046
|
-
|
|
1047
|
-
if (isThemeCurationAutoPack(pack)) return true;
|
|
1048
|
-
return pack.is_auto_pack === true || Number(pack.is_auto_pack || 0) === 1;
|
|
1029
|
+
return false;
|
|
1049
1030
|
};
|
|
1050
1031
|
|
|
1051
1032
|
const buildPackDescriptionWithTags = (description, tags = []) => {
|
|
@@ -1719,7 +1700,7 @@ const buildMarketplaceStatsSnapshot = async (visibility) => {
|
|
|
1719
1700
|
const getMarketplaceStatsCached = async (visibility) => {
|
|
1720
1701
|
const normalizedVisibility = normalizeCatalogVisibility(visibility);
|
|
1721
1702
|
const bucket = getHomeMarketplaceStatsCacheBucket(normalizedVisibility);
|
|
1722
|
-
const now =
|
|
1703
|
+
const now = __timeNowMs();
|
|
1723
1704
|
const hasValue = Boolean(bucket.value);
|
|
1724
1705
|
|
|
1725
1706
|
if (hasValue && now < bucket.expiresAt) {
|
|
@@ -1730,9 +1711,15 @@ const getMarketplaceStatsCached = async (visibility) => {
|
|
|
1730
1711
|
bucket.pending = withTimeout(buildMarketplaceStatsSnapshot(normalizedVisibility), 5000)
|
|
1731
1712
|
.then((data) => {
|
|
1732
1713
|
bucket.value = data;
|
|
1733
|
-
bucket.expiresAt =
|
|
1714
|
+
bucket.expiresAt = __timeNowMs() + HOME_MARKETPLACE_STATS_CACHE_SECONDS * 1000;
|
|
1734
1715
|
return data;
|
|
1735
1716
|
})
|
|
1717
|
+
.catch((error) => {
|
|
1718
|
+
if (hasValue && bucket.value) {
|
|
1719
|
+
return bucket.value;
|
|
1720
|
+
}
|
|
1721
|
+
throw error;
|
|
1722
|
+
})
|
|
1736
1723
|
.finally(() => {
|
|
1737
1724
|
bucket.pending = null;
|
|
1738
1725
|
});
|
|
@@ -2485,7 +2472,7 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
2485
2472
|
deleted: true,
|
|
2486
2473
|
pack_key: result?.deletedPack?.pack_key || normalizedPackKey,
|
|
2487
2474
|
id: result?.deletedPack?.id || context.pack?.id || null,
|
|
2488
|
-
deleted_at: toIsoOrNull(result?.deletedPack?.deleted_at ||
|
|
2475
|
+
deleted_at: toIsoOrNull(result?.deletedPack?.deleted_at || __timeNow()),
|
|
2489
2476
|
removed_sticker_count: Number(result?.removedCount || 0),
|
|
2490
2477
|
});
|
|
2491
2478
|
} catch (error) {
|
|
@@ -3218,7 +3205,7 @@ const handleCreatePackRequest = async (req, res) => {
|
|
|
3218
3205
|
});
|
|
3219
3206
|
const manualTags = mergeUniqueTags(Array.isArray(payload?.tags) ? payload.tags : []).slice(0, 8);
|
|
3220
3207
|
const persistedDescription = buildPackDescriptionWithTags(description, manualTags);
|
|
3221
|
-
const visibility = String(payload?.visibility || '
|
|
3208
|
+
const visibility = String(payload?.visibility || 'private')
|
|
3222
3209
|
.trim()
|
|
3223
3210
|
.toLowerCase();
|
|
3224
3211
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
@@ -3468,7 +3455,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
3468
3455
|
source_mimetype: decoded.mimetype || 'image/webp',
|
|
3469
3456
|
upload_status: 'processing',
|
|
3470
3457
|
attempt_count: 1,
|
|
3471
|
-
last_attempt_at:
|
|
3458
|
+
last_attempt_at: __timeNow(),
|
|
3472
3459
|
},
|
|
3473
3460
|
connection,
|
|
3474
3461
|
);
|
|
@@ -3481,7 +3468,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
3481
3468
|
error_code: null,
|
|
3482
3469
|
error_message: null,
|
|
3483
3470
|
attempt_count: Math.max(1, Number(existingUpload.attempt_count || 0) + 1),
|
|
3484
|
-
last_attempt_at:
|
|
3471
|
+
last_attempt_at: __timeNow(),
|
|
3485
3472
|
},
|
|
3486
3473
|
connection,
|
|
3487
3474
|
);
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import logger from '#logger';
|
|
2
2
|
import { getActiveSocket, getAdminPhone, getAdminRawValue, getJidUser, resolveAdminJid, resolveBotJid, extractUserIdInfo, resolveUserId } from '../../../app/config/index.js';
|
|
3
|
+
import { isLikelyWhatsAppPhone, normalizePhoneDigits, resolveAdminPhoneFromEnv, resolveBotPhoneFromEnv, resolveSupportPhoneFromEnv } from '../../../utils/whatsapp/contactEnv.js';
|
|
3
4
|
|
|
4
5
|
const PACK_COMMAND_PREFIX = String(process.env.COMMAND_PREFIX || '/').trim() || '/';
|
|
5
6
|
|
|
6
|
-
const normalizePhoneDigits = (value) => String(value || '').replace(/\D+/g, '');
|
|
7
|
-
|
|
8
7
|
const isPlausibleWhatsAppPhone = (value) => {
|
|
9
8
|
const digits = normalizePhoneDigits(value);
|
|
10
|
-
return digits
|
|
9
|
+
return isLikelyWhatsAppPhone(digits) ? digits : '';
|
|
11
10
|
};
|
|
12
11
|
|
|
13
12
|
const resolveActiveSocketBotJid = (activeSocket) => {
|
|
@@ -26,18 +25,12 @@ export const resolveCatalogBotPhone = () => {
|
|
|
26
25
|
const jidUser = botJid ? getJidUser(botJid) : null;
|
|
27
26
|
const fromSocket = normalizePhoneDigits(jidUser || '');
|
|
28
27
|
|
|
29
|
-
if (fromSocket
|
|
28
|
+
if (isLikelyWhatsAppPhone(fromSocket)) {
|
|
30
29
|
return fromSocket;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
for (const candidate of envCandidates) {
|
|
36
|
-
const digits = normalizePhoneDigits(candidate || '');
|
|
37
|
-
if (digits && digits.length >= 10 && digits.length <= 15) {
|
|
38
|
-
return digits;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
32
|
+
const fromEnv = resolveBotPhoneFromEnv({ fallback: '' });
|
|
33
|
+
if (fromEnv) return fromEnv;
|
|
41
34
|
|
|
42
35
|
logger.warn('Nao foi possivel resolver o numero do bot para contato.', {
|
|
43
36
|
action: 'resolve_bot_phone_failed',
|
|
@@ -75,12 +68,11 @@ const resolveSupportAdminPhone = async () => {
|
|
|
75
68
|
const adminPhone = isPlausibleWhatsAppPhone(getAdminPhone() || '');
|
|
76
69
|
if (adminPhone) return adminPhone;
|
|
77
70
|
|
|
78
|
-
const
|
|
71
|
+
const configuredAdminPhone = resolveAdminPhoneFromEnv({ fallback: '' });
|
|
72
|
+
if (configuredAdminPhone) return configuredAdminPhone;
|
|
79
73
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (digits) return digits;
|
|
83
|
-
}
|
|
74
|
+
const configuredSupportPhone = resolveSupportPhoneFromEnv({ fallback: '' });
|
|
75
|
+
if (configuredSupportPhone) return configuredSupportPhone;
|
|
84
76
|
|
|
85
77
|
return '';
|
|
86
78
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import logger from '#logger';
|
|
2
3
|
import { toIsoOrNull } from '../../http/httpRequestUtils.js';
|
|
3
4
|
|
|
@@ -33,7 +34,7 @@ const githubFetchJson = async (url) => {
|
|
|
33
34
|
|
|
34
35
|
const headers = {
|
|
35
36
|
Accept: 'application/vnd.github+json',
|
|
36
|
-
'User-Agent': 'omnizap
|
|
37
|
+
'User-Agent': 'omnizap/2.1',
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
if (GITHUB_TOKEN) {
|
|
@@ -109,7 +110,7 @@ export const fetchGitHubProjectSummary = async () => {
|
|
|
109
110
|
throw new Error('GITHUB_REPOSITORY invalido');
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
const now =
|
|
113
|
+
const now = __timeNowMs();
|
|
113
114
|
if (GITHUB_PROJECT_CACHE.value && now < GITHUB_PROJECT_CACHE.expiresAt) {
|
|
114
115
|
return GITHUB_PROJECT_CACHE.value;
|
|
115
116
|
}
|