@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,12 +1,7 @@
1
1
  import logger from '../../utils/logger/loggerModule.js';
2
2
  import { setQueueDepth } from '../../observability/metrics.js';
3
3
  import { isFeatureEnabled } from '../../services/featureFlagService.js';
4
- import {
5
- claimDomainEvent,
6
- completeDomainEvent,
7
- countDomainEventsByStatus,
8
- failDomainEvent,
9
- } from './domainEventOutboxRepository.js';
4
+ import { claimDomainEvent, completeDomainEvent, countDomainEventsByStatus, failDomainEvent } from './domainEventOutboxRepository.js';
10
5
  import { STICKER_DOMAIN_EVENTS } from './domainEvents.js';
11
6
  import { enqueueWorkerTask } from './stickerWorkerTaskQueueRepository.js';
12
7
  import { enqueuePackScoreSnapshotRefresh } from './stickerPackScoreSnapshotRuntime.js';
@@ -21,32 +16,20 @@ const parseEnvBool = (value, fallback) => {
21
16
  };
22
17
 
23
18
  const CONSUMER_ENABLED = parseEnvBool(process.env.STICKER_DOMAIN_EVENT_CONSUMER_ENABLED, true);
24
- const STARTUP_DELAY_MS = Math.max(
25
- 1_000,
26
- Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_STARTUP_DELAY_MS) || 8_000,
27
- );
28
- const POLLER_INTERVAL_MS = Math.max(
29
- 1_000,
30
- Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_POLLER_INTERVAL_MS) || 2_000,
31
- );
32
- const RETRY_DELAY_SECONDS = Math.max(
33
- 5,
34
- Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_RETRY_DELAY_SECONDS) || 45),
35
- );
36
- const CONSUMER_COHORT_KEY =
37
- String(process.env.STICKER_DOMAIN_EVENT_CONSUMER_COHORT_KEY || process.env.HOSTNAME || process.pid).trim()
38
- || 'consumer';
19
+ const STARTUP_DELAY_MS = Math.max(1_000, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_STARTUP_DELAY_MS) || 8_000);
20
+ const POLLER_INTERVAL_MS = Math.max(1_000, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_POLLER_INTERVAL_MS) || 2_000);
21
+ const RETRY_DELAY_SECONDS = Math.max(5, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CONSUMER_RETRY_DELAY_SECONDS) || 45));
22
+ const CLASSIFICATION_COALESCE_WINDOW_SECONDS = Math.max(30, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CLASSIFICATION_COALESCE_WINDOW_SECONDS) || 60));
23
+ const CURATION_COALESCE_WINDOW_SECONDS = Math.max(30, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_CURATION_COALESCE_WINDOW_SECONDS) || 60));
24
+ const REBUILD_COALESCE_WINDOW_SECONDS = Math.max(30, Math.min(3600, Number(process.env.STICKER_DOMAIN_EVENT_REBUILD_COALESCE_WINDOW_SECONDS) || 120));
25
+ const CONSUMER_COHORT_KEY = String(process.env.STICKER_DOMAIN_EVENT_CONSUMER_COHORT_KEY || process.env.HOSTNAME || process.pid).trim() || 'consumer';
39
26
 
40
27
  let startupHandle = null;
41
28
  let pollerHandle = null;
42
29
  let running = false;
43
30
 
44
31
  const refreshOutboxDepthMetrics = async () => {
45
- const [pending, processing, failed] = await Promise.all([
46
- countDomainEventsByStatus('pending'),
47
- countDomainEventsByStatus('processing'),
48
- countDomainEventsByStatus('failed'),
49
- ]);
32
+ const [pending, processing, failed] = await Promise.all([countDomainEventsByStatus('pending'), countDomainEventsByStatus('processing'), countDomainEventsByStatus('failed')]);
50
33
  setQueueDepth('domain_event_outbox_pending', pending);
51
34
  setQueueDepth('domain_event_outbox_processing', processing);
52
35
  setQueueDepth('domain_event_outbox_failed', failed);
@@ -67,22 +50,38 @@ const enqueueTaskSafely = async ({ taskType, payload, priority, idempotencyKey }
67
50
  });
68
51
  };
69
52
 
53
+ const toUnixSeconds = (value) => {
54
+ if (!value) return Math.floor(Date.now() / 1000);
55
+ const numeric = Date.parse(value);
56
+ if (!Number.isFinite(numeric)) return Math.floor(Date.now() / 1000);
57
+ return Math.floor(numeric / 1000);
58
+ };
59
+
60
+ const toWindowBucket = (value, windowSeconds) => {
61
+ const safeWindow = Math.max(1, Math.floor(windowSeconds || 1));
62
+ return Math.floor(toUnixSeconds(value) / safeWindow);
63
+ };
64
+
70
65
  const handleDomainEvent = async (event) => {
71
- const eventType = String(event?.event_type || '').trim().toUpperCase();
66
+ const eventType = String(event?.event_type || '')
67
+ .trim()
68
+ .toUpperCase();
72
69
  const aggregateId = String(event?.aggregate_id || '').trim();
73
70
  const payload = event?.payload && typeof event.payload === 'object' ? event.payload : {};
74
71
 
75
72
  if (eventType === STICKER_DOMAIN_EVENTS.STICKER_ASSET_CREATED) {
73
+ const coalesceBucket = toWindowBucket(event?.created_at, CLASSIFICATION_COALESCE_WINDOW_SECONDS);
76
74
  await enqueueTaskSafely({
77
75
  taskType: 'classification_cycle',
78
- payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId },
76
+ payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, coalesced: true },
79
77
  priority: 80,
80
- idempotencyKey: `evt:${event.id}:classification_cycle`,
78
+ idempotencyKey: `evt:${eventType}:${coalesceBucket}:classification_cycle`,
81
79
  });
82
80
  return;
83
81
  }
84
82
 
85
83
  if (eventType === STICKER_DOMAIN_EVENTS.STICKER_CLASSIFIED) {
84
+ const coalesceBucket = toWindowBucket(event?.created_at, CURATION_COALESCE_WINDOW_SECONDS);
86
85
  const assetId = String(payload?.asset_id || aggregateId || '').trim();
87
86
  const relatedPackIds = assetId ? await listPackIdsByStickerId(assetId).catch(() => []) : [];
88
87
  if (relatedPackIds.length) {
@@ -95,23 +94,28 @@ const handleDomainEvent = async (event) => {
95
94
  event_type: eventType,
96
95
  aggregate_id: aggregateId,
97
96
  related_pack_ids: relatedPackIds,
97
+ coalesced: true,
98
98
  },
99
99
  priority: 65,
100
- idempotencyKey: `evt:${event.id}:curation_cycle`,
100
+ idempotencyKey: `evt:${eventType}:${coalesceBucket}:curation_cycle`,
101
101
  });
102
102
  return;
103
103
  }
104
104
 
105
105
  if (eventType === STICKER_DOMAIN_EVENTS.PACK_UPDATED) {
106
+ const coalesceBucket = toWindowBucket(event?.created_at, REBUILD_COALESCE_WINDOW_SECONDS);
106
107
  const packId = String(payload?.pack_id || aggregateId || '').trim();
107
108
  if (packId) {
108
109
  enqueuePackScoreSnapshotRefresh([packId]);
109
110
  }
111
+ const rebuildIdempotency = packId
112
+ ? `evt:${eventType}:${packId}:${coalesceBucket}:rebuild_cycle`
113
+ : `evt:${eventType}:${coalesceBucket}:rebuild_cycle`;
110
114
  await enqueueTaskSafely({
111
115
  taskType: 'rebuild_cycle',
112
- payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, pack_id: packId || null },
116
+ payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, pack_id: packId || null, coalesced: true },
113
117
  priority: 60,
114
- idempotencyKey: `evt:${event.id}:rebuild_cycle`,
118
+ idempotencyKey: rebuildIdempotency,
115
119
  });
116
120
  return;
117
121
  }
@@ -141,13 +145,10 @@ const pollOnce = async () => {
141
145
  await handleDomainEvent(event);
142
146
  await completeDomainEvent(event.id);
143
147
  } catch (error) {
144
- await failDomainEvent(
145
- event.id,
146
- {
147
- error: error?.message || 'domain_event_consumer_failed',
148
- retryDelaySeconds: RETRY_DELAY_SECONDS,
149
- },
150
- );
148
+ await failDomainEvent(event.id, {
149
+ error: error?.message || 'domain_event_consumer_failed',
150
+ retryDelaySeconds: RETRY_DELAY_SECONDS,
151
+ });
151
152
  logger.warn('Evento de domínio falhou no consumidor interno.', {
152
153
  action: 'sticker_domain_event_consumer_event_failed',
153
154
  event_id: event.id,
@@ -80,10 +80,7 @@ export const getMarketplaceDriftSnapshot = async ({ force = false } = {}) => {
80
80
  return cache;
81
81
  }
82
82
 
83
- const [distribution7d, distribution30d] = await Promise.all([
84
- listClassificationCategoryDistribution({ days: 7 }),
85
- listClassificationCategoryDistribution({ days: 30 }),
86
- ]);
83
+ const [distribution7d, distribution30d] = await Promise.all([listClassificationCategoryDistribution({ days: 7 }), listClassificationCategoryDistribution({ days: 30 })]);
87
84
 
88
85
  const probability7d = toProbabilityMap(distribution7d);
89
86
  const probability30d = toProbabilityMap(distribution30d);
@@ -14,15 +14,22 @@ const parseEnvBool = (value, fallback) => {
14
14
  const OBJECT_STORAGE_ENABLED = parseEnvBool(process.env.STICKER_OBJECT_STORAGE_ENABLED, false);
15
15
  const OBJECT_STORAGE_UPLOAD_ON_WRITE = parseEnvBool(process.env.STICKER_OBJECT_STORAGE_UPLOAD_ON_WRITE, true);
16
16
  const OBJECT_STORAGE_SIGNED_URL_ENABLED = parseEnvBool(process.env.STICKER_OBJECT_STORAGE_SIGNED_URL_ENABLED, true);
17
- const OBJECT_STORAGE_PROVIDER = String(process.env.STICKER_OBJECT_STORAGE_PROVIDER || 's3').trim().toLowerCase();
17
+ const OBJECT_STORAGE_PROVIDER = String(process.env.STICKER_OBJECT_STORAGE_PROVIDER || 's3')
18
+ .trim()
19
+ .toLowerCase();
18
20
  const OBJECT_STORAGE_BUCKET = String(process.env.STICKER_OBJECT_STORAGE_BUCKET || '').trim();
19
21
  const OBJECT_STORAGE_REGION = String(process.env.STICKER_OBJECT_STORAGE_REGION || 'us-east-1').trim() || 'us-east-1';
20
22
  const OBJECT_STORAGE_ENDPOINT = String(process.env.STICKER_OBJECT_STORAGE_ENDPOINT || '').trim();
21
23
  const OBJECT_STORAGE_ACCESS_KEY_ID = String(process.env.STICKER_OBJECT_STORAGE_ACCESS_KEY_ID || '').trim();
22
24
  const OBJECT_STORAGE_SECRET_ACCESS_KEY = String(process.env.STICKER_OBJECT_STORAGE_SECRET_ACCESS_KEY || '').trim();
23
25
  const OBJECT_STORAGE_FORCE_PATH_STYLE = parseEnvBool(process.env.STICKER_OBJECT_STORAGE_FORCE_PATH_STYLE, true);
24
- const OBJECT_STORAGE_CDN_BASE_URL = String(process.env.STICKER_OBJECT_STORAGE_CDN_BASE_URL || '').trim().replace(/\/+$/, '');
25
- const OBJECT_STORAGE_KEY_PREFIX = String(process.env.STICKER_OBJECT_STORAGE_KEY_PREFIX || 'stickers').trim().replace(/^\/+|\/+$/g, '') || 'stickers';
26
+ const OBJECT_STORAGE_CDN_BASE_URL = String(process.env.STICKER_OBJECT_STORAGE_CDN_BASE_URL || '')
27
+ .trim()
28
+ .replace(/\/+$/, '');
29
+ const OBJECT_STORAGE_KEY_PREFIX =
30
+ String(process.env.STICKER_OBJECT_STORAGE_KEY_PREFIX || 'stickers')
31
+ .trim()
32
+ .replace(/^\/+|\/+$/g, '') || 'stickers';
26
33
 
27
34
  let sdkLoadState = {
28
35
  loaded: false,
@@ -36,7 +43,9 @@ let s3Client = null;
36
43
 
37
44
  const safeOwnerToken = (ownerJid) => {
38
45
  const normalized = normalizeOwnerJid(ownerJid);
39
- const token = String(normalized || 'unknown').replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 100);
46
+ const token = String(normalized || 'unknown')
47
+ .replace(/[^a-zA-Z0-9._-]/g, '_')
48
+ .slice(0, 100);
40
49
  return token || 'unknown';
41
50
  };
42
51
 
@@ -62,7 +71,9 @@ const resolveStickerObjectKey = (asset) => {
62
71
  const fromStoragePath = parseS3StoragePath(asset?.storage_path);
63
72
  if (fromStoragePath?.key) return fromStoragePath.key;
64
73
  const ownerToken = safeOwnerToken(asset?.owner_jid || 'unknown');
65
- const sha256 = String(asset?.sha256 || '').trim().toLowerCase();
74
+ const sha256 = String(asset?.sha256 || '')
75
+ .trim()
76
+ .toLowerCase();
66
77
  if (!sha256) return '';
67
78
  return `${OBJECT_STORAGE_KEY_PREFIX}/${ownerToken}/${sha256}.webp`;
68
79
  };
@@ -70,10 +81,7 @@ const resolveStickerObjectKey = (asset) => {
70
81
  const loadAwsSdk = async () => {
71
82
  if (sdkLoadState.loaded) return sdkLoadState;
72
83
  try {
73
- const [{ S3Client, PutObjectCommand, GetObjectCommand }, { getSignedUrl }] = await Promise.all([
74
- import('@aws-sdk/client-s3'),
75
- import('@aws-sdk/s3-request-presigner'),
76
- ]);
84
+ const [{ S3Client, PutObjectCommand, GetObjectCommand }, { getSignedUrl }] = await Promise.all([import('@aws-sdk/client-s3'), import('@aws-sdk/s3-request-presigner')]);
77
85
  sdkLoadState = {
78
86
  loaded: true,
79
87
  warned: false,
@@ -149,15 +157,9 @@ const streamToBuffer = async (body) => {
149
157
  return null;
150
158
  };
151
159
 
152
- export const isStickerObjectStorageEnabled = () =>
153
- Boolean(OBJECT_STORAGE_ENABLED && OBJECT_STORAGE_PROVIDER === 's3' && OBJECT_STORAGE_BUCKET);
160
+ export const isStickerObjectStorageEnabled = () => Boolean(OBJECT_STORAGE_ENABLED && OBJECT_STORAGE_PROVIDER === 's3' && OBJECT_STORAGE_BUCKET);
154
161
 
155
- export const uploadStickerToObjectStorage = async ({
156
- ownerJid,
157
- sha256,
158
- buffer,
159
- mimetype = 'image/webp',
160
- } = {}) => {
162
+ export const uploadStickerToObjectStorage = async ({ ownerJid, sha256, buffer, mimetype = 'image/webp' } = {}) => {
161
163
  if (!OBJECT_STORAGE_UPLOAD_ON_WRITE || !Buffer.isBuffer(buffer) || !buffer.length) {
162
164
  return { uploaded: false, key: null };
163
165
  }
@@ -165,7 +167,9 @@ export const uploadStickerToObjectStorage = async ({
165
167
  return { uploaded: false, key: null };
166
168
  }
167
169
 
168
- const key = `${OBJECT_STORAGE_KEY_PREFIX}/${safeOwnerToken(ownerJid)}/${String(sha256 || '').trim().toLowerCase()}.webp`;
170
+ const key = `${OBJECT_STORAGE_KEY_PREFIX}/${safeOwnerToken(ownerJid)}/${String(sha256 || '')
171
+ .trim()
172
+ .toLowerCase()}.webp`;
169
173
  if (!key || key.endsWith('/.webp')) return { uploaded: false, key: null };
170
174
 
171
175
  try {
@@ -194,13 +198,7 @@ export const uploadStickerToObjectStorage = async ({
194
198
  }
195
199
  };
196
200
 
197
- export const getStickerObjectStorageUrl = async (
198
- asset,
199
- {
200
- secure = true,
201
- expiresInSeconds = 300,
202
- } = {},
203
- ) => {
201
+ export const getStickerObjectStorageUrl = async (asset, { secure = true, expiresInSeconds = 300 } = {}) => {
204
202
  if (!isStickerObjectStorageEnabled()) return null;
205
203
 
206
204
  const key = resolveStickerObjectKey(asset);
@@ -279,7 +277,9 @@ export const toStickerStoragePath = ({ localPath, ownerJid, sha256 }) => {
279
277
  const normalizedLocalPath = path.resolve(String(localPath || ''));
280
278
  if (!isStickerObjectStorageEnabled()) return normalizedLocalPath;
281
279
  if (!OBJECT_STORAGE_UPLOAD_ON_WRITE) return normalizedLocalPath;
282
- const key = `${OBJECT_STORAGE_KEY_PREFIX}/${safeOwnerToken(ownerJid)}/${String(sha256 || '').trim().toLowerCase()}.webp`;
280
+ const key = `${OBJECT_STORAGE_KEY_PREFIX}/${safeOwnerToken(ownerJid)}/${String(sha256 || '')
281
+ .trim()
282
+ .toLowerCase()}.webp`;
283
283
  if (!key || key.endsWith('/.webp')) return normalizedLocalPath;
284
284
  return `s3://${OBJECT_STORAGE_BUCKET}/${key}`;
285
285
  };
@@ -3,10 +3,7 @@ import { sendAndStore } from '../../services/messagePersistenceService.js';
3
3
  import { isUserJid } from '../../config/baileysConfig.js';
4
4
  import stickerPackService from './stickerPackServiceRuntime.js';
5
5
  import { STICKER_PACK_ERROR_CODES, StickerPackError } from './stickerPackErrors.js';
6
- import {
7
- captureIncomingStickerAsset,
8
- resolveStickerAssetForCommand,
9
- } from './stickerStorageService.js';
6
+ import { captureIncomingStickerAsset, resolveStickerAssetForCommand } from './stickerStorageService.js';
10
7
  import { buildStickerPackMessage, sendStickerPackWithFallback } from './stickerPackMessageService.js';
11
8
  import { sanitizeText } from './stickerPackUtils.js';
12
9
 
@@ -221,10 +218,7 @@ const buildPackVisualMessage = ({ intro = [], sections = [], footer = [] }) => {
221
218
  const buildActionMessage = ({ title, explanation = [], details = [], nextSteps = [], footer = [] }) =>
222
219
  buildPackVisualMessage({
223
220
  intro: [title, ...normalizeMessageLines(explanation)],
224
- sections: [
225
- normalizeMessageLines(details).length ? { title: '📌 *DETALHES*', lines: details } : null,
226
- normalizeMessageLines(nextSteps).length ? { title: '➡️ *PRÓXIMAS AÇÕES*', lines: nextSteps } : null,
227
- ],
221
+ sections: [normalizeMessageLines(details).length ? { title: '📌 *DETALHES*', lines: details } : null, normalizeMessageLines(nextSteps).length ? { title: '➡️ *PRÓXIMAS AÇÕES*', lines: nextSteps } : null],
228
222
  footer,
229
223
  });
230
224
 
@@ -238,18 +232,11 @@ const buildActionMessage = ({ title, explanation = [], details = [], nextSteps =
238
232
  const formatPackList = (packs, prefix) => {
239
233
  if (!packs.length) {
240
234
  return buildPackVisualMessage({
241
- intro: [
242
- '📭 *Nenhum pack extra encontrado.*',
243
- 'As figurinhas que você cria continuam sendo salvas automaticamente no seu *Pack Principal*.',
244
- ],
235
+ intro: ['📭 *Nenhum pack extra encontrado.*', 'As figurinhas que você cria continuam sendo salvas automaticamente no seu *Pack Principal*.'],
245
236
  sections: [
246
237
  {
247
238
  title: '🆕 *COMECE EM 3 PASSOS*',
248
- lines: [
249
- `1) Crie um pack: \`${prefix}pack create meupack\``,
250
- `2) Responda uma figurinha e adicione: \`${prefix}pack add <pack>\``,
251
- `3) Veja o resumo: \`${prefix}pack info <pack>\``,
252
- ],
239
+ lines: [`1) Crie um pack: \`${prefix}pack create meupack\``, `2) Responda uma figurinha e adicione: \`${prefix}pack add <pack>\``, `3) Veja o resumo: \`${prefix}pack info <pack>\``],
253
240
  },
254
241
  ],
255
242
  footer: ['💡 Dica: crie packs por tema (memes, animes, reactions) para achar tudo mais rápido.'],
@@ -258,28 +245,16 @@ const formatPackList = (packs, prefix) => {
258
245
 
259
246
  const lines = packs.map((pack, index) => {
260
247
  const count = Number(pack.sticker_count || 0);
261
- return [
262
- `${index + 1}. *${pack.name}*`,
263
- ` 🆔 ID: \`${pack.pack_key}\``,
264
- ` 🧩 Itens: ${count}/${MAX_PACK_ITEMS}`,
265
- ` 👁️ Visibilidade: ${formatVisibilityLabel(pack.visibility)}`,
266
- ].join('\n');
248
+ return [`${index + 1}. *${pack.name}*`, ` 🆔 ID: \`${pack.pack_key}\``, ` 🧩 Itens: ${count}/${MAX_PACK_ITEMS}`, ` 👁️ Visibilidade: ${formatVisibilityLabel(pack.visibility)}`].join('\n');
267
249
  });
268
250
 
269
251
  return buildPackVisualMessage({
270
- intro: [
271
- `📋 *Packs encontrados: ${packs.length}*`,
272
- 'Você pode usar o *nome* ou o *ID* do pack para ver detalhes, editar ou enviar.',
273
- ],
252
+ intro: [`📋 *Packs encontrados: ${packs.length}*`, 'Você pode usar o *nome* ou o *ID* do pack para ver detalhes, editar ou enviar.'],
274
253
  sections: [
275
254
  { title: '📦 *SEUS PACKS*', lines },
276
255
  {
277
256
  title: '🛠 *ATALHOS*',
278
- lines: [
279
- `ℹ️ Detalhes: \`${prefix}pack info <pack>\``,
280
- `📤 Enviar: \`${prefix}pack send <pack>\``,
281
- `🆕 Criar novo: \`${prefix}pack create meupack\``,
282
- ],
257
+ lines: [`ℹ️ Detalhes: \`${prefix}pack info <pack>\``, `📤 Enviar: \`${prefix}pack send <pack>\``, `🆕 Criar novo: \`${prefix}pack create meupack\``],
283
258
  },
284
259
  ],
285
260
  footer: ['✅ Tudo pronto — escolha um pack e continue gerenciando.'],
@@ -307,22 +282,11 @@ const formatPackInfo = (pack, prefix) => {
307
282
  }
308
283
 
309
284
  return buildPackVisualMessage({
310
- intro: [
311
- `ℹ️ *Informações do pack: "${pack.name}"*`,
312
- 'Aqui você vê identificação, visibilidade e uma prévia dos itens cadastrados.',
313
- ],
285
+ intro: [`ℹ️ *Informações do pack: "${pack.name}"*`, 'Aqui você vê identificação, visibilidade e uma prévia dos itens cadastrados.'],
314
286
  sections: [
315
287
  {
316
288
  title: '📌 *DADOS DO PACK*',
317
- lines: [
318
- `📛 Nome: *${pack.name}*`,
319
- `🆔 ID: \`${pack.pack_key}\``,
320
- `👤 Publisher: *${pack.publisher}*`,
321
- `👁️ Visibilidade: ${formatVisibilityLabel(pack.visibility)}`,
322
- `🧩 Itens: *${pack.items.length}/${MAX_PACK_ITEMS}*`,
323
- `🖼️ Capa: *${coverLabel}*`,
324
- `📝 Descrição: ${pack.description ? `"${pack.description}"` : 'não definida'}`,
325
- ],
289
+ lines: [`📛 Nome: *${pack.name}*`, `🆔 ID: \`${pack.pack_key}\``, `👤 Publisher: *${pack.publisher}*`, `👁️ Visibilidade: ${formatVisibilityLabel(pack.visibility)}`, `🧩 Itens: *${pack.items.length}/${MAX_PACK_ITEMS}*`, `🖼️ Capa: *${coverLabel}*`, `📝 Descrição: ${pack.description ? `"${pack.description}"` : 'não definida'}`],
326
290
  },
327
291
  {
328
292
  title: '🖼️ *PRÉVIA (ATÉ 12 ITENS)*',
@@ -330,12 +294,7 @@ const formatPackInfo = (pack, prefix) => {
330
294
  },
331
295
  {
332
296
  title: '⚙️ *AÇÕES DISPONÍVEIS*',
333
- lines: [
334
- `➕ Adicionar: \`${prefix}pack add ${pack.pack_key}\``,
335
- `🖼 Definir capa: \`${prefix}pack setcover ${pack.pack_key}\``,
336
- `🔀 Reordenar: \`${prefix}pack reorder ${pack.pack_key} 1 2 3 ...\``,
337
- `📤 Enviar: \`${prefix}pack send ${pack.pack_key}\``,
338
- ],
297
+ lines: [`➕ Adicionar: \`${prefix}pack add ${pack.pack_key}\``, `🖼 Definir capa: \`${prefix}pack setcover ${pack.pack_key}\``, `🔀 Reordenar: \`${prefix}pack reorder ${pack.pack_key} 1 2 3 ...\``, `📤 Enviar: \`${prefix}pack send ${pack.pack_key}\``],
339
298
  },
340
299
  ],
341
300
  footer: ['💡 Se precisar, use o guia completo com `pack` para ver exemplos e comandos extras.'],
@@ -348,45 +307,7 @@ const formatPackInfo = (pack, prefix) => {
348
307
  * @param {string} prefix Prefixo de comando.
349
308
  * @returns {string} Guia textual.
350
309
  */
351
- const buildPackHelp = (prefix) =>
352
- [
353
- '📦 *PACKS DE FIGURINHAS — GUIA RÁPIDO*',
354
- '',
355
- 'Toda figurinha que você criar é salva automaticamente no seu *Pack Principal*.',
356
- 'Além disso, você pode criar packs extras para organizar por tema e enviar mais rápido.',
357
- '',
358
- PACK_VISUAL_DIVIDER,
359
- '🧭 *COMANDOS PRINCIPAIS*',
360
- '',
361
- '🆕 Criar um pack',
362
- `\`${prefix}pack create "Meus memes 😂" | publisher="Seu Nome" | desc="Descrição"\``,
363
- '_Nome livre: espaços e emojis são permitidos._',
364
- '',
365
- '📋 Listar packs',
366
- `\`${prefix}pack list\``,
367
- '',
368
- 'ℹ️ Ver detalhes do pack',
369
- `\`${prefix}pack info <pack>\``,
370
- '',
371
- '➕ Adicionar figurinha',
372
- `\`${prefix}pack add <pack>\``,
373
- '_Dica: responda uma figurinha (ou use a última enviada)._',
374
- '',
375
- '🖼 Definir capa',
376
- `\`${prefix}pack setcover <pack>\``,
377
- '',
378
- '📤 Enviar pack no chat',
379
- `\`${prefix}pack send "<nome do pack>"\``,
380
- `_Ou use o ID: \`${prefix}pack send <pack_id>\`_`,
381
- '',
382
- PACK_VISUAL_DIVIDER,
383
- '🧰 *COMANDOS EXTRAS*',
384
- '',
385
- '`rename` • `setpub` • `setdesc` • `remove` • `reorder` • `clone` • `publish` • `delete`',
386
- '',
387
- PACK_VISUAL_DIVIDER,
388
- '✅ *Pronto!* Se quiser, diga o que você quer fazer (criar, organizar, enviar) que eu te guio.',
389
- ].join('\n');
310
+ const buildPackHelp = (prefix) => ['📦 *PACKS DE FIGURINHAS — GUIA RÁPIDO*', '', 'Toda figurinha que você criar é salva automaticamente no seu *Pack Principal*.', 'Além disso, você pode criar packs extras para organizar por tema e enviar mais rápido.', '', PACK_VISUAL_DIVIDER, '🧭 *COMANDOS PRINCIPAIS*', '', '🆕 Criar um pack', `\`${prefix}pack create "Meus memes 😂" | publisher="Seu Nome" | desc="Descrição"\``, '_Nome livre: espaços e emojis são permitidos._', '', '📋 Listar packs', `\`${prefix}pack list\``, '', 'ℹ️ Ver detalhes do pack', `\`${prefix}pack info <pack>\``, '', '➕ Adicionar figurinha', `\`${prefix}pack add <pack>\``, '_Dica: responda uma figurinha (ou use a última enviada)._', '', '🖼 Definir capa', `\`${prefix}pack setcover <pack>\``, '', '📤 Enviar pack no chat', `\`${prefix}pack send "<nome do pack>"\``, `_Ou use o ID: \`${prefix}pack send <pack_id>\`_`, '', PACK_VISUAL_DIVIDER, '🧰 *COMANDOS EXTRAS*', '', '`rename` • `setpub` • `setdesc` • `remove` • `reorder` • `clone` • `publish` • `delete`', '', PACK_VISUAL_DIVIDER, '✅ *Pronto!* Se quiser, diga o que você quer fazer (criar, organizar, enviar) que eu te guio.'].join('\n');
390
311
 
391
312
  /**
392
313
  * Template visual de erro orientado a resolução.
@@ -430,41 +351,28 @@ const formatErrorMessage = (error, commandPrefix) => {
430
351
  return buildErrorMessage({
431
352
  title: '🔎 *Pack não encontrado.*',
432
353
  explanation: ['Não localizei um pack com esse nome ou ID.'],
433
- steps: [
434
- `Veja a lista com \`${commandPrefix}pack list\`.`,
435
- 'Copie o ID exatamente como aparece.',
436
- `Depois tente novamente (ex.: \`${commandPrefix}pack info <pack>\`).`,
437
- ],
354
+ steps: [`Veja a lista com \`${commandPrefix}pack list\`.`, 'Copie o ID exatamente como aparece.', `Depois tente novamente (ex.: \`${commandPrefix}pack info <pack>\`).`],
438
355
  commandPrefix,
439
356
  });
440
357
  case STICKER_PACK_ERROR_CODES.DUPLICATE_STICKER:
441
358
  return buildErrorMessage({
442
359
  title: '⚠️ *Essa figurinha já está no pack.*',
443
360
  explanation: ['Para manter o pack organizado, não adiciono itens duplicados.'],
444
- steps: [
445
- `Veja os itens com \`${commandPrefix}pack info <pack>\`.`,
446
- 'Se quiser reorganizar, use `reorder`.',
447
- ],
361
+ steps: [`Veja os itens com \`${commandPrefix}pack info <pack>\`.`, 'Se quiser reorganizar, use `reorder`.'],
448
362
  commandPrefix,
449
363
  });
450
364
  case STICKER_PACK_ERROR_CODES.PACK_LIMIT_REACHED:
451
365
  return buildErrorMessage({
452
366
  title: '⚠️ *Limite de figurinhas atingido.*',
453
367
  explanation: [error.message || 'Este pack já está no limite e não aceita novos itens no momento.'],
454
- steps: [
455
- `Crie outro pack: \`${commandPrefix}pack create novopack\`.`,
456
- 'Depois continue adicionando as próximas figurinhas no novo pack.',
457
- ],
368
+ steps: [`Crie outro pack: \`${commandPrefix}pack create novopack\`.`, 'Depois continue adicionando as próximas figurinhas no novo pack.'],
458
369
  commandPrefix,
459
370
  });
460
371
  case STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND:
461
372
  return buildErrorMessage({
462
373
  title: '🧩 *Não encontrei uma figurinha válida para usar.*',
463
374
  explanation: ['Para esse comando, você precisa responder uma figurinha ou ter uma figurinha recente no contexto.'],
464
- steps: [
465
- 'Responda diretamente a figurinha que você quer usar.',
466
- 'Ou envie uma figurinha e execute o comando novamente.',
467
- ],
375
+ steps: ['Responda diretamente a figurinha que você quer usar.', 'Ou envie uma figurinha e execute o comando novamente.'],
468
376
  commandPrefix,
469
377
  });
470
378
  case STICKER_PACK_ERROR_CODES.INVALID_INPUT:
@@ -621,16 +529,7 @@ const resolveStickerFromCommandContext = async ({ messageInfo, ownerJid, include
621
529
  * }} params Contexto da requisição.
622
530
  * @returns {Promise<void>}
623
531
  */
624
- export async function handlePackCommand({
625
- sock,
626
- remoteJid,
627
- messageInfo,
628
- expirationMessage,
629
- senderJid,
630
- senderName,
631
- text,
632
- commandPrefix,
633
- }) {
532
+ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirationMessage, senderJid, senderName, text, commandPrefix }) {
634
533
  const ownerJid = senderJid;
635
534
  const rate = checkRateLimit(ownerJid);
636
535
 
@@ -682,16 +581,8 @@ export async function handlePackCommand({
682
581
  text: buildActionMessage({
683
582
  title: '✅ *Pack criado!*',
684
583
  explanation: ['Seu pack já está disponível e pronto para receber figurinhas.'],
685
- details: [
686
- `📛 Nome: *${created.name}*`,
687
- `🆔 ID: \`${created.pack_key}\``,
688
- `👤 Publisher: *${created.publisher}*`,
689
- `👁️ Visibilidade: ${formatVisibilityLabel(created.visibility)}`,
690
- ],
691
- nextSteps: [
692
- `Responda uma figurinha e use: \`${commandPrefix}pack add ${created.pack_key}\`.`,
693
- `Para conferir: \`${commandPrefix}pack info ${created.pack_key}\`.`,
694
- ],
584
+ details: [`📛 Nome: *${created.name}*`, `🆔 ID: \`${created.pack_key}\``, `👤 Publisher: *${created.publisher}*`, `👁️ Visibilidade: ${formatVisibilityLabel(created.visibility)}`],
585
+ nextSteps: [`Responda uma figurinha e use: \`${commandPrefix}pack add ${created.pack_key}\`.`, `Para conferir: \`${commandPrefix}pack info ${created.pack_key}\`.`],
695
586
  footer: ['💡 Dica: use packs por tema para organizar e enviar mais rápido.'],
696
587
  }),
697
588
  });
@@ -759,9 +650,7 @@ export async function handlePackCommand({
759
650
  title: '👤 *Publisher atualizado!*',
760
651
  explanation: ['O publisher deste pack foi ajustado e já aparece nas informações.'],
761
652
  details: [`📦 Pack: *${updated.name}*`, `👤 Publisher: *${updated.publisher}*`, `🆔 ID: \`${updated.pack_key}\``],
762
- nextSteps: [
763
- `Se quiser, ajuste a descrição: \`${commandPrefix}pack setdesc ${updated.pack_key} "Nova descrição"\`.`,
764
- ],
653
+ nextSteps: [`Se quiser, ajuste a descrição: \`${commandPrefix}pack setdesc ${updated.pack_key} "Nova descrição"\`.`],
765
654
  }),
766
655
  });
767
656
  return;
@@ -780,10 +669,7 @@ export async function handlePackCommand({
780
669
  text: buildActionMessage({
781
670
  title: '📝 *Descrição atualizada!*',
782
671
  explanation: ['A descrição ajuda a identificar o tema do pack.'],
783
- details: [
784
- `📦 Pack: *${updated.name}*`,
785
- description ? `📝 Descrição: "${updated.description}"` : '🧹 Descrição removida.',
786
- ],
672
+ details: [`📦 Pack: *${updated.name}*`, description ? `📝 Descrição: "${updated.description}"` : '🧹 Descrição removida.'],
787
673
  nextSteps: [`Ver como ficou: \`${commandPrefix}pack info ${updated.pack_key}\`.`],
788
674
  }),
789
675
  });
@@ -795,10 +681,7 @@ export async function handlePackCommand({
795
681
  const asset = await resolveStickerFromCommandContext({ messageInfo, ownerJid });
796
682
 
797
683
  if (!asset) {
798
- throw new StickerPackError(
799
- STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND,
800
- 'Não encontrei uma figurinha para definir como capa.',
801
- );
684
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND, 'Não encontrei uma figurinha para definir como capa.');
802
685
  }
803
686
 
804
687
  const updated = await stickerPackService.setPackCover({
@@ -829,10 +712,7 @@ export async function handlePackCommand({
829
712
 
830
713
  const asset = await resolveStickerFromCommandContext({ messageInfo, ownerJid });
831
714
  if (!asset) {
832
- throw new StickerPackError(
833
- STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND,
834
- 'Não encontrei uma figurinha para adicionar.',
835
- );
715
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND, 'Não encontrei uma figurinha para adicionar.');
836
716
  }
837
717
 
838
718
  const updated = await stickerPackService.addStickerToPack({
@@ -851,15 +731,8 @@ export async function handlePackCommand({
851
731
  text: buildActionMessage({
852
732
  title: '➕ *Figurinha adicionada!*',
853
733
  explanation: ['Item adicionado com sucesso ao pack selecionado.'],
854
- details: [
855
- `📦 Pack: *${updated.name}*`,
856
- `🧩 Itens: *${updated.items.length}/${MAX_PACK_ITEMS}*`,
857
- `🆔 ID: \`${updated.pack_key}\``,
858
- ],
859
- nextSteps: [
860
- `Definir como capa: responda a figurinha e use \`${commandPrefix}pack setcover ${updated.pack_key}\`.`,
861
- `Ver lista completa: \`${commandPrefix}pack info ${updated.pack_key}\`.`,
862
- ],
734
+ details: [`📦 Pack: *${updated.name}*`, `🧩 Itens: *${updated.items.length}/${MAX_PACK_ITEMS}*`, `🆔 ID: \`${updated.pack_key}\``],
735
+ nextSteps: [`Definir como capa: responda a figurinha e use \`${commandPrefix}pack setcover ${updated.pack_key}\`.`, `Ver lista completa: \`${commandPrefix}pack info ${updated.pack_key}\`.`],
863
736
  }),
864
737
  });
865
738
  return;
@@ -883,11 +756,7 @@ export async function handlePackCommand({
883
756
  text: buildActionMessage({
884
757
  title: '🗑️ *Figurinha removida!*',
885
758
  explanation: ['Remoção concluída e o pack foi reordenado automaticamente.'],
886
- details: [
887
- `📦 Pack: *${result.pack.name}*`,
888
- `🔢 Item removido: figurinha #${result.removed.position}`,
889
- `🧩 Itens: *${result.pack.items.length}/${MAX_PACK_ITEMS}*`,
890
- ],
759
+ details: [`📦 Pack: *${result.pack.name}*`, `🔢 Item removido: figurinha #${result.removed.position}`, `🧩 Itens: *${result.pack.items.length}/${MAX_PACK_ITEMS}*`],
891
760
  nextSteps: [`Conferir: \`${commandPrefix}pack info ${result.pack.pack_key}\`.`],
892
761
  }),
893
762
  });
@@ -942,10 +811,7 @@ export async function handlePackCommand({
942
811
  title: '🧬 *Clone criado!*',
943
812
  explanation: ['O pack foi duplicado com as mesmas figurinhas e configurações.'],
944
813
  details: [`📦 Novo pack: *${cloned.name}*`, `🆔 ID: \`${cloned.pack_key}\``],
945
- nextSteps: [
946
- `Renomear: \`${commandPrefix}pack rename ${cloned.pack_key} novonome\`.`,
947
- `Enviar: \`${commandPrefix}pack send ${cloned.pack_key}\`.`,
948
- ],
814
+ nextSteps: [`Renomear: \`${commandPrefix}pack rename ${cloned.pack_key} novonome\`.`, `Enviar: \`${commandPrefix}pack send ${cloned.pack_key}\`.`],
949
815
  }),
950
816
  });
951
817
  return;
@@ -988,11 +854,7 @@ export async function handlePackCommand({
988
854
  text: buildActionMessage({
989
855
  title: '🌐 *Visibilidade atualizada!*',
990
856
  explanation: ['A configuração de privacidade foi aplicada ao pack.'],
991
- details: [
992
- `📦 Pack: *${updated.name}*`,
993
- `👁️ Visibilidade: ${formatVisibilityLabel(updated.visibility)}`,
994
- `🆔 ID: \`${updated.pack_key}\``,
995
- ],
857
+ details: [`📦 Pack: *${updated.name}*`, `👁️ Visibilidade: ${formatVisibilityLabel(updated.visibility)}`, `🆔 ID: \`${updated.pack_key}\``],
996
858
  nextSteps: [`Compartilhar/enviar: \`${commandPrefix}pack send ${updated.pack_key}\`.`],
997
859
  }),
998
860
  });
@@ -1020,15 +882,8 @@ export async function handlePackCommand({
1020
882
  text: buildActionMessage({
1021
883
  title: '📤 *Pack enviado!*',
1022
884
  explanation: ['Enviei no formato nativo (melhor experiência e compatibilidade).'],
1023
- details: [
1024
- `📦 Pack: *${packDetails.name}*`,
1025
- `🆔 ID: \`${packDetails.pack_key}\``,
1026
- `🧩 Enviadas: *${sendResult.sentCount} figurinha(s)*`,
1027
- ],
1028
- nextSteps: [
1029
- `Ver detalhes: \`${commandPrefix}pack info ${packDetails.pack_key}\`.`,
1030
- `Editar: \`${commandPrefix}pack add ${packDetails.pack_key}\` ou \`${commandPrefix}pack remove ${packDetails.pack_key} <item>\`.`,
1031
- ],
885
+ details: [`📦 Pack: *${packDetails.name}*`, `🆔 ID: \`${packDetails.pack_key}\``, `🧩 Enviadas: *${sendResult.sentCount} figurinha(s)*`],
886
+ nextSteps: [`Ver detalhes: \`${commandPrefix}pack info ${packDetails.pack_key}\`.`, `Editar: \`${commandPrefix}pack add ${packDetails.pack_key}\` ou \`${commandPrefix}pack remove ${packDetails.pack_key} <item>\`.`],
1032
887
  }),
1033
888
  });
1034
889
  } else {
@@ -1039,19 +894,9 @@ export async function handlePackCommand({
1039
894
  expirationMessage,
1040
895
  text: buildActionMessage({
1041
896
  title: 'ℹ️ *Pack enviado em modo compatível.*',
1042
- explanation: [
1043
- `O cliente não aceitou o formato nativo para *${packDetails.name}*.`,
1044
- 'Enviei em modo compatível (prévia + figurinhas individuais).',
1045
- ],
1046
- details: [
1047
- `📦 Pack: *${packDetails.name}*`,
1048
- `🧩 Progresso: *${sendResult.sentCount}/${sendResult.total}*`,
1049
- sendResult.nativeError ? `🛠 Detalhe técnico: ${sendResult.nativeError}` : null,
1050
- ],
1051
- nextSteps: [
1052
- `Você pode continuar gerenciando: \`${commandPrefix}pack info ${packDetails.pack_key}\`.`,
1053
- `Para tentar novamente no formato nativo: \`${commandPrefix}pack send ${packDetails.pack_key}\` mais tarde.`,
1054
- ],
897
+ explanation: [`O cliente não aceitou o formato nativo para *${packDetails.name}*.`, 'Enviei em modo compatível (prévia + figurinhas individuais).'],
898
+ details: [`📦 Pack: *${packDetails.name}*`, `🧩 Progresso: *${sendResult.sentCount}/${sendResult.total}*`, sendResult.nativeError ? `🛠 Detalhe técnico: ${sendResult.nativeError}` : null],
899
+ nextSteps: [`Você pode continuar gerenciando: \`${commandPrefix}pack info ${packDetails.pack_key}\`.`, `Para tentar novamente no formato nativo: \`${commandPrefix}pack send ${packDetails.pack_key}\` mais tarde.`],
1055
900
  }),
1056
901
  });
1057
902
  }