@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.
- package/README.md +13 -13
- package/app/config/adminIdentity.js +1 -3
- package/app/connection/socketController.js +10 -20
- package/app/controllers/messageController.js +7 -28
- package/app/modules/aiModule/catCommand.js +29 -192
- package/app/modules/broadcastModule/noticeCommand.js +28 -97
- package/app/modules/gameModule/diceCommand.js +6 -32
- package/app/modules/playModule/playCommand.js +57 -258
- package/app/modules/quoteModule/quoteCommand.js +2 -4
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
- package/app/modules/statsModule/noMessageCommand.js +16 -84
- package/app/modules/statsModule/rankingCommand.js +5 -25
- package/app/modules/statsModule/rankingCommon.js +1 -9
- package/app/modules/stickerModule/convertToWebp.js +4 -27
- package/app/modules/stickerModule/stickerCommand.js +13 -24
- package/app/modules/stickerModule/stickerTextCommand.js +13 -25
- package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
- package/app/modules/stickerPackModule/domainEvents.js +2 -11
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
- package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
- package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +40 -39
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
- package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
- package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
- package/app/modules/stickerPackModule/stickerPackService.js +50 -115
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
- package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
- package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
- package/app/modules/systemMetricsModule/pingCommand.js +9 -39
- package/app/modules/tiktokModule/tiktokCommand.js +17 -109
- package/app/modules/userModule/userCommand.js +2 -88
- package/app/observability/metrics.js +5 -16
- package/app/services/captchaService.js +1 -6
- package/app/services/dbWriteQueue.js +3 -18
- package/app/services/featureFlagService.js +2 -8
- package/app/services/newsBroadcastService.js +0 -1
- package/app/services/queueUtils.js +2 -4
- package/app/services/whatsappLoginLinkService.js +7 -9
- package/app/store/premiumUserStore.js +1 -2
- package/app/utils/antiLink/antiLinkModule.js +3 -233
- package/app/utils/logger/loggerModule.js +9 -34
- package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
- package/database/index.js +1 -0
- package/database/init.js +1 -8
- package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
- package/docker-compose.yml +27 -27
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
- package/docs/seo/satellite-page-template.md +2 -0
- package/docs/seo/satellite-pages-phase1.json +40 -177
- package/eslint.config.js +2 -15
- package/index.js +8 -36
- package/ml/clip_classifier/README.md +4 -6
- package/observability/alert-rules.yml +12 -12
- package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
- package/package.json +6 -3
- package/public/api-docs/index.html +220 -193
- package/public/bot-whatsapp-para-grupo/index.html +291 -261
- package/public/bot-whatsapp-sem-programar/index.html +291 -261
- package/public/comandos/index.html +421 -406
- package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
- package/public/como-criar-comandos-whatsapp/index.html +291 -261
- package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
- package/public/como-moderar-grupo-whatsapp/index.html +291 -261
- package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
- package/public/css/github-project-panel.css +13 -8
- package/public/css/stickers-admin.css +25 -9
- package/public/css/styles.css +23 -16
- package/public/index.html +1106 -993
- package/public/js/apps/apiDocsApp.js +17 -167
- package/public/js/apps/createPackApp.js +69 -332
- package/public/js/apps/homeApp.js +274 -101
- package/public/js/apps/loginApp.js +3 -12
- package/public/js/apps/stickersAdminApp.js +190 -181
- package/public/js/apps/stickersApp.js +482 -1411
- package/public/js/apps/userApp.js +217 -1
- package/public/js/catalog.js +11 -74
- package/public/js/github-panel/components/ErrorState.js +1 -8
- package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
- package/public/js/github-panel/components/SkeletonPanel.js +1 -11
- package/public/js/github-panel/components/StatCard.js +1 -7
- package/public/js/github-panel/vendor/react.js +1 -9
- package/public/js/runtime/react-runtime.js +1 -9
- package/public/licenca/index.html +200 -86
- package/public/login/index.html +315 -325
- package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
- package/public/stickers/admin/index.html +14 -19
- package/public/stickers/create/index.html +39 -44
- package/public/stickers/index.html +96 -107
- package/public/termos-de-uso/index.html +369 -122
- package/public/user/index.html +527 -350
- package/scripts/cache-bust.mjs +5 -24
- package/scripts/generate-seo-satellite-pages.mjs +10 -13
- package/scripts/run-prettier-all.mjs +25 -0
- package/scripts/sticker-catalog-loadtest.mjs +13 -11
- package/scripts/sticker-worker-task.mjs +1 -4
- package/scripts/sync-readme-snapshot.mjs +3 -2
- package/server/auth/googleWebAuth/googleWebAuthService.js +614 -0
- package/server/controllers/stickerCatalogController.js +297 -632
- package/server/http/httpServer.js +2 -10
- package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
- package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
- package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
- package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
- package/server/routes/stickerCatalog/catalogRouter.js +11 -13
|
@@ -116,237 +116,8 @@ const EDGE_PUNCTUATION_CHARS = new Set([',', '!', '?', ';', ':', ')', '(', '[',
|
|
|
116
116
|
const TOKEN_SEPARATOR_CHARS = new Set([',', ';', '|']);
|
|
117
117
|
const HOST_TERMINATORS = new Set(['/', '?', '#', ':', '\\', ',', ';']);
|
|
118
118
|
const URL_HINTS = ['https://', 'http://', 'www.'];
|
|
119
|
-
const STRICT_TLD_SUFFIXES = new Set([
|
|
120
|
-
|
|
121
|
-
'net',
|
|
122
|
-
'org',
|
|
123
|
-
'edu',
|
|
124
|
-
'gov',
|
|
125
|
-
'mil',
|
|
126
|
-
'io',
|
|
127
|
-
'me',
|
|
128
|
-
'tv',
|
|
129
|
-
'co',
|
|
130
|
-
'cc',
|
|
131
|
-
'gg',
|
|
132
|
-
'gl',
|
|
133
|
-
'ly',
|
|
134
|
-
'so',
|
|
135
|
-
'br',
|
|
136
|
-
'us',
|
|
137
|
-
'uk',
|
|
138
|
-
'eu',
|
|
139
|
-
'de',
|
|
140
|
-
'fr',
|
|
141
|
-
'es',
|
|
142
|
-
'pt',
|
|
143
|
-
'it',
|
|
144
|
-
'nl',
|
|
145
|
-
'be',
|
|
146
|
-
'ch',
|
|
147
|
-
'at',
|
|
148
|
-
'se',
|
|
149
|
-
'no',
|
|
150
|
-
'fi',
|
|
151
|
-
'dk',
|
|
152
|
-
'ie',
|
|
153
|
-
'pl',
|
|
154
|
-
'cz',
|
|
155
|
-
'sk',
|
|
156
|
-
'hu',
|
|
157
|
-
'ro',
|
|
158
|
-
'bg',
|
|
159
|
-
'gr',
|
|
160
|
-
'ru',
|
|
161
|
-
'ua',
|
|
162
|
-
'tr',
|
|
163
|
-
'il',
|
|
164
|
-
'ae',
|
|
165
|
-
'sa',
|
|
166
|
-
'qa',
|
|
167
|
-
'eg',
|
|
168
|
-
'ma',
|
|
169
|
-
'tn',
|
|
170
|
-
'dz',
|
|
171
|
-
'za',
|
|
172
|
-
'ng',
|
|
173
|
-
'ke',
|
|
174
|
-
'gh',
|
|
175
|
-
'in',
|
|
176
|
-
'pk',
|
|
177
|
-
'bd',
|
|
178
|
-
'lk',
|
|
179
|
-
'cn',
|
|
180
|
-
'jp',
|
|
181
|
-
'kr',
|
|
182
|
-
'tw',
|
|
183
|
-
'hk',
|
|
184
|
-
'sg',
|
|
185
|
-
'my',
|
|
186
|
-
'th',
|
|
187
|
-
'vn',
|
|
188
|
-
'ph',
|
|
189
|
-
'id',
|
|
190
|
-
'au',
|
|
191
|
-
'nz',
|
|
192
|
-
'ca',
|
|
193
|
-
'mx',
|
|
194
|
-
'ar',
|
|
195
|
-
'cl',
|
|
196
|
-
'pe',
|
|
197
|
-
'uy',
|
|
198
|
-
'py',
|
|
199
|
-
'bo',
|
|
200
|
-
'ec',
|
|
201
|
-
've',
|
|
202
|
-
'do',
|
|
203
|
-
'cu',
|
|
204
|
-
'pa',
|
|
205
|
-
'cr',
|
|
206
|
-
'gt',
|
|
207
|
-
'hn',
|
|
208
|
-
'ni',
|
|
209
|
-
'sv',
|
|
210
|
-
'pr',
|
|
211
|
-
'com.br',
|
|
212
|
-
'net.br',
|
|
213
|
-
'org.br',
|
|
214
|
-
'gov.br',
|
|
215
|
-
'edu.br',
|
|
216
|
-
'jus.br',
|
|
217
|
-
'mil.br',
|
|
218
|
-
'co.uk',
|
|
219
|
-
'org.uk',
|
|
220
|
-
'gov.uk',
|
|
221
|
-
'ac.uk',
|
|
222
|
-
'co.jp',
|
|
223
|
-
'ne.jp',
|
|
224
|
-
'or.jp',
|
|
225
|
-
'go.jp',
|
|
226
|
-
'ac.jp',
|
|
227
|
-
'com.au',
|
|
228
|
-
'net.au',
|
|
229
|
-
'org.au',
|
|
230
|
-
'edu.au',
|
|
231
|
-
'gov.au',
|
|
232
|
-
'com.mx',
|
|
233
|
-
'com.ar',
|
|
234
|
-
'com.co',
|
|
235
|
-
'com.pe',
|
|
236
|
-
'com.tr',
|
|
237
|
-
'com.sg',
|
|
238
|
-
'com.my',
|
|
239
|
-
'com.ph',
|
|
240
|
-
'co.in',
|
|
241
|
-
'firm.in',
|
|
242
|
-
'net.in',
|
|
243
|
-
'org.in',
|
|
244
|
-
'gen.in',
|
|
245
|
-
'ind.in',
|
|
246
|
-
'co.id',
|
|
247
|
-
'or.id',
|
|
248
|
-
'go.id',
|
|
249
|
-
'web.id',
|
|
250
|
-
'co.za',
|
|
251
|
-
'org.za',
|
|
252
|
-
'net.za',
|
|
253
|
-
'com.ng',
|
|
254
|
-
'com.gh',
|
|
255
|
-
'com.eg',
|
|
256
|
-
'com.sa',
|
|
257
|
-
'com.qa',
|
|
258
|
-
'com.ae',
|
|
259
|
-
'page.link',
|
|
260
|
-
'g.page',
|
|
261
|
-
]);
|
|
262
|
-
const EXTRA_TLD_SUFFIXES = new Set([
|
|
263
|
-
'ai',
|
|
264
|
-
'app',
|
|
265
|
-
'dev',
|
|
266
|
-
'xyz',
|
|
267
|
-
'site',
|
|
268
|
-
'online',
|
|
269
|
-
'store',
|
|
270
|
-
'shop',
|
|
271
|
-
'blog',
|
|
272
|
-
'tech',
|
|
273
|
-
'cloud',
|
|
274
|
-
'digital',
|
|
275
|
-
'live',
|
|
276
|
-
'media',
|
|
277
|
-
'news',
|
|
278
|
-
'one',
|
|
279
|
-
'top',
|
|
280
|
-
'club',
|
|
281
|
-
'vip',
|
|
282
|
-
'fun',
|
|
283
|
-
'games',
|
|
284
|
-
'game',
|
|
285
|
-
'space',
|
|
286
|
-
'world',
|
|
287
|
-
'today',
|
|
288
|
-
'agency',
|
|
289
|
-
'email',
|
|
290
|
-
'center',
|
|
291
|
-
'company',
|
|
292
|
-
'group',
|
|
293
|
-
'solutions',
|
|
294
|
-
'systems',
|
|
295
|
-
'services',
|
|
296
|
-
'network',
|
|
297
|
-
'social',
|
|
298
|
-
'design',
|
|
299
|
-
'studio',
|
|
300
|
-
'photo',
|
|
301
|
-
'video',
|
|
302
|
-
'audio',
|
|
303
|
-
'music',
|
|
304
|
-
'art',
|
|
305
|
-
'wiki',
|
|
306
|
-
'finance',
|
|
307
|
-
'capital',
|
|
308
|
-
'money',
|
|
309
|
-
'loans',
|
|
310
|
-
'insurance',
|
|
311
|
-
'legal',
|
|
312
|
-
'law',
|
|
313
|
-
'health',
|
|
314
|
-
'care',
|
|
315
|
-
'clinic',
|
|
316
|
-
'dental',
|
|
317
|
-
'academy',
|
|
318
|
-
'school',
|
|
319
|
-
'college',
|
|
320
|
-
'university',
|
|
321
|
-
'education',
|
|
322
|
-
'training',
|
|
323
|
-
'support',
|
|
324
|
-
'chat',
|
|
325
|
-
'forum',
|
|
326
|
-
'community',
|
|
327
|
-
'events',
|
|
328
|
-
'travel',
|
|
329
|
-
'tours',
|
|
330
|
-
'hotel',
|
|
331
|
-
'homes',
|
|
332
|
-
'house',
|
|
333
|
-
'auto',
|
|
334
|
-
'cars',
|
|
335
|
-
'bike',
|
|
336
|
-
'food',
|
|
337
|
-
'restaurant',
|
|
338
|
-
'cafe',
|
|
339
|
-
'bar',
|
|
340
|
-
'pizza',
|
|
341
|
-
'delivery',
|
|
342
|
-
'fashion',
|
|
343
|
-
'beauty',
|
|
344
|
-
'style',
|
|
345
|
-
'fit',
|
|
346
|
-
'fitness',
|
|
347
|
-
'sports',
|
|
348
|
-
'download',
|
|
349
|
-
]);
|
|
119
|
+
const STRICT_TLD_SUFFIXES = new Set(['com', 'net', 'org', 'edu', 'gov', 'mil', 'io', 'me', 'tv', 'co', 'cc', 'gg', 'gl', 'ly', 'so', 'br', 'us', 'uk', 'eu', 'de', 'fr', 'es', 'pt', 'it', 'nl', 'be', 'ch', 'at', 'se', 'no', 'fi', 'dk', 'ie', 'pl', 'cz', 'sk', 'hu', 'ro', 'bg', 'gr', 'ru', 'ua', 'tr', 'il', 'ae', 'sa', 'qa', 'eg', 'ma', 'tn', 'dz', 'za', 'ng', 'ke', 'gh', 'in', 'pk', 'bd', 'lk', 'cn', 'jp', 'kr', 'tw', 'hk', 'sg', 'my', 'th', 'vn', 'ph', 'id', 'au', 'nz', 'ca', 'mx', 'ar', 'cl', 'pe', 'uy', 'py', 'bo', 'ec', 've', 'do', 'cu', 'pa', 'cr', 'gt', 'hn', 'ni', 'sv', 'pr', 'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br', 'jus.br', 'mil.br', 'co.uk', 'org.uk', 'gov.uk', 'ac.uk', 'co.jp', 'ne.jp', 'or.jp', 'go.jp', 'ac.jp', 'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'com.mx', 'com.ar', 'com.co', 'com.pe', 'com.tr', 'com.sg', 'com.my', 'com.ph', 'co.in', 'firm.in', 'net.in', 'org.in', 'gen.in', 'ind.in', 'co.id', 'or.id', 'go.id', 'web.id', 'co.za', 'org.za', 'net.za', 'com.ng', 'com.gh', 'com.eg', 'com.sa', 'com.qa', 'com.ae', 'page.link', 'g.page']);
|
|
120
|
+
const EXTRA_TLD_SUFFIXES = new Set(['ai', 'app', 'dev', 'xyz', 'site', 'online', 'store', 'shop', 'blog', 'tech', 'cloud', 'digital', 'live', 'media', 'news', 'one', 'top', 'club', 'vip', 'fun', 'games', 'game', 'space', 'world', 'today', 'agency', 'email', 'center', 'company', 'group', 'solutions', 'systems', 'services', 'network', 'social', 'design', 'studio', 'photo', 'video', 'audio', 'music', 'art', 'wiki', 'finance', 'capital', 'money', 'loans', 'insurance', 'legal', 'law', 'health', 'care', 'clinic', 'dental', 'academy', 'school', 'college', 'university', 'education', 'training', 'support', 'chat', 'forum', 'community', 'events', 'travel', 'tours', 'hotel', 'homes', 'house', 'auto', 'cars', 'bike', 'food', 'restaurant', 'cafe', 'bar', 'pizza', 'delivery', 'fashion', 'beauty', 'style', 'fit', 'fitness', 'sports', 'download']);
|
|
350
121
|
const ANY_TLD_SUFFIXES = new Set([...STRICT_TLD_SUFFIXES, ...EXTRA_TLD_SUFFIXES]);
|
|
351
122
|
|
|
352
123
|
/**
|
|
@@ -697,8 +468,7 @@ const normalizeAllowedDomains = (allowedDomains = []) => {
|
|
|
697
468
|
* @param {string[]} normalizedAllowedDomains
|
|
698
469
|
* @returns {boolean}
|
|
699
470
|
*/
|
|
700
|
-
const isDomainAllowed = (domain, normalizedAllowedDomains) =>
|
|
701
|
-
normalizedAllowedDomains.some((allowedDomain) => domain === allowedDomain || domain.endsWith(`.${allowedDomain}`));
|
|
471
|
+
const isDomainAllowed = (domain, normalizedAllowedDomains) => normalizedAllowedDomains.some((allowedDomain) => domain === allowedDomain || domain.endsWith(`.${allowedDomain}`));
|
|
702
472
|
|
|
703
473
|
/**
|
|
704
474
|
* Monta a lista final de domínios permitidos (redes conhecidas + personalizados).
|
|
@@ -76,16 +76,7 @@ const consoleFormat = winston.format.combine(
|
|
|
76
76
|
winston.format.colorize({ all: true }),
|
|
77
77
|
winston.format.splat(),
|
|
78
78
|
winston.format.metadata({
|
|
79
|
-
fillExcept: [
|
|
80
|
-
'message',
|
|
81
|
-
'level',
|
|
82
|
-
'timestamp',
|
|
83
|
-
'label',
|
|
84
|
-
'service',
|
|
85
|
-
'instanceId',
|
|
86
|
-
'environment',
|
|
87
|
-
'stack',
|
|
88
|
-
],
|
|
79
|
+
fillExcept: ['message', 'level', 'timestamp', 'label', 'service', 'instanceId', 'environment', 'stack'],
|
|
89
80
|
}),
|
|
90
81
|
winston.format.printf((info) => {
|
|
91
82
|
const { timestamp, level, message, metadata, stack } = info;
|
|
@@ -96,10 +87,7 @@ const consoleFormat = winston.format.combine(
|
|
|
96
87
|
delete metaToPrint.label;
|
|
97
88
|
}
|
|
98
89
|
|
|
99
|
-
const metaPart =
|
|
100
|
-
Object.keys(metaToPrint).length > 0
|
|
101
|
-
? ` ${util.inspect(metaToPrint, { colors: true, depth: 2 })}`
|
|
102
|
-
: '';
|
|
90
|
+
const metaPart = Object.keys(metaToPrint).length > 0 ? ` ${util.inspect(metaToPrint, { colors: true, depth: 2 })}` : '';
|
|
103
91
|
|
|
104
92
|
const servicePart = info.service ? ` [${info.service}]` : '';
|
|
105
93
|
const instancePart = info.instanceId ? ` [${info.instanceId}]` : '';
|
|
@@ -180,13 +168,9 @@ function ensureLogDirectoryExists() {
|
|
|
180
168
|
try {
|
|
181
169
|
fs.mkdirSync(logDir, { recursive: true, mode: dirMode });
|
|
182
170
|
|
|
183
|
-
console.log(
|
|
184
|
-
`[ LoggerSetup ] Diretório de log garantido: '${logDir}' (modo ${dirMode.toString(8)})`,
|
|
185
|
-
);
|
|
171
|
+
console.log(`[ LoggerSetup ] Diretório de log garantido: '${logDir}' (modo ${dirMode.toString(8)})`);
|
|
186
172
|
} catch (error) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
`Falha na configuração do Logger: Não foi possível acessar/criar o diretório de log '${logDir}'. Erro original: ${error.message}`,
|
|
189
|
-
);
|
|
173
|
+
throw new Error(`Falha na configuração do Logger: Não foi possível acessar/criar o diretório de log '${logDir}'. Erro original: ${error.message}`);
|
|
190
174
|
}
|
|
191
175
|
}
|
|
192
176
|
|
|
@@ -199,8 +183,7 @@ const createLoggerInstance = (overrideOptions = {}) => {
|
|
|
199
183
|
if (overrideOptions.transports) {
|
|
200
184
|
configuredTransports = overrideOptions.transports;
|
|
201
185
|
} else {
|
|
202
|
-
const transportDefinitions =
|
|
203
|
-
overrideOptions.transportDefinitions || getDefaultTransportDefinitions(effectiveLevel);
|
|
186
|
+
const transportDefinitions = overrideOptions.transportDefinitions || getDefaultTransportDefinitions(effectiveLevel);
|
|
204
187
|
configuredTransports = transportDefinitions
|
|
205
188
|
.map((def) => {
|
|
206
189
|
try {
|
|
@@ -210,16 +193,11 @@ const createLoggerInstance = (overrideOptions = {}) => {
|
|
|
210
193
|
case 'dailyRotateFile':
|
|
211
194
|
return new DailyRotateFile(def.options);
|
|
212
195
|
default:
|
|
213
|
-
console.warn(
|
|
214
|
-
`[ LoggerSetup ] Tipo de transporte desconhecido: ${def.type}. Pulando.`,
|
|
215
|
-
);
|
|
196
|
+
console.warn(`[ LoggerSetup ] Tipo de transporte desconhecido: ${def.type}. Pulando.`);
|
|
216
197
|
return null;
|
|
217
198
|
}
|
|
218
199
|
} catch (error) {
|
|
219
|
-
console.error(
|
|
220
|
-
`[ LoggerSetup ] Falha ao criar transporte tipo ${def.type}: ${error.message}`,
|
|
221
|
-
error,
|
|
222
|
-
);
|
|
200
|
+
console.error(`[ LoggerSetup ] Falha ao criar transporte tipo ${def.type}: ${error.message}`, error);
|
|
223
201
|
return null;
|
|
224
202
|
}
|
|
225
203
|
})
|
|
@@ -234,8 +212,7 @@ const createLoggerInstance = (overrideOptions = {}) => {
|
|
|
234
212
|
|
|
235
213
|
const defaultMeta = { ...baseDefaultMeta, ...(overrideOptions.defaultMeta || {}) };
|
|
236
214
|
|
|
237
|
-
const loggerFormat =
|
|
238
|
-
overrideOptions.format || winston.format.combine(winston.format.errors({ stack: true }));
|
|
215
|
+
const loggerFormat = overrideOptions.format || winston.format.combine(winston.format.errors({ stack: true }));
|
|
239
216
|
|
|
240
217
|
const loggerInstance = winston.createLogger({
|
|
241
218
|
level: effectiveLevel,
|
|
@@ -250,9 +227,7 @@ const createLoggerInstance = (overrideOptions = {}) => {
|
|
|
250
227
|
console.error('Erro ocorrido dentro do Winston Logger:', error);
|
|
251
228
|
});
|
|
252
229
|
|
|
253
|
-
console.log(
|
|
254
|
-
`[ LoggerSetup ] Instância do Logger criada. Nível: ${effectiveLevel}, Env: ${NODE_ENV}, Instância: ${INSTANCE_ID}, Serviço: ${ECOSYSTEM_NAME}`,
|
|
255
|
-
);
|
|
230
|
+
console.log(`[ LoggerSetup ] Instância do Logger criada. Nível: ${effectiveLevel}, Env: ${NODE_ENV}, Instância: ${INSTANCE_ID}, Serviço: ${ECOSYSTEM_NAME}`);
|
|
256
231
|
|
|
257
232
|
return loggerInstance;
|
|
258
233
|
};
|
|
@@ -45,10 +45,7 @@ export const getSystemMetrics = () => {
|
|
|
45
45
|
const uptime = formatDuration(processUptimeSeconds);
|
|
46
46
|
const systemUptime = formatDuration(systemUptimeSeconds);
|
|
47
47
|
const loadAverage = os.loadavg();
|
|
48
|
-
const cpuSpeedAverage =
|
|
49
|
-
totalCpus > 0
|
|
50
|
-
? Math.round(cpus.reduce((sum, cpu) => sum + (cpu.speed || 0), 0) / totalCpus)
|
|
51
|
-
: 0;
|
|
48
|
+
const cpuSpeedAverage = totalCpus > 0 ? Math.round(cpus.reduce((sum, cpu) => sum + (cpu.speed || 0), 0) / totalCpus) : 0;
|
|
52
49
|
const memoryUsage = process.memoryUsage();
|
|
53
50
|
|
|
54
51
|
return {
|
package/database/index.js
CHANGED
|
@@ -104,6 +104,7 @@ export const TABLES = {
|
|
|
104
104
|
SEMANTIC_THEME_SUGGESTION_CACHE: 'semantic_theme_suggestion_cache',
|
|
105
105
|
STICKER_PACK_ENGAGEMENT: 'sticker_pack_engagement',
|
|
106
106
|
STICKER_PACK_INTERACTION_EVENT: 'sticker_pack_interaction_event',
|
|
107
|
+
WEB_VISIT_EVENT: 'web_visit_event',
|
|
107
108
|
STICKER_PACK_SCORE_SNAPSHOT: 'sticker_pack_score_snapshot',
|
|
108
109
|
STICKER_ASSET_REPROCESS_QUEUE: 'sticker_asset_reprocess_queue',
|
|
109
110
|
STICKER_WORKER_TASK_QUEUE: 'sticker_worker_task_queue',
|
package/database/init.js
CHANGED
|
@@ -474,14 +474,7 @@ export default async function initializeDatabase() {
|
|
|
474
474
|
await connection.changeUser({ database: dbToCreate });
|
|
475
475
|
|
|
476
476
|
// Cria todas as tabelas em paralelo
|
|
477
|
-
await Promise.all([
|
|
478
|
-
connection.query(createMessagesTableSQL),
|
|
479
|
-
connection.query(createChatsTableSQL),
|
|
480
|
-
connection.query(createGroupsMetadataTableSQL),
|
|
481
|
-
connection.query(createGroupConfigsTableSQL),
|
|
482
|
-
connection.query(createLidMapTableSQL),
|
|
483
|
-
connection.query(createStickerAssetTableSQL),
|
|
484
|
-
]);
|
|
477
|
+
await Promise.all([connection.query(createMessagesTableSQL), connection.query(createChatsTableSQL), connection.query(createGroupsMetadataTableSQL), connection.query(createGroupConfigsTableSQL), connection.query(createLidMapTableSQL), connection.query(createStickerAssetTableSQL)]);
|
|
485
478
|
|
|
486
479
|
// Ordem importa por conta das FKs: pack depende de asset e item depende de pack+asset.
|
|
487
480
|
await connection.query(createStickerPackTableSQL);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS web_visit_event (
|
|
2
|
+
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|
3
|
+
visitor_key VARCHAR(80) NOT NULL,
|
|
4
|
+
session_key VARCHAR(80) NOT NULL,
|
|
5
|
+
page_path VARCHAR(255) NOT NULL,
|
|
6
|
+
referrer VARCHAR(1024) NULL,
|
|
7
|
+
user_agent VARCHAR(512) NULL,
|
|
8
|
+
source VARCHAR(32) NOT NULL DEFAULT 'web',
|
|
9
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
10
|
+
INDEX idx_web_visit_created_at (created_at),
|
|
11
|
+
INDEX idx_web_visit_page_created (page_path, created_at),
|
|
12
|
+
INDEX idx_web_visit_visitor_created (visitor_key, created_at),
|
|
13
|
+
INDEX idx_web_visit_session_created (session_key, created_at),
|
|
14
|
+
INDEX idx_web_visit_source_created (source, created_at)
|
|
15
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
package/docker-compose.yml
CHANGED
|
@@ -3,17 +3,17 @@ services:
|
|
|
3
3
|
image: prom/prometheus:${PROMETHEUS_IMAGE_TAG:-v2.52.0}
|
|
4
4
|
restart: unless-stopped
|
|
5
5
|
command:
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
6
|
+
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
7
|
+
- '--storage.tsdb.retention.time=${PROMETHEUS_RETENTION_TIME:-15d}'
|
|
8
|
+
- '--storage.tsdb.retention.size=${PROMETHEUS_RETENTION_SIZE:-20GB}'
|
|
9
9
|
volumes:
|
|
10
10
|
- ${PROMETHEUS_CONFIG_PATH:-./observability/prometheus.yml}:/etc/prometheus/prometheus.yml:ro
|
|
11
11
|
- ${PROMETHEUS_ALERT_RULES_PATH:-./observability/alert-rules.yml}:/etc/prometheus/alert-rules.yml:ro
|
|
12
12
|
- prometheus_data:/prometheus
|
|
13
13
|
ports:
|
|
14
|
-
-
|
|
14
|
+
- '${PROMETHEUS_PORT:-9090}:9090'
|
|
15
15
|
extra_hosts:
|
|
16
|
-
-
|
|
16
|
+
- 'host.docker.internal:host-gateway'
|
|
17
17
|
|
|
18
18
|
grafana:
|
|
19
19
|
image: grafana/grafana:${GRAFANA_IMAGE_TAG:-10.4.3}
|
|
@@ -30,7 +30,7 @@ services:
|
|
|
30
30
|
- ${GRAFANA_PROVISIONING_PATH:-./observability/grafana/provisioning}:/etc/grafana/provisioning:ro
|
|
31
31
|
- ${GRAFANA_DASHBOARDS_PATH:-./observability/grafana/dashboards}:/var/lib/grafana/dashboards:ro
|
|
32
32
|
ports:
|
|
33
|
-
-
|
|
33
|
+
- '${GRAFANA_PORT:-3003}:3000'
|
|
34
34
|
depends_on:
|
|
35
35
|
- prometheus
|
|
36
36
|
- loki
|
|
@@ -43,7 +43,7 @@ services:
|
|
|
43
43
|
- ${LOKI_CONFIG_PATH:-./observability/loki-config.yml}:/etc/loki/loki-config.yml:ro
|
|
44
44
|
- loki_data:/loki
|
|
45
45
|
ports:
|
|
46
|
-
-
|
|
46
|
+
- '${LOKI_PORT:-3100}:3100'
|
|
47
47
|
|
|
48
48
|
promtail:
|
|
49
49
|
image: grafana/promtail:${PROMTAIL_IMAGE_TAG:-2.9.4}
|
|
@@ -54,11 +54,11 @@ services:
|
|
|
54
54
|
- ${APP_LOGS_PATH:-./logs}:/var/log/omnizap:ro
|
|
55
55
|
- ${MYSQL_LOGS_PATH:-/var/log/mysql}:/var/log/mysql:ro
|
|
56
56
|
ports:
|
|
57
|
-
-
|
|
57
|
+
- '${PROMTAIL_PORT:-9080}:9080'
|
|
58
58
|
depends_on:
|
|
59
59
|
- loki
|
|
60
60
|
extra_hosts:
|
|
61
|
-
-
|
|
61
|
+
- 'host.docker.internal:host-gateway'
|
|
62
62
|
|
|
63
63
|
mysql-exporter:
|
|
64
64
|
image: prom/mysqld-exporter:${MYSQL_EXPORTER_IMAGE_TAG:-v0.15.1}
|
|
@@ -66,38 +66,38 @@ services:
|
|
|
66
66
|
environment:
|
|
67
67
|
- DATA_SOURCE_NAME=${MYSQL_EXPORTER_DSN:-exporter:exporter@(host.docker.internal:3306)/}
|
|
68
68
|
command:
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
69
|
+
- '--config.my-cnf=/etc/mysql-exporter/my.cnf'
|
|
70
|
+
- '--collect.global_status'
|
|
71
|
+
- '--collect.global_variables'
|
|
72
|
+
- '--no-collect.slave_status'
|
|
73
|
+
- '--collect.info_schema.innodb_metrics'
|
|
74
|
+
- '--collect.info_schema.processlist'
|
|
75
|
+
- '--collect.info_schema.tables'
|
|
76
|
+
- '--collect.perf_schema.eventsstatements'
|
|
77
|
+
- '--collect.perf_schema.tableiowaits'
|
|
78
|
+
- '--collect.perf_schema.tablelocks'
|
|
79
79
|
volumes:
|
|
80
80
|
- ${MYSQL_EXPORTER_CNF_PATH:-./observability/mysql-exporter.cnf}:/etc/mysql-exporter/my.cnf:ro
|
|
81
81
|
ports:
|
|
82
|
-
-
|
|
82
|
+
- '${MYSQL_EXPORTER_PORT:-9104}:9104'
|
|
83
83
|
extra_hosts:
|
|
84
|
-
-
|
|
84
|
+
- 'host.docker.internal:host-gateway'
|
|
85
85
|
|
|
86
86
|
node-exporter:
|
|
87
87
|
image: prom/node-exporter:${NODE_EXPORTER_IMAGE_TAG:-v1.7.0}
|
|
88
88
|
restart: unless-stopped
|
|
89
|
-
pid:
|
|
89
|
+
pid: 'host'
|
|
90
90
|
command:
|
|
91
|
-
-
|
|
91
|
+
- '--path.rootfs=/host'
|
|
92
92
|
volumes:
|
|
93
93
|
- /:/host:ro,rslave
|
|
94
94
|
ports:
|
|
95
|
-
-
|
|
95
|
+
- '${NODE_EXPORTER_PORT:-9100}:9100'
|
|
96
96
|
|
|
97
97
|
volumes:
|
|
98
98
|
grafana_data:
|
|
99
|
-
name:
|
|
99
|
+
name: '${STACK_NAME:-omnizap}_grafana_data'
|
|
100
100
|
prometheus_data:
|
|
101
|
-
name:
|
|
101
|
+
name: '${STACK_NAME:-omnizap}_prometheus_data'
|
|
102
102
|
loki_data:
|
|
103
|
-
name:
|
|
103
|
+
name: '${STACK_NAME:-omnizap}_loki_data'
|
|
@@ -23,12 +23,14 @@ Objetivo: posicionar OmniZap como "bot pronto para WhatsApp" para usuario final,
|
|
|
23
23
|
## 1) FAQ + JSON-LD pronta
|
|
24
24
|
|
|
25
25
|
Status implementado no projeto:
|
|
26
|
+
|
|
26
27
|
- Home (`/`): FAQ visual + `FAQPage` JSON-LD alinhado ao posicionamento "adicionar e usar".
|
|
27
28
|
- Catalogo (`/stickers/`): `FAQPage` JSON-LD no HTML publico.
|
|
28
29
|
- Pack (`/stickers/{packKey}`): `FAQPage` JSON-LD dinamico no HTML SEO server-side.
|
|
29
30
|
- API Docs (`/api-docs/`): ja possui `SoftwareApplication` + `FAQPage`.
|
|
30
31
|
|
|
31
32
|
Checklist tecnico para manter:
|
|
33
|
+
|
|
32
34
|
- FAQ da pagina deve bater 1:1 com o JSON-LD.
|
|
33
35
|
- Linguagem da FAQ deve seguir intencao da pagina (usuario final na home, integracao no cluster tecnico).
|
|
34
36
|
- Atualizar data de cache-bust quando houver mudanca textual relevante.
|
|
@@ -38,6 +40,7 @@ Checklist tecnico para manter:
|
|
|
38
40
|
Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada e os demais itens seguem como backlog de expansao.
|
|
39
41
|
|
|
40
42
|
### Cluster principal (comercial)
|
|
43
|
+
|
|
41
44
|
- `/bot-whatsapp-para-grupo/`
|
|
42
45
|
- `/bot-whatsapp-para-comunidade/`
|
|
43
46
|
- `/bot-whatsapp-para-loja-online/`
|
|
@@ -45,6 +48,7 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
45
48
|
- `/bot-whatsapp-para-equipes-internas/`
|
|
46
49
|
|
|
47
50
|
### Cluster dores (intencao alta)
|
|
51
|
+
|
|
48
52
|
- `/como-moderar-grupo-whatsapp/`
|
|
49
53
|
- `/como-evitar-spam-no-whatsapp/`
|
|
50
54
|
- `/como-organizar-comunidade-whatsapp/`
|
|
@@ -52,17 +56,20 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
52
56
|
- `/como-criar-comandos-whatsapp/`
|
|
53
57
|
|
|
54
58
|
### Cluster comparativo (captura de decisao)
|
|
59
|
+
|
|
55
60
|
- `/melhor-bot-whatsapp-para-grupos/`
|
|
56
61
|
- `/omnizap-vs-blip/`
|
|
57
62
|
- `/omnizap-vs-zenvia/`
|
|
58
63
|
- `/omnizap-vs-huggy/`
|
|
59
64
|
|
|
60
65
|
### Cluster de feature (stickers como modulo)
|
|
66
|
+
|
|
61
67
|
- `/stickers-para-bot-whatsapp/`
|
|
62
68
|
- `/como-usar-stickers-no-bot/`
|
|
63
69
|
- `/pack-de-stickers-para-grupos/`
|
|
64
70
|
|
|
65
71
|
### Regra de interlink obrigatoria
|
|
72
|
+
|
|
66
73
|
- Toda pagina satelite linka para: `/`, `/api-docs/`, `/stickers/`.
|
|
67
74
|
- Toda pagina de pack linka para: `/api-docs/` + `/` + `/stickers/`.
|
|
68
75
|
- Home aponta para satelites comerciais e para dores.
|
|
@@ -70,6 +77,7 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
70
77
|
## 3) Mapa de palavras-chave Brasil
|
|
71
78
|
|
|
72
79
|
### Head terms (topo de volume)
|
|
80
|
+
|
|
73
81
|
- bot para whatsapp
|
|
74
82
|
- chatbot para whatsapp
|
|
75
83
|
- automacao whatsapp
|
|
@@ -77,6 +85,7 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
77
85
|
- bot para grupo whatsapp
|
|
78
86
|
|
|
79
87
|
### Mid-tail (intencao de solucao)
|
|
88
|
+
|
|
80
89
|
- bot para moderar grupo whatsapp
|
|
81
90
|
- bot para comunidade whatsapp
|
|
82
91
|
- bot para responder mensagens no whatsapp
|
|
@@ -84,6 +93,7 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
84
93
|
- bot de atendimento whatsapp sem programar
|
|
85
94
|
|
|
86
95
|
### Long-tail (oportunidade de rank rapido)
|
|
96
|
+
|
|
87
97
|
- como organizar grupo de whatsapp automaticamente
|
|
88
98
|
- como evitar spam em grupo de whatsapp com bot
|
|
89
99
|
- bot pronto para grupo de estudos no whatsapp
|
|
@@ -91,12 +101,14 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
91
101
|
- como adicionar bot no grupo do whatsapp
|
|
92
102
|
|
|
93
103
|
### Keywords de modulo (stickers)
|
|
104
|
+
|
|
94
105
|
- sticker para bot whatsapp
|
|
95
106
|
- pack de stickers para whatsapp
|
|
96
107
|
- catalogo de stickers para bot
|
|
97
108
|
- stickers integrados via api
|
|
98
109
|
|
|
99
110
|
### Mapa por intencao
|
|
111
|
+
|
|
100
112
|
- Descoberta: "como", "o que e", "vale a pena".
|
|
101
113
|
- Consideracao: "melhor", "comparativo", "preco", "funciona".
|
|
102
114
|
- Conversao: "adicionar bot", "testar bot", "bot pronto", "sem configuracao".
|
|
@@ -104,22 +116,26 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
104
116
|
## 4) Estrategia para ranquear antes dos concorrentes
|
|
105
117
|
|
|
106
118
|
### Fase 1 (dias 0-30) - ganhar velocidade
|
|
119
|
+
|
|
107
120
|
- Publicar 8-12 conteudos long-tail de dor real (moderar, spam, avisos, comunidade).
|
|
108
121
|
- Criar comparativos orientados a decisao (sem ataque de marca, foco em "para quem serve").
|
|
109
122
|
- Fortalecer CTR com titles de resultado: "em 1 minuto", "sem configuracao", "sem programar".
|
|
110
123
|
- Revisar links internos para formar ciclo fechado entre Home -> API -> Stickers -> Packs.
|
|
111
124
|
|
|
112
125
|
### Fase 2 (dias 31-60) - escalar cobertura
|
|
126
|
+
|
|
113
127
|
- Expandir para paginas satelite por nicho (comunidade, loja, estudo, creators).
|
|
114
128
|
- Criar template de prova social por caso de uso (antes/depois em metricas simples).
|
|
115
129
|
- Atualizar sitemap com prioridade para novas satelites e hubs de cluster.
|
|
116
130
|
|
|
117
131
|
### Fase 3 (dias 61-90) - defender posicao
|
|
132
|
+
|
|
118
133
|
- Criar FAQ adicional por pagina (3-5 perguntas de objecao).
|
|
119
134
|
- Otimizar paginas que baterem top 20 para top 10 (title, intro, links internos, schema).
|
|
120
135
|
- Construir backlinks de contexto (comunidades, blogs de creators, automacao e WhatsApp).
|
|
121
136
|
|
|
122
137
|
### KPI alvo (90 dias)
|
|
138
|
+
|
|
123
139
|
- +30% impressoes organicas (GSC) nas queries com "bot", "grupo", "whatsapp".
|
|
124
140
|
- +20% CTR medio nas paginas comerciais.
|
|
125
141
|
- 20+ keywords long-tail em top 10.
|
|
@@ -128,44 +144,54 @@ Observacao: abaixo esta o mapa estrategico de satelites. A fase 1 foi publicada
|
|
|
128
144
|
## 5) Analise de concorrencia nacional (snapshot)
|
|
129
145
|
|
|
130
146
|
### Camada 1: suites enterprise (fortes em marca e CAC)
|
|
147
|
+
|
|
131
148
|
1. Blip
|
|
149
|
+
|
|
132
150
|
- Sinal: posicionamento forte em WhatsApp + IA + vendas.
|
|
133
151
|
- Evidencia publica: "plataforma oficial", numeros altos de mensagens/chatbots.
|
|
134
152
|
- Risco para OmniZap: domina termos genericos institucionais e enterprise.
|
|
135
153
|
- Brecha para OmniZap: pouca proposta "bot pronto para grupo em 1 minuto".
|
|
136
154
|
|
|
137
155
|
2. Zenvia
|
|
156
|
+
|
|
138
157
|
- Sinal: narrativa de Customer Cloud multicanal com WhatsApp no centro.
|
|
139
158
|
- Evidencia publica: foco em automacoes, campanhas, API e atendimento.
|
|
140
159
|
- Risco para OmniZap: forte presenca B2B em termos de atendimento e vendas.
|
|
141
160
|
- Brecha para OmniZap: proposta da Zenvia tende a ser mais "suite" e menos "plug-and-play para comunidade".
|
|
142
161
|
|
|
143
162
|
3. Huggy
|
|
163
|
+
|
|
144
164
|
- Sinal: oferta de chatbot/atendimento, com claim de 4000 negocios.
|
|
145
165
|
- Evidencia publica: foco em centralizacao de canais + automacao 24/7.
|
|
146
166
|
- Risco para OmniZap: captura media-tail de atendimento WhatsApp.
|
|
147
167
|
- Brecha para OmniZap: mensagem menos focada em "grupo/comunidade".
|
|
148
168
|
|
|
149
169
|
4. Leadster
|
|
170
|
+
|
|
150
171
|
- Sinal: entrada forte em marketing conversacional + WhatsApp.
|
|
151
172
|
- Evidencia publica: foco em captacao e qualificacao de leads.
|
|
152
173
|
- Risco para OmniZap: ocupa termos de "chatbot para vender".
|
|
153
174
|
- Brecha para OmniZap: menos foco em operacao de grupos e moderacao.
|
|
154
175
|
|
|
155
176
|
### Camada 2: nicho "bot para grupos" (fragmentado)
|
|
177
|
+
|
|
156
178
|
1. BotAdmin
|
|
179
|
+
|
|
157
180
|
- Foco: moderacao de grupos, comandos e automacao.
|
|
158
181
|
- Risco: pode capturar suas palavras exatas de intencao alta (grupo/moderacao).
|
|
159
182
|
|
|
160
183
|
2. AutoGrupo
|
|
184
|
+
|
|
161
185
|
- Foco: monetizacao de grupos pagos.
|
|
162
186
|
- Risco: captura nicho de creators infoproduto.
|
|
163
187
|
|
|
164
188
|
3. Sites pequenos de "bot de figurinhas"
|
|
189
|
+
|
|
165
190
|
- Foco: termos de cauda longa e transacional de baixo ticket.
|
|
166
191
|
- Risco: volume pulverizado, baixa qualidade media, mas alta capilaridade de indexacao.
|
|
167
192
|
|
|
168
193
|
### Leitura competitiva objetiva
|
|
194
|
+
|
|
169
195
|
- Grandes players defendem termos amplos (chatbot, plataforma, API, atendimento).
|
|
170
196
|
- Oportunidade real de OmniZap esta no "desconforto operacional" de quem administra grupos.
|
|
171
197
|
- Melhor estrategia: dominar long-tail de dor + CTA de uso imediato.
|