@kaikybrofc/omnizap-system 2.2.10 → 2.3.1

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 (123) hide show
  1. package/README.md +13 -13
  2. package/app/config/adminIdentity.js +1 -3
  3. package/app/connection/socketController.js +10 -20
  4. package/app/controllers/messageController.js +7 -28
  5. package/app/modules/aiModule/catCommand.js +29 -192
  6. package/app/modules/broadcastModule/noticeCommand.js +28 -97
  7. package/app/modules/gameModule/diceCommand.js +6 -32
  8. package/app/modules/playModule/playCommand.js +57 -258
  9. package/app/modules/quoteModule/quoteCommand.js +2 -4
  10. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
  11. package/app/modules/statsModule/noMessageCommand.js +16 -84
  12. package/app/modules/statsModule/rankingCommand.js +5 -25
  13. package/app/modules/statsModule/rankingCommon.js +1 -9
  14. package/app/modules/stickerModule/convertToWebp.js +4 -27
  15. package/app/modules/stickerModule/stickerCommand.js +13 -24
  16. package/app/modules/stickerModule/stickerTextCommand.js +13 -25
  17. package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
  18. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
  19. package/app/modules/stickerPackModule/domainEvents.js +2 -11
  20. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
  21. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
  22. package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
  23. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
  24. package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
  25. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
  26. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
  27. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
  28. package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
  29. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
  30. package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
  31. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +40 -39
  32. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
  33. package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
  34. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
  35. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
  36. package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
  37. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
  38. package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
  39. package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
  40. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
  41. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
  42. package/app/modules/stickerPackModule/stickerPackService.js +50 -115
  43. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
  44. package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
  45. package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
  46. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
  47. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
  48. package/app/modules/systemMetricsModule/pingCommand.js +9 -39
  49. package/app/modules/tiktokModule/tiktokCommand.js +17 -109
  50. package/app/modules/userModule/userCommand.js +2 -88
  51. package/app/observability/metrics.js +5 -16
  52. package/app/services/captchaService.js +1 -6
  53. package/app/services/dbWriteQueue.js +3 -18
  54. package/app/services/featureFlagService.js +2 -8
  55. package/app/services/newsBroadcastService.js +0 -1
  56. package/app/services/queueUtils.js +2 -4
  57. package/app/services/whatsappLoginLinkService.js +7 -9
  58. package/app/store/premiumUserStore.js +1 -2
  59. package/app/utils/antiLink/antiLinkModule.js +3 -233
  60. package/app/utils/logger/loggerModule.js +9 -34
  61. package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
  62. package/database/index.js +1 -0
  63. package/database/init.js +1 -8
  64. package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
  65. package/docker-compose.yml +27 -27
  66. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
  67. package/docs/seo/satellite-page-template.md +2 -0
  68. package/docs/seo/satellite-pages-phase1.json +40 -177
  69. package/eslint.config.js +2 -15
  70. package/index.js +8 -36
  71. package/ml/clip_classifier/README.md +4 -6
  72. package/observability/alert-rules.yml +12 -12
  73. package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
  74. package/package.json +6 -3
  75. package/public/api-docs/index.html +220 -193
  76. package/public/bot-whatsapp-para-grupo/index.html +291 -261
  77. package/public/bot-whatsapp-sem-programar/index.html +291 -261
  78. package/public/comandos/index.html +421 -406
  79. package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
  80. package/public/como-criar-comandos-whatsapp/index.html +291 -261
  81. package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
  82. package/public/como-moderar-grupo-whatsapp/index.html +291 -261
  83. package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
  84. package/public/css/github-project-panel.css +13 -8
  85. package/public/css/stickers-admin.css +25 -9
  86. package/public/css/styles.css +23 -16
  87. package/public/index.html +1106 -993
  88. package/public/js/apps/apiDocsApp.js +17 -167
  89. package/public/js/apps/createPackApp.js +69 -332
  90. package/public/js/apps/homeApp.js +274 -101
  91. package/public/js/apps/loginApp.js +3 -12
  92. package/public/js/apps/stickersAdminApp.js +190 -181
  93. package/public/js/apps/stickersApp.js +482 -1411
  94. package/public/js/apps/userApp.js +217 -1
  95. package/public/js/catalog.js +11 -74
  96. package/public/js/github-panel/components/ErrorState.js +1 -8
  97. package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
  98. package/public/js/github-panel/components/SkeletonPanel.js +1 -11
  99. package/public/js/github-panel/components/StatCard.js +1 -7
  100. package/public/js/github-panel/vendor/react.js +1 -9
  101. package/public/js/runtime/react-runtime.js +1 -9
  102. package/public/licenca/index.html +200 -86
  103. package/public/login/index.html +315 -325
  104. package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
  105. package/public/stickers/admin/index.html +14 -19
  106. package/public/stickers/create/index.html +39 -44
  107. package/public/stickers/index.html +96 -107
  108. package/public/termos-de-uso/index.html +369 -122
  109. package/public/user/index.html +527 -350
  110. package/scripts/cache-bust.mjs +5 -24
  111. package/scripts/generate-seo-satellite-pages.mjs +10 -13
  112. package/scripts/run-prettier-all.mjs +25 -0
  113. package/scripts/sticker-catalog-loadtest.mjs +13 -11
  114. package/scripts/sticker-worker-task.mjs +1 -4
  115. package/scripts/sync-readme-snapshot.mjs +3 -2
  116. package/server/auth/googleWebAuth/googleWebAuthService.js +614 -0
  117. package/server/controllers/stickerCatalogController.js +297 -632
  118. package/server/http/httpServer.js +2 -10
  119. package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
  120. package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
  121. package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
  122. package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
  123. package/server/routes/stickerCatalog/catalogRouter.js +11 -13
@@ -1,4 +1,40 @@
1
1
  const FALLBACK_THUMB_URL = '/assets/images/brand-logo-128.webp';
2
+ const HOME_BOOTSTRAP_ENDPOINT = '/api/sticker-packs/home-bootstrap';
3
+ const SVG_NS = 'http://www.w3.org/2000/svg';
4
+ let homeBootstrapPayloadPromise = null;
5
+
6
+ const fetchHomeBootstrapPayload = async () => {
7
+ if (!homeBootstrapPayloadPromise) {
8
+ homeBootstrapPayloadPromise = fetch(HOME_BOOTSTRAP_ENDPOINT, { credentials: 'include' })
9
+ .then((response) => {
10
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
11
+ return response.json();
12
+ })
13
+ .then((payload) => payload?.data || {})
14
+ .catch((error) => {
15
+ homeBootstrapPayloadPromise = null;
16
+ throw error;
17
+ });
18
+ }
19
+ return homeBootstrapPayloadPromise;
20
+ };
21
+
22
+ const createIcon = (iconId, className = 'icon') => {
23
+ const wrapper = document.createElement('span');
24
+ wrapper.className = className;
25
+ wrapper.setAttribute('aria-hidden', 'true');
26
+
27
+ const svg = document.createElementNS(SVG_NS, 'svg');
28
+ svg.setAttribute('viewBox', '0 0 24 24');
29
+ svg.setAttribute('focusable', 'false');
30
+
31
+ const use = document.createElementNS(SVG_NS, 'use');
32
+ use.setAttribute('href', `#${iconId}`);
33
+
34
+ svg.appendChild(use);
35
+ wrapper.appendChild(svg);
36
+ return wrapper;
37
+ };
2
38
 
3
39
  const shortNum = (value) =>
4
40
  new Intl.NumberFormat('pt-BR', {
@@ -101,7 +137,21 @@ const initNavToggle = () => {
101
137
  toggle.setAttribute('aria-expanded', 'false');
102
138
  };
103
139
 
104
- const onClick = () => {
140
+ const onClick = (event) => {
141
+ if (toggle.classList.contains('nav-toggle-login')) {
142
+ if (event) event.preventDefault();
143
+ closeMenu();
144
+ const loginUrl = String(toggle.dataset.loginUrl || '/login/').trim() || '/login/';
145
+ window.location.assign(loginUrl);
146
+ return;
147
+ }
148
+ if (toggle.classList.contains('nav-toggle-user')) {
149
+ if (event) event.preventDefault();
150
+ closeMenu();
151
+ const profileUrl = String(toggle.dataset.profileUrl || '/user/').trim() || '/user/';
152
+ window.location.assign(profileUrl);
153
+ return;
154
+ }
105
155
  const isOpen = nav.classList.toggle('open');
106
156
  toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
107
157
  };
@@ -127,38 +177,153 @@ const initNavToggle = () => {
127
177
 
128
178
  const initAuthSession = () => {
129
179
  const authLink = document.getElementById('nav-auth-link');
130
- const heroLoginCta = document.getElementById('hero-login-cta');
131
- const finalLoginCta = document.getElementById('final-login-cta');
180
+ const schedulerLink = document.getElementById('nav-scheduler-link');
181
+ const navToggle = document.getElementById('nav-toggle');
132
182
  if (!authLink) return null;
133
183
 
184
+ const mobileQuery = typeof window.matchMedia === 'function' ? window.matchMedia('(max-width: 920px)') : null;
185
+ let currentSessionData = null;
186
+ let isAuthenticated = false;
187
+ const isMobileViewport = () => {
188
+ const byMedia = Boolean(mobileQuery?.matches);
189
+ const viewportWidth = Math.max(Number(window.innerWidth || 0), Number(document.documentElement?.clientWidth || 0));
190
+ return byMedia || (viewportWidth > 0 && viewportWidth <= 920);
191
+ };
192
+ const schedulerDefaultHref = String(schedulerLink?.getAttribute('href') || '#beneficios').trim() || '#beneficios';
193
+ const schedulerDefaultLabel = String(schedulerLink?.textContent || 'Benefícios').trim() || 'Benefícios';
194
+ const navToggleDefaultLabel = String(navToggle?.textContent || '☰').trim() || '☰';
195
+
134
196
  const clearChildren = (node) => {
135
197
  while (node.firstChild) node.removeChild(node.firstChild);
136
198
  };
137
199
 
200
+ const setSchedulerDefaultState = () => {
201
+ if (!schedulerLink) return;
202
+ schedulerLink.classList.remove('nav-user-chip');
203
+ schedulerLink.href = schedulerDefaultHref;
204
+ schedulerLink.removeAttribute('title');
205
+ schedulerLink.removeAttribute('aria-label');
206
+ clearChildren(schedulerLink);
207
+ schedulerLink.append(document.createTextNode(schedulerDefaultLabel));
208
+ };
209
+
210
+ const setToggleDefaultState = () => {
211
+ if (!navToggle) return;
212
+ navToggle.classList.remove('nav-toggle-user', 'nav-toggle-login');
213
+ delete navToggle.dataset.profileUrl;
214
+ delete navToggle.dataset.loginUrl;
215
+ navToggle.setAttribute('aria-label', 'Abrir menu');
216
+ navToggle.setAttribute('aria-controls', 'main-nav');
217
+ navToggle.setAttribute('aria-expanded', 'false');
218
+ clearChildren(navToggle);
219
+ navToggle.append(document.createTextNode(navToggleDefaultLabel));
220
+ };
221
+
222
+ const setToggleLoginState = () => {
223
+ if (!navToggle) return;
224
+ navToggle.classList.remove('nav-toggle-user');
225
+ navToggle.classList.add('nav-toggle-login');
226
+ delete navToggle.dataset.profileUrl;
227
+ navToggle.dataset.loginUrl = '/login/';
228
+ navToggle.setAttribute('aria-label', 'Entrar');
229
+ navToggle.removeAttribute('aria-controls');
230
+ navToggle.removeAttribute('aria-expanded');
231
+ clearChildren(navToggle);
232
+ navToggle.append(document.createTextNode('Entrar'));
233
+ };
234
+
235
+ const setSchedulerAsUserBubble = (sessionData) => {
236
+ if (!schedulerLink) return;
237
+
238
+ const profile = sessionData?.user || {};
239
+ const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
240
+ const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
241
+
242
+ schedulerLink.classList.add('nav-user-chip');
243
+ schedulerLink.href = '/user/';
244
+ schedulerLink.title = `${resolvedName} (sessão ativa)`;
245
+ schedulerLink.setAttribute('aria-label', `Sessão ativa de ${resolvedName}`);
246
+ clearChildren(schedulerLink);
247
+
248
+ const avatarBubble = document.createElement('span');
249
+ avatarBubble.className = 'nav-user-avatar-bubble';
250
+
251
+ const photo = document.createElement('img');
252
+ photo.className = 'nav-user-photo';
253
+ photo.src = resolvedPhoto;
254
+ photo.alt = `Foto de ${resolvedName}`;
255
+ photo.loading = 'lazy';
256
+ photo.decoding = 'async';
257
+ photo.width = 34;
258
+ photo.height = 34;
259
+ photo.onerror = () => {
260
+ photo.src = FALLBACK_THUMB_URL;
261
+ };
262
+ avatarBubble.appendChild(photo);
263
+ schedulerLink.append(avatarBubble);
264
+ };
265
+
266
+ const setToggleAsUserBubble = (sessionData) => {
267
+ if (!navToggle) return;
268
+
269
+ const profile = sessionData?.user || {};
270
+ const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
271
+ const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
272
+
273
+ navToggle.classList.remove('nav-toggle-login');
274
+ navToggle.classList.add('nav-toggle-user');
275
+ navToggle.dataset.profileUrl = '/user/';
276
+ delete navToggle.dataset.loginUrl;
277
+ navToggle.setAttribute('aria-label', `Abrir perfil de ${resolvedName}`);
278
+ navToggle.removeAttribute('aria-controls');
279
+ navToggle.removeAttribute('aria-expanded');
280
+ clearChildren(navToggle);
281
+
282
+ const photo = document.createElement('img');
283
+ photo.className = 'nav-toggle-photo';
284
+ photo.src = resolvedPhoto;
285
+ photo.alt = `Foto de ${resolvedName}`;
286
+ photo.loading = 'lazy';
287
+ photo.decoding = 'async';
288
+ photo.width = 40;
289
+ photo.height = 40;
290
+ photo.onerror = () => {
291
+ photo.src = FALLBACK_THUMB_URL;
292
+ };
293
+ navToggle.append(photo);
294
+ };
295
+
296
+ const applyLoggedOutLayout = () => {
297
+ setSchedulerDefaultState();
298
+ if (isMobileViewport()) {
299
+ authLink.classList.add('nav-mobile-hidden');
300
+ setToggleLoginState();
301
+ return;
302
+ }
303
+ authLink.classList.remove('nav-mobile-hidden');
304
+ setToggleDefaultState();
305
+ };
306
+
138
307
  const setLoginState = () => {
308
+ currentSessionData = null;
309
+ isAuthenticated = false;
139
310
  authLink.classList.remove('nav-user-chip');
311
+ authLink.classList.remove('nav-mobile-hidden');
140
312
  authLink.href = '/login/';
141
313
  authLink.removeAttribute('title');
142
314
  authLink.removeAttribute('aria-label');
143
315
  clearChildren(authLink);
144
316
 
145
- const icon = document.createElement('i');
146
- icon.className = 'fa-solid fa-right-to-bracket';
147
- icon.setAttribute('aria-hidden', 'true');
317
+ const icon = createIcon('icon-login');
148
318
 
149
319
  authLink.append(icon, document.createTextNode('Entrar'));
150
-
151
- if (heroLoginCta) {
152
- heroLoginCta.hidden = false;
153
- heroLoginCta.removeAttribute('aria-hidden');
154
- }
155
- if (finalLoginCta) {
156
- finalLoginCta.hidden = false;
157
- finalLoginCta.removeAttribute('aria-hidden');
158
- }
320
+ applyLoggedOutLayout();
159
321
  };
160
322
 
161
- const setLoggedState = (sessionData) => {
323
+ const applyLoggedLayout = () => {
324
+ if (!currentSessionData) return;
325
+
326
+ const sessionData = currentSessionData;
162
327
  const profile = sessionData?.user || {};
163
328
  const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
164
329
  const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
@@ -184,61 +349,68 @@ const initAuthSession = () => {
184
349
  photo.src = FALLBACK_THUMB_URL;
185
350
  };
186
351
  avatarBubble.appendChild(photo);
352
+ authLink.append(avatarBubble);
187
353
 
188
- const nameBubble = document.createElement('span');
189
- nameBubble.className = 'nav-user-name-bubble';
190
-
191
- const icon = document.createElement('i');
192
- icon.className = 'fa-solid fa-user nav-user-icon';
193
- icon.setAttribute('aria-hidden', 'true');
354
+ if (isMobileViewport()) {
355
+ setSchedulerAsUserBubble(sessionData);
356
+ setToggleAsUserBubble(sessionData);
357
+ authLink.classList.add('nav-mobile-hidden');
358
+ return;
359
+ }
194
360
 
195
- const name = document.createElement('span');
196
- name.className = 'nav-user-name';
197
- name.textContent = resolvedName;
361
+ authLink.classList.remove('nav-mobile-hidden');
362
+ setSchedulerDefaultState();
363
+ setToggleDefaultState();
364
+ };
198
365
 
199
- nameBubble.append(icon, name);
200
- authLink.append(avatarBubble, nameBubble);
366
+ const setLoggedState = (sessionData) => {
367
+ currentSessionData = sessionData || null;
368
+ isAuthenticated = true;
369
+ applyLoggedLayout();
370
+ };
201
371
 
202
- if (heroLoginCta) {
203
- heroLoginCta.hidden = true;
204
- heroLoginCta.setAttribute('aria-hidden', 'true');
205
- }
206
- if (finalLoginCta) {
207
- finalLoginCta.hidden = true;
208
- finalLoginCta.setAttribute('aria-hidden', 'true');
372
+ const onViewportChange = () => {
373
+ if (isAuthenticated) {
374
+ applyLoggedLayout();
375
+ return;
209
376
  }
377
+ applyLoggedOutLayout();
210
378
  };
211
379
 
212
- return runAfterLoadIdle(() => {
213
- fetch('/api/sticker-packs/auth/google/session', { credentials: 'include' })
214
- .then((response) => {
215
- if (!response.ok) throw new Error('Sessão indisponível');
216
- return response.json();
217
- })
218
- .then((payload) => {
219
- const sessionData = payload?.data || {};
220
- if (!sessionData?.authenticated || !sessionData?.user?.sub) {
380
+ if (mobileQuery && typeof mobileQuery.addEventListener === 'function') {
381
+ mobileQuery.addEventListener('change', onViewportChange);
382
+ } else if (mobileQuery && typeof mobileQuery.addListener === 'function') {
383
+ mobileQuery.addListener(onViewportChange);
384
+ }
385
+ window.addEventListener('resize', onViewportChange);
386
+
387
+ const stopBootstrap = runAfterLoadIdle(
388
+ () => {
389
+ fetchHomeBootstrapPayload()
390
+ .then((bootstrapData) => {
391
+ const sessionData = bootstrapData?.session || {};
392
+ if (!sessionData?.authenticated || !sessionData?.user?.sub) {
393
+ setLoginState();
394
+ return;
395
+ }
396
+ setLoggedState(sessionData);
397
+ })
398
+ .catch(() => {
221
399
  setLoginState();
222
- return;
223
- }
224
- setLoggedState(sessionData);
225
- })
226
- .catch(() => {
227
- setLoginState();
228
- });
229
- }, { delayMs: 160, timeoutMs: 1200 });
230
- };
400
+ });
401
+ },
402
+ { delayMs: 520, timeoutMs: 1200 },
403
+ );
231
404
 
232
- const resolveSupportUrl = async () => {
233
- try {
234
- const response = await fetch('/api/sticker-packs/support');
235
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
236
- const payload = await response.json();
237
- const url = String(payload?.data?.url || '').trim();
238
- return url || '';
239
- } catch {
240
- return '';
241
- }
405
+ return () => {
406
+ stopBootstrap();
407
+ window.removeEventListener('resize', onViewportChange);
408
+ if (mobileQuery && typeof mobileQuery.removeEventListener === 'function') {
409
+ mobileQuery.removeEventListener('change', onViewportChange);
410
+ } else if (mobileQuery && typeof mobileQuery.removeListener === 'function') {
411
+ mobileQuery.removeListener(onViewportChange);
412
+ }
413
+ };
242
414
  };
243
415
 
244
416
  const initAddBotCtas = () => {
@@ -263,18 +435,22 @@ const initAddBotCtas = () => {
263
435
  return true;
264
436
  };
265
437
 
266
- return runAfterLoadIdle(() => {
267
- resolveSupportUrl()
268
- .then((url) => {
269
- const applied = applyLink(url);
270
- if (!applied && floatButton) {
271
- floatButton.hidden = true;
272
- }
273
- })
274
- .catch(() => {
275
- if (floatButton) floatButton.hidden = true;
276
- });
277
- }, { delayMs: 120, timeoutMs: 1400 });
438
+ return runAfterLoadIdle(
439
+ () => {
440
+ fetchHomeBootstrapPayload()
441
+ .then((bootstrapData) => {
442
+ const url = String(bootstrapData?.bot_contact?.urls?.menu || '').trim();
443
+ const applied = applyLink(url);
444
+ if (!applied && floatButton) {
445
+ floatButton.hidden = true;
446
+ }
447
+ })
448
+ .catch(() => {
449
+ if (floatButton) floatButton.hidden = true;
450
+ });
451
+ },
452
+ { delayMs: 480, timeoutMs: 1400 },
453
+ );
278
454
  };
279
455
 
280
456
  const initSocialProof = () => {
@@ -293,7 +469,9 @@ const initSocialProof = () => {
293
469
  };
294
470
 
295
471
  const normalizeStatus = (value) => {
296
- const normalized = String(value || '').trim().toLowerCase();
472
+ const normalized = String(value || '')
473
+ .trim()
474
+ .toLowerCase();
297
475
  if (!normalized) return 'pronto';
298
476
  if (['online', 'ok', 'healthy'].includes(normalized)) return 'online';
299
477
  if (['connecting', 'opening', 'reconnecting'].includes(normalized)) return 'conectando';
@@ -301,32 +479,27 @@ const initSocialProof = () => {
301
479
  return 'pronto';
302
480
  };
303
481
 
304
- return runAfterLoadIdle(() => {
305
- Promise.all([
306
- fetch('/api/sticker-packs/stats'),
307
- fetch('/api/sticker-packs/system-summary'),
308
- ])
309
- .then(async ([statsRes, summaryRes]) => {
310
- if (!statsRes.ok || !summaryRes.ok) throw new Error('Falha ao carregar prova social');
311
-
312
- const statsPayload = await statsRes.json();
313
- const summaryPayload = await summaryRes.json();
314
-
315
- const stats = statsPayload?.data || {};
316
- const summary = summaryPayload?.data || {};
317
-
318
- animateCountUp(packsEl, Number(stats.packs_total || 0));
319
- animateCountUp(stickersEl, Number(stats.stickers_total || 0));
320
- animateCountUp(groupsEl, Number(summary?.platform?.total_groups || 0));
321
-
322
- if (statusEl) {
323
- statusEl.textContent = `bot ${normalizeStatus(summary?.system_status || summary?.bot?.connection_status)}`;
324
- }
325
- })
326
- .catch(() => {
327
- setFallback();
328
- });
329
- }, { delayMs: 260, timeoutMs: 1500 });
482
+ return runAfterLoadIdle(
483
+ () => {
484
+ fetchHomeBootstrapPayload()
485
+ .then((bootstrapData) => {
486
+ const stats = bootstrapData?.stats || {};
487
+ const summary = bootstrapData?.system_summary || {};
488
+
489
+ animateCountUp(packsEl, Number(stats.packs_total || 0));
490
+ animateCountUp(stickersEl, Number(stats.stickers_total || 0));
491
+ animateCountUp(groupsEl, Number(summary?.platform?.total_groups || 0));
492
+
493
+ if (statusEl) {
494
+ statusEl.textContent = `bot ${normalizeStatus(summary?.system_status || summary?.bot?.connection_status)}`;
495
+ }
496
+ })
497
+ .catch(() => {
498
+ setFallback();
499
+ });
500
+ },
501
+ { delayMs: 620, timeoutMs: 1500 },
502
+ );
330
503
  };
331
504
 
332
505
  const registerCleanup = (cleanups, cleanup) => {
@@ -146,18 +146,12 @@ if (root) {
146
146
 
147
147
  const renderHint = () => {
148
148
  if (!state.hint.hasPayload) {
149
- setText(
150
- ui.hint,
151
- 'Voce abriu esta pagina direto. Por seguranca, gere seu link no WhatsApp clicando no botao abaixo e enviando "iniciar".',
152
- );
149
+ setText(ui.hint, 'Voce abriu esta pagina direto. Por seguranca, gere seu link no WhatsApp clicando no botao abaixo e enviando "iniciar".');
153
150
  return;
154
151
  }
155
152
 
156
153
  if (!state.hint.phone) {
157
- setText(
158
- ui.hint,
159
- 'Este link nao trouxe um numero de WhatsApp valido. Ele pode ter sido alterado ou expirado. Gere um novo link enviando "iniciar".',
160
- );
154
+ setText(ui.hint, 'Este link nao trouxe um numero de WhatsApp valido. Ele pode ter sido alterado ou expirado. Gere um novo link enviando "iniciar".');
161
155
  return;
162
156
  }
163
157
 
@@ -193,10 +187,7 @@ if (root) {
193
187
  }
194
188
 
195
189
  if (state.hint.phone) {
196
- setText(
197
- ui.summaryOwner,
198
- `Numero detectado: +${formatPhone(state.hint.phone)}. Clique no botao do Google para concluir o vinculo.`,
199
- );
190
+ setText(ui.summaryOwner, `Numero detectado: +${formatPhone(state.hint.phone)}. Clique no botao do Google para concluir o vinculo.`);
200
191
  renderWhatsAppCta();
201
192
  return;
202
193
  }