@kaikybrofc/omnizap-system 2.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +534 -0
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/RELEASE-v2.1.2.md +83 -0
- package/app/config/adminIdentity.js +87 -0
- package/app/config/baileysConfig.js +693 -0
- package/app/config/groupUtils.js +388 -0
- package/app/connection/socketController.js +992 -0
- package/app/controllers/messageController.js +354 -0
- package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
- package/app/modules/adminModule/groupEventHandlers.js +355 -0
- package/app/modules/aiModule/catCommand.js +1006 -0
- package/app/modules/broadcastModule/noticeCommand.js +416 -0
- package/app/modules/gameModule/diceCommand.js +67 -0
- package/app/modules/menuModule/common.js +311 -0
- package/app/modules/menuModule/menus.js +59 -0
- package/app/modules/playModule/playCommand.js +1615 -0
- package/app/modules/quoteModule/quoteCommand.js +851 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
- package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
- package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
- package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
- package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
- package/app/modules/statsModule/globalRankingCommand.js +65 -0
- package/app/modules/statsModule/noMessageCommand.js +288 -0
- package/app/modules/statsModule/rankingCommand.js +60 -0
- package/app/modules/statsModule/rankingCommon.js +889 -0
- package/app/modules/stickerModule/addStickerMetadata.js +239 -0
- package/app/modules/stickerModule/convertToWebp.js +390 -0
- package/app/modules/stickerModule/stickerCommand.js +454 -0
- package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
- package/app/modules/stickerModule/stickerTextCommand.js +657 -0
- package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
- package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
- package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
- package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
- package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
- package/app/modules/stickerPackModule/stickerPackService.js +788 -0
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
- package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
- package/app/modules/systemMetricsModule/pingCommand.js +421 -0
- package/app/modules/tiktokModule/tiktokCommand.js +798 -0
- package/app/modules/userModule/userCommand.js +1217 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
- package/app/observability/metrics.js +734 -0
- package/app/services/captchaService.js +492 -0
- package/app/services/dbWriteQueue.js +572 -0
- package/app/services/groupMetadataService.js +279 -0
- package/app/services/lidMapService.js +663 -0
- package/app/services/messagePersistenceService.js +56 -0
- package/app/services/newsBroadcastService.js +351 -0
- package/app/services/pokeApiService.js +398 -0
- package/app/services/queueUtils.js +57 -0
- package/app/services/socketState.js +7 -0
- package/app/store/aiPromptStore.js +38 -0
- package/app/store/groupConfigStore.js +58 -0
- package/app/store/premiumUserStore.js +36 -0
- package/app/utils/antiLink/antiLinkModule.js +804 -0
- package/app/utils/http/getImageBufferModule.js +18 -0
- package/app/utils/json/jsonSanitizer.js +113 -0
- package/app/utils/json/jsonSanitizer.test.js +40 -0
- package/app/utils/logger/loggerModule.js +262 -0
- package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
- package/database/index.js +2052 -0
- package/database/init.js +516 -0
- package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
- package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
- package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
- package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
- package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
- package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
- package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
- package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
- package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
- package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
- package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
- package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
- package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
- package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
- package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
- package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
- package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
- package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
- package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
- package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
- package/docker-compose.yml +103 -0
- package/ecosystem.prod.config.cjs +35 -0
- package/eslint.config.js +61 -0
- package/index.js +437 -0
- package/ml/clip_classifier/Dockerfile +16 -0
- package/ml/clip_classifier/README.md +120 -0
- package/ml/clip_classifier/adaptive_scoring.py +40 -0
- package/ml/clip_classifier/classifier.py +654 -0
- package/ml/clip_classifier/embedding_store.py +481 -0
- package/ml/clip_classifier/env_loader.py +15 -0
- package/ml/clip_classifier/llm_label_expander.py +144 -0
- package/ml/clip_classifier/main.py +213 -0
- package/ml/clip_classifier/requirements.txt +10 -0
- package/ml/clip_classifier/similarity_engine.py +74 -0
- package/observability/alert-rules.yml +60 -0
- package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
- package/observability/grafana/dashboards/omnizap-overview.json +170 -0
- package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
- package/observability/loki-config.yml +38 -0
- package/observability/mysql-exporter.cnf +5 -0
- package/observability/mysql-setup.sql +46 -0
- package/observability/prometheus.yml +32 -0
- package/observability/promtail-config.yml +84 -0
- package/package.json +109 -0
- package/public/api-docs/index.html +144 -0
- package/public/css/github-project-panel.css +297 -0
- package/public/css/stickers-admin.css +1272 -0
- package/public/css/styles.css +671 -0
- package/public/index.html +1311 -0
- package/public/js/apps/apiDocsApp.js +310 -0
- package/public/js/apps/createPackApp.js +2069 -0
- package/public/js/apps/homeApp.js +396 -0
- package/public/js/apps/stickersAdminApp.js +1744 -0
- package/public/js/apps/stickersApp.js +4830 -0
- package/public/js/catalog.js +1019 -0
- package/public/js/github-panel/components/CommitList.js +34 -0
- package/public/js/github-panel/components/ErrorState.js +16 -0
- package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
- package/public/js/github-panel/components/ReleaseList.js +38 -0
- package/public/js/github-panel/components/SkeletonPanel.js +22 -0
- package/public/js/github-panel/components/StatCard.js +15 -0
- package/public/js/github-panel/index.js +15 -0
- package/public/js/github-panel/useGithubRepoData.js +154 -0
- package/public/js/github-panel/vendor/react.js +11 -0
- package/public/js/runtime/react-runtime.js +19 -0
- package/public/licenca/index.html +106 -0
- package/public/stickers/admin/index.html +23 -0
- package/public/stickers/create/index.html +47 -0
- package/public/stickers/index.html +48 -0
- package/public/termos-de-uso/index.html +125 -0
- package/scripts/cache-bust.mjs +107 -0
- package/scripts/deploy.sh +458 -0
- package/scripts/github-deploy-notify.mjs +174 -0
- package/scripts/release.sh +129 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import logger from '../../utils/logger/loggerModule.js';
|
|
2
|
+
import getImageBuffer from '../../utils/http/getImageBufferModule.js';
|
|
3
|
+
import { getAllParticipatingGroups } from '../../config/groupUtils.js';
|
|
4
|
+
import { sendAndStore } from '../../services/messagePersistenceService.js';
|
|
5
|
+
import { getAdminJid, isAdminSenderAsync } from '../../config/adminIdentity.js';
|
|
6
|
+
|
|
7
|
+
const MENU_IMAGE_ENV = 'IMAGE_MENU';
|
|
8
|
+
const PROGRESS_EVERY = 10;
|
|
9
|
+
const PROGRESS_INTERVAL_MS = 15 * 1000;
|
|
10
|
+
const MAX_FAILURE_SAMPLE = 10;
|
|
11
|
+
const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
|
|
12
|
+
|
|
13
|
+
const MODE_CONFIG = {
|
|
14
|
+
default: {
|
|
15
|
+
concurrency: 4,
|
|
16
|
+
jitterMin: 200,
|
|
17
|
+
jitterMax: 900,
|
|
18
|
+
retries: 2,
|
|
19
|
+
backoffBaseMs: 2000,
|
|
20
|
+
},
|
|
21
|
+
fast: {
|
|
22
|
+
concurrency: 6,
|
|
23
|
+
jitterMin: 120,
|
|
24
|
+
jitterMax: 600,
|
|
25
|
+
retries: 1,
|
|
26
|
+
backoffBaseMs: 2000,
|
|
27
|
+
},
|
|
28
|
+
safe: {
|
|
29
|
+
concurrency: 2,
|
|
30
|
+
jitterMin: 400,
|
|
31
|
+
jitterMax: 1200,
|
|
32
|
+
retries: 3,
|
|
33
|
+
backoffBaseMs: 2500,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Aguarda um tempo em milissegundos.
|
|
39
|
+
*
|
|
40
|
+
* @param {number} ms
|
|
41
|
+
* @returns {Promise<void>}
|
|
42
|
+
*/
|
|
43
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
|
|
45
|
+
const jitter = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
46
|
+
|
|
47
|
+
const isRateLimitError = (error) => {
|
|
48
|
+
const status =
|
|
49
|
+
error?.status || error?.statusCode || error?.response?.status || error?.output?.statusCode;
|
|
50
|
+
if (status === 429) return true;
|
|
51
|
+
const message = String(error?.message || '').toLowerCase();
|
|
52
|
+
return (
|
|
53
|
+
message.includes('rate') ||
|
|
54
|
+
message.includes('flood') ||
|
|
55
|
+
message.includes('too many') ||
|
|
56
|
+
message.includes('spam') ||
|
|
57
|
+
message.includes('limit')
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const isRetryableError = (error) => {
|
|
62
|
+
const status =
|
|
63
|
+
error?.status || error?.statusCode || error?.response?.status || error?.output?.statusCode;
|
|
64
|
+
if (status && status >= 500) return true;
|
|
65
|
+
if (isRateLimitError(error)) return true;
|
|
66
|
+
const message = String(error?.message || '').toLowerCase();
|
|
67
|
+
return (
|
|
68
|
+
message.includes('timed out') ||
|
|
69
|
+
message.includes('timeout') ||
|
|
70
|
+
message.includes('network') ||
|
|
71
|
+
message.includes('connection') ||
|
|
72
|
+
message.includes('socket')
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const withRetry = async (
|
|
77
|
+
fn,
|
|
78
|
+
{ retries = 2, baseDelayMs = 2000, jitterMin = 200, jitterMax = 800, onRateLimit } = {},
|
|
79
|
+
) => {
|
|
80
|
+
let attempt = 0;
|
|
81
|
+
while (true) {
|
|
82
|
+
try {
|
|
83
|
+
return await fn(attempt);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const retryable = isRetryableError(error);
|
|
86
|
+
if (!retryable || attempt >= retries) {
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
const base = Math.min(15000, baseDelayMs * 2 ** attempt);
|
|
90
|
+
const wait = base + jitter(jitterMin, jitterMax);
|
|
91
|
+
if (isRateLimitError(error) && typeof onRateLimit === 'function') {
|
|
92
|
+
onRateLimit(wait);
|
|
93
|
+
}
|
|
94
|
+
await sleep(wait);
|
|
95
|
+
attempt += 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const runWithConcurrency = async (items, limit, workerFn, onProgress) => {
|
|
101
|
+
let index = 0;
|
|
102
|
+
const results = new Array(items.length);
|
|
103
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
104
|
+
while (true) {
|
|
105
|
+
const currentIndex = index;
|
|
106
|
+
if (currentIndex >= items.length) break;
|
|
107
|
+
const item = items[currentIndex];
|
|
108
|
+
index += 1;
|
|
109
|
+
let result;
|
|
110
|
+
try {
|
|
111
|
+
result = await workerFn(item, currentIndex);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
result = { ok: false, id: item?.id, error };
|
|
114
|
+
}
|
|
115
|
+
results[currentIndex] = result;
|
|
116
|
+
if (onProgress) {
|
|
117
|
+
await onProgress(result, currentIndex);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
await Promise.all(workers);
|
|
122
|
+
return results;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const parseNoticeArgs = (text = '') => {
|
|
126
|
+
let mode = 'default';
|
|
127
|
+
let remaining = text || '';
|
|
128
|
+
while (true) {
|
|
129
|
+
const match = remaining.match(/^\s*(-fast|-safe)(?=\s|$)/i);
|
|
130
|
+
if (!match) break;
|
|
131
|
+
const flag = match[1].toLowerCase();
|
|
132
|
+
if (flag === '-fast') mode = 'fast';
|
|
133
|
+
else if (flag === '-safe') mode = 'safe';
|
|
134
|
+
remaining = remaining.slice(match[0].length);
|
|
135
|
+
}
|
|
136
|
+
return { mode, message: remaining };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const UNICODE_SPACES_REGEX = /[\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
140
|
+
const INVISIBLE_TO_SPACE_REGEX = /[\u200B\u2060\u180E\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
|
|
141
|
+
const INVISIBLE_REMOVE_REGEX = /[\u00AD\uFEFF]/g;
|
|
142
|
+
|
|
143
|
+
const countMatches = (value, regex) => {
|
|
144
|
+
if (!value) return 0;
|
|
145
|
+
const matches = value.match(regex);
|
|
146
|
+
return matches ? matches.length : 0;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const getTextStats = (value) => {
|
|
150
|
+
const text = String(value ?? '');
|
|
151
|
+
const normalizedLineBreaks = text.replace(/\r\n?/g, '\n').replace(/\u2028|\u2029/g, '\n');
|
|
152
|
+
return {
|
|
153
|
+
length: text.length,
|
|
154
|
+
lines: text ? normalizedLineBreaks.split('\n').length : 0,
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const normalizeWhatsAppText = (input = '') => {
|
|
159
|
+
let text = String(input ?? '');
|
|
160
|
+
if (!text) return '';
|
|
161
|
+
text = text.replace(/\r\n?/g, '\n').replace(/\u2028|\u2029/g, '\n');
|
|
162
|
+
text = text.replace(UNICODE_SPACES_REGEX, ' ');
|
|
163
|
+
text = text.replace(/\t/g, ' ');
|
|
164
|
+
text = text.replace(INVISIBLE_REMOVE_REGEX, '');
|
|
165
|
+
text = text.replace(INVISIBLE_TO_SPACE_REGEX, ' ');
|
|
166
|
+
|
|
167
|
+
const lines = text.split('\n');
|
|
168
|
+
const normalizedLines = lines.map((line) => {
|
|
169
|
+
if (!line) return '';
|
|
170
|
+
const spaceRuns = line.match(/ {2,}/g);
|
|
171
|
+
const hasMultipleSpaceRuns = spaceRuns && spaceRuns.length >= 2;
|
|
172
|
+
const isFormatted =
|
|
173
|
+
/^\s{2,}/.test(line) || /^\s*```/.test(line) || line.includes('|') || hasMultipleSpaceRuns;
|
|
174
|
+
if (isFormatted) {
|
|
175
|
+
return line.replace(/ {6,}/g, ' ');
|
|
176
|
+
}
|
|
177
|
+
return line.replace(/ {3,}/g, ' ');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const normalized = normalizedLines.join('\n');
|
|
181
|
+
return /[^\s]/.test(normalized) ? normalized : '';
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Monta uma lista textual dos grupos.
|
|
186
|
+
*
|
|
187
|
+
* @param {Array<{id: string, subject?: string}>} groups
|
|
188
|
+
* @returns {string}
|
|
189
|
+
*/
|
|
190
|
+
const buildGroupList = (groups) =>
|
|
191
|
+
groups
|
|
192
|
+
.map((group, index) => {
|
|
193
|
+
const name = group.subject || 'Sem nome';
|
|
194
|
+
return `${index + 1}. ${name} (${group.id})`;
|
|
195
|
+
})
|
|
196
|
+
.join('\n');
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Comando do dono do bot para enviar um aviso com a imagem do menu a todos os grupos.
|
|
200
|
+
*
|
|
201
|
+
* @param {object} params
|
|
202
|
+
* @param {object} params.sock
|
|
203
|
+
* @param {string} params.remoteJid
|
|
204
|
+
* @param {object} params.messageInfo
|
|
205
|
+
* @param {number} params.expirationMessage
|
|
206
|
+
* @param {string} params.senderJid
|
|
207
|
+
* @param {string} params.text
|
|
208
|
+
* @returns {Promise<void>}
|
|
209
|
+
*/
|
|
210
|
+
export async function handleNoticeCommand({
|
|
211
|
+
sock,
|
|
212
|
+
remoteJid,
|
|
213
|
+
messageInfo,
|
|
214
|
+
expirationMessage,
|
|
215
|
+
senderJid,
|
|
216
|
+
text,
|
|
217
|
+
commandPrefix = DEFAULT_COMMAND_PREFIX,
|
|
218
|
+
}) {
|
|
219
|
+
const ownerJid = getAdminJid();
|
|
220
|
+
if (!ownerJid) {
|
|
221
|
+
await sendAndStore(sock,
|
|
222
|
+
remoteJid,
|
|
223
|
+
{ text: '❌ USER_ADMIN não configurado no ambiente.' },
|
|
224
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
225
|
+
);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (
|
|
230
|
+
!(await isAdminSenderAsync({
|
|
231
|
+
jid: senderJid || null,
|
|
232
|
+
participantAlt: messageInfo?.key?.participantAlt || null,
|
|
233
|
+
participant: messageInfo?.key?.participant || null,
|
|
234
|
+
}))
|
|
235
|
+
) {
|
|
236
|
+
await sendAndStore(sock,
|
|
237
|
+
remoteJid,
|
|
238
|
+
{ text: '❌ Você não tem permissão para usar este comando.' },
|
|
239
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
240
|
+
);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const { mode, message: rawNoticeText } = parseNoticeArgs(text || '');
|
|
245
|
+
if (!rawNoticeText || !rawNoticeText.replace(/\s/g, '')) {
|
|
246
|
+
await sendAndStore(sock,
|
|
247
|
+
remoteJid,
|
|
248
|
+
{ text: `Uso: ${commandPrefix}aviso [-fast|-safe] <mensagem>` },
|
|
249
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
250
|
+
);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const noticeStatsBefore = getTextStats(rawNoticeText);
|
|
255
|
+
const unicodeSpaceCount = countMatches(rawNoticeText, UNICODE_SPACES_REGEX);
|
|
256
|
+
const invisibleCount =
|
|
257
|
+
countMatches(rawNoticeText, INVISIBLE_TO_SPACE_REGEX) +
|
|
258
|
+
countMatches(rawNoticeText, INVISIBLE_REMOVE_REGEX);
|
|
259
|
+
const normalizedNoticeText = normalizeWhatsAppText(rawNoticeText);
|
|
260
|
+
const noticeStatsAfter = getTextStats(normalizedNoticeText);
|
|
261
|
+
|
|
262
|
+
logger.info(
|
|
263
|
+
`handleNoticeCommand Normalizacao do aviso: tamanho ${noticeStatsBefore.length}->${noticeStatsAfter.length} | linhas ${noticeStatsBefore.lines}->${noticeStatsAfter.lines} | invisiveis ${invisibleCount} | espacosUnicode ${unicodeSpaceCount}`,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (!normalizedNoticeText) {
|
|
267
|
+
logger.warn(
|
|
268
|
+
`handleNoticeCommand Aviso vazio apos normalizacao: tamanho ${noticeStatsBefore.length} | linhas ${noticeStatsBefore.lines}`,
|
|
269
|
+
);
|
|
270
|
+
await sendAndStore(sock,
|
|
271
|
+
remoteJid,
|
|
272
|
+
{ text: '❌ A mensagem do aviso ficou vazia após a normalização.' },
|
|
273
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const imageUrl = process.env[MENU_IMAGE_ENV];
|
|
279
|
+
if (!imageUrl) {
|
|
280
|
+
await sendAndStore(sock,
|
|
281
|
+
remoteJid,
|
|
282
|
+
{ text: '❌ IMAGE_MENU não configurado no ambiente.' },
|
|
283
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
284
|
+
);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let groupsMap = null;
|
|
289
|
+
try {
|
|
290
|
+
groupsMap = await getAllParticipatingGroups(sock);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
logger.error(`handleNoticeCommand Erro ao obter grupos: ${error.message}`);
|
|
293
|
+
await sendAndStore(sock,
|
|
294
|
+
remoteJid,
|
|
295
|
+
{ text: '❌ Não foi possível obter a lista de grupos.' },
|
|
296
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
297
|
+
);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const groups = Object.values(groupsMap || {});
|
|
302
|
+
if (groups.length === 0) {
|
|
303
|
+
await sendAndStore(sock,
|
|
304
|
+
remoteJid,
|
|
305
|
+
{ text: '⚠️ O bot não está em nenhum grupo.' },
|
|
306
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
307
|
+
);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const groupListText = buildGroupList(groups);
|
|
312
|
+
const config = MODE_CONFIG[mode] || MODE_CONFIG.default;
|
|
313
|
+
await sendAndStore(sock,
|
|
314
|
+
remoteJid,
|
|
315
|
+
{
|
|
316
|
+
text: `📋 Grupos (${groups.length}):\n${groupListText}\n\n📣 Aviso:\n${normalizedNoticeText}\n\n🚀 Iniciando envio (modo ${mode}).\nConcorrência: ${config.concurrency} | Jitter: ${config.jitterMin}-${config.jitterMax}ms | Retries: ${config.retries}`,
|
|
317
|
+
},
|
|
318
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
let imageBuffer = null;
|
|
322
|
+
try {
|
|
323
|
+
imageBuffer = await getImageBuffer(imageUrl);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
logger.error(`handleNoticeCommand Erro ao baixar imagem do menu: ${error.message}`);
|
|
326
|
+
await sendAndStore(sock,
|
|
327
|
+
remoteJid,
|
|
328
|
+
{ text: '❌ Não foi possível baixar a imagem do menu.' },
|
|
329
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
330
|
+
);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const sendBroadcast = async () => {
|
|
335
|
+
let processed = 0;
|
|
336
|
+
let successCount = 0;
|
|
337
|
+
let failureCount = 0;
|
|
338
|
+
let lastProgressAt = Date.now();
|
|
339
|
+
const failureIds = [];
|
|
340
|
+
let globalBackoffUntil = 0;
|
|
341
|
+
let rateLimitHits = 0;
|
|
342
|
+
|
|
343
|
+
const worker = async (group) => {
|
|
344
|
+
await sleep(jitter(config.jitterMin, config.jitterMax));
|
|
345
|
+
const sendOnce = async () => {
|
|
346
|
+
const now = Date.now();
|
|
347
|
+
if (globalBackoffUntil > now) {
|
|
348
|
+
await sleep(globalBackoffUntil - now);
|
|
349
|
+
}
|
|
350
|
+
await sendAndStore(sock, group.id, { image: imageBuffer, caption: normalizedNoticeText });
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
await withRetry(sendOnce, {
|
|
354
|
+
retries: config.retries,
|
|
355
|
+
baseDelayMs: config.backoffBaseMs,
|
|
356
|
+
jitterMin: config.jitterMin,
|
|
357
|
+
jitterMax: config.jitterMax,
|
|
358
|
+
onRateLimit: (delay) => {
|
|
359
|
+
rateLimitHits += 1;
|
|
360
|
+
const until = Date.now() + delay;
|
|
361
|
+
if (until > globalBackoffUntil) globalBackoffUntil = until;
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
return { ok: true, id: group.id };
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const onProgress = async (result) => {
|
|
368
|
+
processed += 1;
|
|
369
|
+
if (result.ok) {
|
|
370
|
+
successCount += 1;
|
|
371
|
+
} else {
|
|
372
|
+
failureCount += 1;
|
|
373
|
+
if (failureIds.length < MAX_FAILURE_SAMPLE && result.id) {
|
|
374
|
+
failureIds.push(result.id);
|
|
375
|
+
}
|
|
376
|
+
logger.error(
|
|
377
|
+
`handleNoticeCommand Falha ao enviar aviso para ${result.id}: ${result.error?.message || result.error}`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
if (
|
|
383
|
+
processed % PROGRESS_EVERY === 0 ||
|
|
384
|
+
(now - lastProgressAt >= PROGRESS_INTERVAL_MS && processed < groups.length)
|
|
385
|
+
) {
|
|
386
|
+
lastProgressAt = now;
|
|
387
|
+
await sendAndStore(sock,
|
|
388
|
+
remoteJid,
|
|
389
|
+
{
|
|
390
|
+
text: `📣 Progresso: ${processed}/${groups.length}\n✅ Sucesso: ${successCount}\n❌ Falhas: ${failureCount}`,
|
|
391
|
+
},
|
|
392
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
await runWithConcurrency(groups, config.concurrency, worker, onProgress);
|
|
398
|
+
|
|
399
|
+
const extraFailures =
|
|
400
|
+
failureCount > failureIds.length ? ` … +${failureCount - failureIds.length}` : '';
|
|
401
|
+
const failureList = failureIds.length ? `\nFalhas: ${failureIds.join(', ')}${extraFailures}` : '';
|
|
402
|
+
const rateLimitText = rateLimitHits
|
|
403
|
+
? `\n⚠️ Rate limit detectado: ${rateLimitHits}x (backoff aplicado)`
|
|
404
|
+
: '';
|
|
405
|
+
|
|
406
|
+
await sendAndStore(sock,
|
|
407
|
+
remoteJid,
|
|
408
|
+
{
|
|
409
|
+
text: `✅ Aviso finalizado.\nTotal: ${groups.length}\n✅ Sucesso: ${successCount}\n❌ Falhas: ${failureCount}${failureList}${rateLimitText}`,
|
|
410
|
+
},
|
|
411
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
412
|
+
);
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
void sendBroadcast();
|
|
416
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import logger from '../../utils/logger/loggerModule.js';
|
|
2
|
+
import { sendAndStore } from '../../services/messagePersistenceService.js';
|
|
3
|
+
|
|
4
|
+
const DICE_FACES = ['⚀', '⚁', '⚂', '⚃', '⚄', '⚅'];
|
|
5
|
+
const MAX_SIDES = 1000;
|
|
6
|
+
|
|
7
|
+
const parseSidesArgument = (rawArg) => {
|
|
8
|
+
if (!rawArg) return 6;
|
|
9
|
+
const parsed = Number.parseInt(rawArg, 10);
|
|
10
|
+
if (!Number.isFinite(parsed)) return null;
|
|
11
|
+
if (parsed < 2 || parsed > MAX_SIDES) return null;
|
|
12
|
+
return parsed;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const rollDice = (sides) => Math.floor(Math.random() * sides) + 1;
|
|
16
|
+
|
|
17
|
+
const buildUsageText = (commandPrefix) =>
|
|
18
|
+
[`Formato de uso:`, `${commandPrefix}dado`, `${commandPrefix}dado <lados (2-${MAX_SIDES})>`, `${commandPrefix}dice <lados (2-${MAX_SIDES})>`].join('\n');
|
|
19
|
+
|
|
20
|
+
export async function handleDiceCommand({
|
|
21
|
+
sock,
|
|
22
|
+
remoteJid,
|
|
23
|
+
messageInfo,
|
|
24
|
+
expirationMessage,
|
|
25
|
+
args = [],
|
|
26
|
+
commandPrefix = '/',
|
|
27
|
+
}) {
|
|
28
|
+
const sides = parseSidesArgument(args[0]);
|
|
29
|
+
if (sides === null) {
|
|
30
|
+
await sendAndStore(
|
|
31
|
+
sock,
|
|
32
|
+
remoteJid,
|
|
33
|
+
{ text: `❌ Número de lados inválido.\n\n${buildUsageText(commandPrefix)}` },
|
|
34
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
35
|
+
);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = rollDice(sides);
|
|
40
|
+
const face = sides === 6 ? DICE_FACES[result - 1] : '🎲';
|
|
41
|
+
const messageText =
|
|
42
|
+
sides === 6
|
|
43
|
+
? `🎲 Você rolou o dado e caiu em *${result}* ${face}`
|
|
44
|
+
: `🎲 Você rolou um dado de *${sides}* lados e caiu em *${result}*`;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await sendAndStore(
|
|
48
|
+
sock,
|
|
49
|
+
remoteJid,
|
|
50
|
+
{ text: messageText },
|
|
51
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
52
|
+
);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.error('handleDiceCommand: erro ao enviar resultado do dado.', {
|
|
55
|
+
error: error.message,
|
|
56
|
+
remoteJid,
|
|
57
|
+
sides,
|
|
58
|
+
result,
|
|
59
|
+
});
|
|
60
|
+
await sendAndStore(
|
|
61
|
+
sock,
|
|
62
|
+
remoteJid,
|
|
63
|
+
{ text: '❌ Não foi possível rolar o dado agora. Tente novamente.' },
|
|
64
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|