@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
@@ -92,7 +92,10 @@ const includesToken = (parts, token) => {
92
92
 
93
93
  const isAdminAuthenticated = () => Boolean(state.adminStatus?.session?.authenticated);
94
94
  const canUnlockAdmin = () => Boolean(state.adminStatus?.eligible_google_login);
95
- const getAdminRole = () => String(state.adminStatus?.session?.role || '').trim().toLowerCase();
95
+ const getAdminRole = () =>
96
+ String(state.adminStatus?.session?.role || '')
97
+ .trim()
98
+ .toLowerCase();
96
99
  const canManageModerators = () => Boolean(state.adminStatus?.session?.capabilities?.can_manage_moderators || getAdminRole() === 'owner');
97
100
 
98
101
  function setToast(type, message, timeoutMs = 3200) {
@@ -250,7 +253,9 @@ function renderPagination(key, page) {
250
253
  }
251
254
 
252
255
  function toneClassForStatus(status) {
253
- const normalized = String(status || '').trim().toLowerCase();
256
+ const normalized = String(status || '')
257
+ .trim()
258
+ .toLowerCase();
254
259
  if (['published', 'ready', 'done', 'online', 'active'].includes(normalized)) return 'status-success';
255
260
  if (['failed', 'error', 'deleted'].includes(normalized)) return 'status-danger';
256
261
  if (['uploading', 'processing', 'pending', 'draft'].includes(normalized)) return 'status-warning';
@@ -373,9 +378,7 @@ function renderUsersTab() {
373
378
  const { activeSessions, users } = getOverviewData();
374
379
  const token = normalizeToken(state.usersQuery);
375
380
 
376
- const filteredSessions = activeSessions.filter((row) =>
377
- includesToken([row?.name, row?.email, row?.owner_jid, row?.google_sub], token),
378
- );
381
+ const filteredSessions = activeSessions.filter((row) => includesToken([row?.name, row?.email, row?.owner_jid, row?.google_sub], token));
379
382
  const filteredUsers = users.filter((row) => includesToken([row?.name, row?.email, row?.owner_jid, row?.google_sub], token));
380
383
 
381
384
  const sessionsPage = paginate(filteredSessions, 'sessions');
@@ -383,13 +386,7 @@ function renderUsersTab() {
383
386
 
384
387
  const sessionRowsDesktop = sessionsPage.items
385
388
  .map((row) => {
386
- const menu = renderMenuWrap(
387
- 'session',
388
- row?.session_token || row?.google_sub || row?.email || Math.random(),
389
- `<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
390
- row?.google_sub || '',
391
- )}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`,
392
- );
389
+ const menu = renderMenuWrap('session', row?.session_token || row?.google_sub || row?.email || Math.random(), `<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`);
393
390
  return `
394
391
  <tr>
395
392
  <td>
@@ -406,13 +403,7 @@ function renderUsersTab() {
406
403
 
407
404
  const usersRowsDesktop = usersPage.items
408
405
  .map((row) => {
409
- const menu = renderMenuWrap(
410
- 'user',
411
- row?.google_sub || row?.email || row?.owner_jid || Math.random(),
412
- `<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
413
- row?.google_sub || '',
414
- )}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`,
415
- );
406
+ const menu = renderMenuWrap('user', row?.google_sub || row?.email || row?.owner_jid || Math.random(), `<button class="row-menu-item danger" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir usuario</button>`);
416
407
  return `
417
408
  <tr>
418
409
  <td>
@@ -437,9 +428,7 @@ function renderUsersTab() {
437
428
  <p class="row-meta break-all">${escapeHtml(row?.owner_jid || '')}</p>
438
429
  <div class="mobile-card-foot">
439
430
  <span class="muted">${escapeHtml(fmtDate(row?.last_seen_at || row?.created_at))}</span>
440
- <button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
441
- row?.google_sub || '',
442
- )}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
431
+ <button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
443
432
  </div>
444
433
  </article>
445
434
  `,
@@ -456,9 +445,7 @@ function renderUsersTab() {
456
445
  <p class="row-meta break-all mono">${escapeHtml(row?.google_sub || '')}</p>
457
446
  <div class="mobile-card-foot">
458
447
  <span class="muted">${escapeHtml(fmtDate(row?.last_login_at || row?.last_seen_at || row?.updated_at))}</span>
459
- <button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(
460
- row?.google_sub || '',
461
- )}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
448
+ <button class="danger-btn" data-action="ban-user" data-email="${escapeHtml(row?.email || '')}" data-sub="${escapeHtml(row?.google_sub || '')}" data-owner="${escapeHtml(row?.owner_jid || '')}">Banir</button>
462
449
  </div>
463
450
  </article>
464
451
  `,
@@ -529,11 +516,7 @@ function renderPacksTab() {
529
516
 
530
517
  const packRowsDesktop = packsPage.items
531
518
  .map((pack) => {
532
- const statusBadges = [
533
- renderStatusBadge(pack?.visibility || 'n/a'),
534
- renderStatusBadge(pack?.status || 'n/a'),
535
- renderStatusBadge(pack?.pack_status || 'ready'),
536
- ].join('');
519
+ const statusBadges = [renderStatusBadge(pack?.visibility || 'n/a'), renderStatusBadge(pack?.status || 'n/a'), renderStatusBadge(pack?.pack_status || 'ready')].join('');
537
520
 
538
521
  const menu = renderMenuWrap(
539
522
  'pack',
@@ -634,8 +617,9 @@ function renderPacksTab() {
634
617
  <div class="panel-head slim">
635
618
  <h4 class="panel-title">Pack selecionado</h4>
636
619
  </div>
637
- ${selectedPack
638
- ? `
620
+ ${
621
+ selectedPack
622
+ ? `
639
623
  <div class="selected-pack-head">
640
624
  <div>
641
625
  <p class="row-title break-words">${escapeHtml(selectedPack?.name || selectedPack?.pack_key || 'Pack')}</p>
@@ -655,7 +639,8 @@ function renderPacksTab() {
655
639
  </div>
656
640
  <div class="detail-list">${detailItems || '<div class="empty-box">Pack sem stickers.</div>'}</div>
657
641
  `
658
- : '<div class="empty-box">Selecione um pack na tabela para visualizar e moderar stickers.</div>'}
642
+ : '<div class="empty-box">Selecione um pack na tabela para visualizar e moderar stickers.</div>'
643
+ }
659
644
  </section>
660
645
  </section>
661
646
  `;
@@ -670,13 +655,7 @@ function renderLogsTab() {
670
655
  const rowsDesktop = page.items
671
656
  .map((ban) => {
672
657
  const revoked = Boolean(ban?.revoked_at);
673
- const menu = revoked
674
- ? ''
675
- : renderMenuWrap(
676
- 'ban',
677
- ban?.id || Math.random(),
678
- `<button class="row-menu-item" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar banimento</button>`,
679
- );
658
+ const menu = revoked ? '' : renderMenuWrap('ban', ban?.id || Math.random(), `<button class="row-menu-item" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar banimento</button>`);
680
659
 
681
660
  return `
682
661
  <tr>
@@ -702,9 +681,7 @@ function renderLogsTab() {
702
681
  <p class="row-sub">${escapeHtml(ban?.reason || 'Sem motivo')}</p>
703
682
  <p class="row-meta">${escapeHtml(fmtDate(ban?.created_at))}</p>
704
683
  <div class="mobile-card-foot">
705
- ${Boolean(ban?.revoked_at)
706
- ? renderStatusBadge('revogado', 'status-neutral')
707
- : `<button class="outline-btn" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar</button>`}
684
+ ${Boolean(ban?.revoked_at) ? renderStatusBadge('revogado', 'status-neutral') : `<button class="outline-btn" data-action="revoke-ban" data-ban-id="${escapeHtml(ban?.id || '')}">Revogar</button>`}
708
685
  </div>
709
686
  </article>
710
687
  `,
@@ -869,9 +846,7 @@ function renderSystemTab() {
869
846
  <p class="row-meta break-all">${escapeHtml(row?.owner_jid || '')}</p>
870
847
  <div class="mobile-card-foot">
871
848
  ${Boolean(row?.active && !row?.revoked_at) ? renderStatusBadge('ativo', 'status-success') : renderStatusBadge('revogado', 'status-neutral')}
872
- ${Boolean(row?.active && !row?.revoked_at)
873
- ? `<button class="danger-btn" data-action="revoke-moderator" data-google-sub="${escapeHtml(row?.google_sub || '')}">Remover</button>`
874
- : ''}
849
+ ${Boolean(row?.active && !row?.revoked_at) ? `<button class="danger-btn" data-action="revoke-moderator" data-google-sub="${escapeHtml(row?.google_sub || '')}">Remover</button>` : ''}
875
850
  </div>
876
851
  </article>
877
852
  `,
@@ -930,8 +905,9 @@ function renderSystemTab() {
930
905
  <h3 class="panel-title">Moderadores</h3>
931
906
  <p class="panel-desc">Acesso secundario do painel com senha individual e sessao Google obrigatoria.</p>
932
907
  </div>
933
- ${ownerMode
934
- ? `
908
+ ${
909
+ ownerMode
910
+ ? `
935
911
  <form data-form="moderator-upsert" class="form-grid">
936
912
  <input class="search-input" list="moderator-target-list" name="target" placeholder="email, google_sub ou owner_jid do usuario logado" />
937
913
  <datalist id="moderator-target-list">${targetOptions}</datalist>
@@ -952,7 +928,8 @@ function renderSystemTab() {
952
928
  </div>
953
929
  <div class="mobile-list mobile-only">${moderatorRowsMobile || '<div class="empty-box">Nenhum moderador cadastrado.</div>'}</div>
954
930
  `
955
- : '<div class="empty-box">Sessao atual de moderador. Apenas o owner pode cadastrar/remover moderadores.</div>'}
931
+ : '<div class="empty-box">Sessao atual de moderador. Apenas o owner pode cadastrar/remover moderadores.</div>'
932
+ }
956
933
  </article>
957
934
 
958
935
  <article class="panel">
@@ -1044,12 +1021,8 @@ function renderHeader() {
1044
1021
  </div>
1045
1022
  <div class="topbar-actions">
1046
1023
  <button class="ghost-btn" data-action="refresh-dashboard" ${state.busy ? 'disabled' : ''}>Atualizar</button>
1047
- ${isAdminAuthenticated()
1048
- ? `<span class="user-chip">${escapeHtml(`${roleLabel ? `${roleLabel} ` : ''}${adminUser?.email || adminUser?.name || 'Admin'}`)}</span>`
1049
- : '<span class="user-chip muted">Nao autenticado</span>'}
1050
- ${isAdminAuthenticated()
1051
- ? `<button class="ghost-btn danger" data-action="logout-admin" ${state.busy ? 'disabled' : ''}>Sair</button>`
1052
- : `<button class="ghost-btn" data-action="refresh-admin-status" ${state.busy ? 'disabled' : ''}>Revalidar</button>`}
1024
+ ${isAdminAuthenticated() ? `<span class="user-chip">${escapeHtml(`${roleLabel ? `${roleLabel} • ` : ''}${adminUser?.email || adminUser?.name || 'Admin'}`)}</span>` : '<span class="user-chip muted">Nao autenticado</span>'}
1025
+ ${isAdminAuthenticated() ? `<button class="ghost-btn danger" data-action="logout-admin" ${state.busy ? 'disabled' : ''}>Sair</button>` : `<button class="ghost-btn" data-action="refresh-admin-status" ${state.busy ? 'disabled' : ''}>Revalidar</button>`}
1053
1026
  </div>
1054
1027
  </div>
1055
1028
  </header>
@@ -1108,31 +1081,33 @@ function renderUnlockView() {
1108
1081
  <button class="subtle-btn" data-action="refresh-admin-status" ${state.busy ? 'disabled' : ''}>Revalidar</button>
1109
1082
  </div>
1110
1083
 
1111
- ${googleSession?.authenticated
1112
- ? `
1084
+ ${
1085
+ googleSession?.authenticated
1086
+ ? `
1113
1087
  <div class="account-box">
1114
1088
  <p class="row-title">${escapeHtml(googleSession.user?.name || 'Conta Google')}</p>
1115
1089
  <p class="row-sub break-all">${escapeHtml(googleSession.user?.email || '')}</p>
1116
1090
  <p class="row-meta">${canUnlockAdmin() ? 'Conta elegivel para admin.' : 'Conta Google logada nao bate com ADM_EMAIL.'}</p>
1117
1091
  </div>
1118
1092
  `
1119
- : `
1093
+ : `
1120
1094
  <div class="account-box warning">
1121
1095
  <p class="row-title">Nenhuma sessao Google ativa no servidor.</p>
1122
- ${hasLocalCache
1123
- ? `<p class="row-sub">Sessao local encontrada: ${escapeHtml(localGoogleCache.user?.email || localGoogleCache.user?.name || 'Conta Google')}.</p>`
1124
- : '<p class="row-sub">Nao encontramos cache local de login Google.</p>'}
1096
+ ${hasLocalCache ? `<p class="row-sub">Sessao local encontrada: ${escapeHtml(localGoogleCache.user?.email || localGoogleCache.user?.name || 'Conta Google')}.</p>` : '<p class="row-sub">Nao encontramos cache local de login Google.</p>'}
1125
1097
  <p class="row-meta">Renove o login Google abaixo para continuar.</p>
1126
1098
  </div>
1127
- ${googleConfigEnabled
1128
- ? `
1099
+ ${
1100
+ googleConfigEnabled
1101
+ ? `
1129
1102
  <div class="google-login-box">
1130
1103
  <div data-google-admin-login-button class="google-login-slot"></div>
1131
1104
  ${state.googleLoginUiReady ? '' : '<p class="row-meta">Carregando botao de login Google...</p>'}
1132
1105
  </div>
1133
1106
  `
1134
- : `<p class="row-meta">Login Google indisponivel no painel (${escapeHtml(state.googleAuthConfigError || 'config nao encontrada')}).</p>`}
1135
- `}
1107
+ : `<p class="row-meta">Login Google indisponivel no painel (${escapeHtml(state.googleAuthConfigError || 'config nao encontrada')}).</p>`
1108
+ }
1109
+ `
1110
+ }
1136
1111
  </article>
1137
1112
 
1138
1113
  <article class="panel inner">
@@ -1142,9 +1117,7 @@ function renderUnlockView() {
1142
1117
  <form data-form="admin-unlock" class="form-grid">
1143
1118
  <input class="search-input" type="password" name="password" placeholder="Digite a senha do painel" autocomplete="current-password" />
1144
1119
  <button class="primary-btn" type="submit" ${state.busy ? 'disabled' : ''}>${state.busy ? 'Validando...' : 'Desbloquear Painel'}</button>
1145
- ${!canUnlockAdmin()
1146
- ? `<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (${escapeHtml(adminEmail)} ou moderador autorizado).</p>`
1147
- : '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
1120
+ ${!canUnlockAdmin() ? `<p class="hint warning">A senha so desbloqueia apos sessao Google elegivel (${escapeHtml(adminEmail)} ou moderador autorizado).</p>` : '<p class="hint">Sessao Google elegivel detectada. Informe a senha correspondente.</p>'}
1148
1121
  </form>
1149
1122
  </article>
1150
1123
  </div>
@@ -1296,81 +1269,96 @@ async function boot() {
1296
1269
  }
1297
1270
 
1298
1271
  async function refreshAdminStatusOnly() {
1299
- await runTask(async () => {
1300
- await Promise.all([loadAdminStatus(), loadGoogleAuthConfig()]);
1301
- }, { successMessage: 'Status de autenticacao atualizado.' });
1272
+ await runTask(
1273
+ async () => {
1274
+ await Promise.all([loadAdminStatus(), loadGoogleAuthConfig()]);
1275
+ },
1276
+ { successMessage: 'Status de autenticacao atualizado.' },
1277
+ );
1302
1278
  }
1303
1279
 
1304
1280
  async function refreshDashboard() {
1305
- await runTask(async () => {
1306
- await Promise.all([loadAdminStatus(), loadGoogleAuthConfig()]);
1307
- if (!isAdminAuthenticated()) {
1308
- state.overview = null;
1309
- state.packs = [];
1310
- state.moderators = [];
1311
- state.selectedPack = null;
1312
- state.selectedPackKey = '';
1313
- return;
1314
- }
1315
- await bootstrapDashboardData();
1316
- }, { successMessage: 'Painel atualizado.' });
1281
+ await runTask(
1282
+ async () => {
1283
+ await Promise.all([loadAdminStatus(), loadGoogleAuthConfig()]);
1284
+ if (!isAdminAuthenticated()) {
1285
+ state.overview = null;
1286
+ state.packs = [];
1287
+ state.moderators = [];
1288
+ state.selectedPack = null;
1289
+ state.selectedPackKey = '';
1290
+ return;
1291
+ }
1292
+ await bootstrapDashboardData();
1293
+ },
1294
+ { successMessage: 'Painel atualizado.' },
1295
+ );
1317
1296
  }
1318
1297
 
1319
1298
  async function loginGoogleForAdmin(credential) {
1320
- await runTask(async () => {
1321
- const payload = await fetchJson(googleSessionApiPath, {
1322
- method: 'POST',
1323
- headers: { 'Content-Type': 'application/json; charset=utf-8' },
1324
- body: JSON.stringify({ google_id_token: credential }),
1325
- });
1326
- const data = payload?.data || {};
1327
- if (!data?.authenticated || !data?.user?.sub) {
1328
- throw new Error('Nao foi possivel criar sessao Google do site.');
1329
- }
1330
- await loadAdminStatus();
1331
- }, { successMessage: 'Sessao Google renovada.' });
1299
+ await runTask(
1300
+ async () => {
1301
+ const payload = await fetchJson(googleSessionApiPath, {
1302
+ method: 'POST',
1303
+ headers: { 'Content-Type': 'application/json; charset=utf-8' },
1304
+ body: JSON.stringify({ google_id_token: credential }),
1305
+ });
1306
+ const data = payload?.data || {};
1307
+ if (!data?.authenticated || !data?.user?.sub) {
1308
+ throw new Error('Nao foi possivel criar sessao Google do site.');
1309
+ }
1310
+ await loadAdminStatus();
1311
+ },
1312
+ { successMessage: 'Sessao Google renovada.' },
1313
+ );
1332
1314
  }
1333
1315
 
1334
1316
  async function unlockAdmin(password) {
1335
- await runTask(async () => {
1336
- if (!canUnlockAdmin()) {
1337
- const google = state.adminStatus?.google || {};
1338
- const adminEmail = String(state.adminStatus?.admin_email || '').trim();
1339
- const loggedEmail = String(google?.user?.email || '').trim();
1340
- if (!google?.authenticated) {
1341
- const local = readLocalGoogleAuthCache();
1342
- if (local?.user?.email) {
1343
- throw new Error(`Sua sessao Google do servidor expirou. Renove o login Google (${local.user.email}) e tente novamente.`);
1317
+ await runTask(
1318
+ async () => {
1319
+ if (!canUnlockAdmin()) {
1320
+ const google = state.adminStatus?.google || {};
1321
+ const adminEmail = String(state.adminStatus?.admin_email || '').trim();
1322
+ const loggedEmail = String(google?.user?.email || '').trim();
1323
+ if (!google?.authenticated) {
1324
+ const local = readLocalGoogleAuthCache();
1325
+ if (local?.user?.email) {
1326
+ throw new Error(`Sua sessao Google do servidor expirou. Renove o login Google (${local.user.email}) e tente novamente.`);
1327
+ }
1328
+ throw new Error('Faca login Google no site com o email admin antes de digitar a senha.');
1344
1329
  }
1345
- throw new Error('Faca login Google no site com o email admin antes de digitar a senha.');
1346
- }
1347
- if (loggedEmail && adminEmail && loggedEmail.toLowerCase() !== adminEmail.toLowerCase()) {
1348
- throw new Error(`Email logado (${loggedEmail}) e diferente do ADM_EMAIL (${adminEmail}).`);
1330
+ if (loggedEmail && adminEmail && loggedEmail.toLowerCase() !== adminEmail.toLowerCase()) {
1331
+ throw new Error(`Email logado (${loggedEmail}) e diferente do ADM_EMAIL (${adminEmail}).`);
1332
+ }
1333
+ throw new Error('Conta Google atual nao esta elegivel para desbloquear o painel.');
1349
1334
  }
1350
- throw new Error('Conta Google atual nao esta elegivel para desbloquear o painel.');
1351
- }
1352
1335
 
1353
- const payload = await fetchJson(`${adminApiBase}/session`, {
1354
- method: 'POST',
1355
- headers: { 'Content-Type': 'application/json; charset=utf-8' },
1356
- body: JSON.stringify({ password }),
1357
- });
1358
- state.adminStatus = payload?.data || null;
1359
- state.activeTab = 'users';
1360
- await bootstrapDashboardData();
1361
- }, { successMessage: 'Painel admin desbloqueado.' });
1336
+ const payload = await fetchJson(`${adminApiBase}/session`, {
1337
+ method: 'POST',
1338
+ headers: { 'Content-Type': 'application/json; charset=utf-8' },
1339
+ body: JSON.stringify({ password }),
1340
+ });
1341
+ state.adminStatus = payload?.data || null;
1342
+ state.activeTab = 'users';
1343
+ await bootstrapDashboardData();
1344
+ },
1345
+ { successMessage: 'Painel admin desbloqueado.' },
1346
+ );
1362
1347
  }
1363
1348
 
1364
1349
  async function logoutAdmin() {
1365
- await runTask(async () => {
1366
- await fetchJson(`${adminApiBase}/session`, { method: 'DELETE' });
1367
- await loadAdminStatus();
1368
- state.overview = null;
1369
- state.packs = [];
1370
- state.moderators = [];
1371
- state.selectedPack = null;
1372
- state.selectedPackKey = '';
1373
- }, { successMessage: 'Sessao admin encerrada.' });
1350
+ await runTask(
1351
+ async () => {
1352
+ await fetchJson(`${adminApiBase}/session`, { method: 'DELETE' });
1353
+ await loadAdminStatus();
1354
+ state.overview = null;
1355
+ state.packs = [];
1356
+ state.moderators = [];
1357
+ state.selectedPack = null;
1358
+ state.selectedPackKey = '';
1359
+ },
1360
+ { successMessage: 'Sessao admin encerrada.' },
1361
+ );
1374
1362
  }
1375
1363
 
1376
1364
  async function searchPacks(query) {
@@ -1389,83 +1377,104 @@ async function openPackDetailsAdmin(packKey) {
1389
1377
 
1390
1378
  async function deletePackAdmin(packKey) {
1391
1379
  if (!window.confirm(`Apagar pack "${packKey}"?`)) return;
1392
- await runTask(async () => {
1393
- await fetchJson(`${adminApiBase}/packs/${encodeURIComponent(packKey)}/delete`, { method: 'DELETE' });
1394
- await loadPacks(state.packsQuery || '');
1395
- if (state.selectedPackKey === packKey) {
1396
- state.selectedPack = null;
1397
- state.selectedPackKey = '';
1398
- }
1399
- if (state.overview) await loadOverview();
1400
- }, { successMessage: 'Pack removido.' });
1380
+ await runTask(
1381
+ async () => {
1382
+ await fetchJson(`${adminApiBase}/packs/${encodeURIComponent(packKey)}/delete`, { method: 'DELETE' });
1383
+ await loadPacks(state.packsQuery || '');
1384
+ if (state.selectedPackKey === packKey) {
1385
+ state.selectedPack = null;
1386
+ state.selectedPackKey = '';
1387
+ }
1388
+ if (state.overview) await loadOverview();
1389
+ },
1390
+ { successMessage: 'Pack removido.' },
1391
+ );
1401
1392
  }
1402
1393
 
1403
1394
  async function removeStickerFromPackAdmin(packKey, stickerId) {
1404
1395
  if (!window.confirm(`Remover sticker ${stickerId} do pack ${packKey}?`)) return;
1405
- await runTask(async () => {
1406
- await fetchJson(`${adminApiBase}/packs/${encodeURIComponent(packKey)}/stickers/${encodeURIComponent(stickerId)}/delete`, {
1407
- method: 'DELETE',
1408
- });
1409
- await loadSelectedPack(packKey);
1410
- await loadPacks(state.packsQuery || '');
1411
- if (state.overview) await loadOverview();
1412
- }, { successMessage: 'Sticker removido do pack.' });
1396
+ await runTask(
1397
+ async () => {
1398
+ await fetchJson(`${adminApiBase}/packs/${encodeURIComponent(packKey)}/stickers/${encodeURIComponent(stickerId)}/delete`, {
1399
+ method: 'DELETE',
1400
+ });
1401
+ await loadSelectedPack(packKey);
1402
+ await loadPacks(state.packsQuery || '');
1403
+ if (state.overview) await loadOverview();
1404
+ },
1405
+ { successMessage: 'Sticker removido do pack.' },
1406
+ );
1413
1407
  }
1414
1408
 
1415
1409
  async function forceDeleteStickerAdmin(stickerId) {
1416
1410
  if (!window.confirm(`Apagar sticker ${stickerId} globalmente (todas as referencias)?`)) return;
1417
- await runTask(async () => {
1418
- await fetchJson(`${adminApiBase}/stickers/${encodeURIComponent(stickerId)}/delete`, { method: 'DELETE' });
1419
- if (state.selectedPackKey) {
1420
- await loadSelectedPack(state.selectedPackKey).catch(() => {
1421
- state.selectedPack = null;
1422
- });
1423
- }
1424
- await loadPacks(state.packsQuery || '');
1425
- if (state.overview) await loadOverview();
1426
- }, { successMessage: 'Sticker removido globalmente.' });
1411
+ await runTask(
1412
+ async () => {
1413
+ await fetchJson(`${adminApiBase}/stickers/${encodeURIComponent(stickerId)}/delete`, { method: 'DELETE' });
1414
+ if (state.selectedPackKey) {
1415
+ await loadSelectedPack(state.selectedPackKey).catch(() => {
1416
+ state.selectedPack = null;
1417
+ });
1418
+ }
1419
+ await loadPacks(state.packsQuery || '');
1420
+ if (state.overview) await loadOverview();
1421
+ },
1422
+ { successMessage: 'Sticker removido globalmente.' },
1423
+ );
1427
1424
  }
1428
1425
 
1429
1426
  async function createBanAdmin(payload) {
1430
- await runTask(async () => {
1431
- await fetchJson(`${adminApiBase}/bans`, {
1432
- method: 'POST',
1433
- headers: { 'Content-Type': 'application/json; charset=utf-8' },
1434
- body: JSON.stringify(payload),
1435
- });
1436
- await loadOverview();
1437
- }, { successMessage: 'Usuario banido.' });
1427
+ await runTask(
1428
+ async () => {
1429
+ await fetchJson(`${adminApiBase}/bans`, {
1430
+ method: 'POST',
1431
+ headers: { 'Content-Type': 'application/json; charset=utf-8' },
1432
+ body: JSON.stringify(payload),
1433
+ });
1434
+ await loadOverview();
1435
+ },
1436
+ { successMessage: 'Usuario banido.' },
1437
+ );
1438
1438
  }
1439
1439
 
1440
1440
  async function revokeBanAdmin(banId) {
1441
1441
  if (!window.confirm('Revogar este banimento?')) return;
1442
- await runTask(async () => {
1443
- await fetchJson(`${adminApiBase}/bans/${encodeURIComponent(banId)}/revoke`, { method: 'DELETE' });
1444
- await loadOverview();
1445
- }, { successMessage: 'Banimento revogado.' });
1442
+ await runTask(
1443
+ async () => {
1444
+ await fetchJson(`${adminApiBase}/bans/${encodeURIComponent(banId)}/revoke`, { method: 'DELETE' });
1445
+ await loadOverview();
1446
+ },
1447
+ { successMessage: 'Banimento revogado.' },
1448
+ );
1446
1449
  }
1447
1450
 
1448
1451
  async function upsertModeratorAdmin(payload) {
1449
- await runTask(async () => {
1450
- await fetchJson(`${adminApiBase}/moderators`, {
1451
- method: 'POST',
1452
- headers: { 'Content-Type': 'application/json; charset=utf-8' },
1453
- body: JSON.stringify(payload),
1454
- });
1455
- await Promise.all([loadModerators(), loadOverview()]);
1456
- }, { successMessage: 'Moderador salvo com sucesso.' });
1452
+ await runTask(
1453
+ async () => {
1454
+ await fetchJson(`${adminApiBase}/moderators`, {
1455
+ method: 'POST',
1456
+ headers: { 'Content-Type': 'application/json; charset=utf-8' },
1457
+ body: JSON.stringify(payload),
1458
+ });
1459
+ await Promise.all([loadModerators(), loadOverview()]);
1460
+ },
1461
+ { successMessage: 'Moderador salvo com sucesso.' },
1462
+ );
1457
1463
  }
1458
1464
 
1459
1465
  async function revokeModeratorAdmin(googleSub) {
1460
1466
  const normalized = String(googleSub || '').trim();
1461
1467
  if (!normalized) return;
1462
1468
  if (!window.confirm('Remover acesso deste moderador?')) return;
1463
- await runTask(async () => {
1464
- await fetchJson(`${adminApiBase}/moderators/${encodeURIComponent(normalized)}`, {
1465
- method: 'DELETE',
1466
- });
1467
- await Promise.all([loadModerators(), loadOverview()]);
1468
- }, { successMessage: 'Moderador removido.' });
1469
+ await runTask(
1470
+ async () => {
1471
+ await fetchJson(`${adminApiBase}/moderators/${encodeURIComponent(normalized)}`, {
1472
+ method: 'DELETE',
1473
+ });
1474
+ await Promise.all([loadModerators(), loadOverview()]);
1475
+ },
1476
+ { successMessage: 'Moderador removido.' },
1477
+ );
1469
1478
  }
1470
1479
 
1471
1480
  async function ensureGoogleLoginButtonRendered() {