@omnizap-system/omnizap 2.6.1 → 2.6.3
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 +78 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +6 -0
- package/app/configParts/adminIdentity.js +36 -7
- package/app/configParts/baileysConfig.js +343 -56
- package/app/configParts/groupUtils.js +226 -0
- package/app/configParts/loggerConfig.js +185 -0
- package/app/configParts/messagePersistenceService.js +307 -5
- package/app/configParts/sessionConfig.js +242 -0
- package/app/connection/baileysCompatibility.test.js +10 -1
- package/app/connection/baileysDbAuthState.js +205 -9
- package/app/connection/baileysLibsignalPatch.js +210 -0
- package/app/connection/groupOwnerWriteStateResolver.js +141 -0
- package/app/connection/socketController.js +694 -123
- package/app/connection/socketController.multiSession.test.js +128 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +96 -4
- package/app/controllers/messageProcessingPipeline.js +90 -9
- package/app/controllers/messageProcessingPipeline.test.js +202 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +452 -0
- package/app/services/multiSession/groupOwnershipRepository.js +346 -0
- package/app/services/multiSession/groupOwnershipService.js +809 -0
- package/app/services/multiSession/groupOwnershipService.test.js +317 -0
- package/app/services/multiSession/sessionRegistryService.js +239 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +391 -25
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +14 -6
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/css/payments-react.css +478 -0
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/homeReactApp.js +2 -2
- package/public/js/apps/paymentsCancelReactApp.js +45 -0
- package/public/js/apps/paymentsReactApp.js +399 -0
- package/public/js/apps/paymentsSuccessReactApp.js +148 -0
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/pagamentos-cancelado.html +21 -0
- package/public/pages/pagamentos-sucesso.html +21 -0
- package/public/pages/pagamentos.html +30 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +13 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +564 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +254 -0
- package/server/controllers/payments/paymentsController.js +731 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +228 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailAutomationRuntime.js +36 -1
- package/server/email/emailAutomationService.js +42 -1
- package/server/email/emailTemplateService.js +140 -33
- package/server/http/httpRequestUtils.js +18 -14
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +35 -3
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +50 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/payments/paymentsRouter.js +47 -0
- package/server/routes/static/staticPageRouter.js +30 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +5 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
ENV_FILE="${ENV_FILE:-$PROJECT_ROOT/.env}"
|
|
6
|
+
|
|
7
|
+
SESSION_ID_PATTERN='^[a-zA-Z0-9:_-]+$'
|
|
8
|
+
SESSION_ID_MAX_LENGTH=64
|
|
9
|
+
|
|
10
|
+
SESSION_ID=""
|
|
11
|
+
SESSION_PREFIX="work"
|
|
12
|
+
SESSION_WEIGHT=1
|
|
13
|
+
SET_PRIMARY=0
|
|
14
|
+
CONNECT_NOW=1
|
|
15
|
+
RESET_AUTH=0
|
|
16
|
+
CLEAR_AUTH_FILES=0
|
|
17
|
+
ALLOW_REUSE=0
|
|
18
|
+
PM2_SYNC=1
|
|
19
|
+
PM2_TARGET_APP=""
|
|
20
|
+
PM2_DRY_RUN=0
|
|
21
|
+
RUN_HEALTHCHECK=1
|
|
22
|
+
PM2_WAIT_TIMEOUT_SECONDS=45
|
|
23
|
+
PM2_WAIT_INTERVAL_SECONDS=3
|
|
24
|
+
HEALTHCHECK_WAIT_SECONDS=45
|
|
25
|
+
HEALTHCHECK_INTERVAL_SECONDS=3
|
|
26
|
+
|
|
27
|
+
log() {
|
|
28
|
+
printf '[new-whatsapp-session] %s\n' "$*"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fail() {
|
|
32
|
+
printf '[new-whatsapp-session] erro: %s\n' "$*" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
require_cmd() {
|
|
37
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
38
|
+
fail "comando ausente: $1"
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
usage() {
|
|
43
|
+
cat <<'EOF'
|
|
44
|
+
Uso:
|
|
45
|
+
bash scripts/new-whatsapp-session.sh [opcoes]
|
|
46
|
+
|
|
47
|
+
Opcoes:
|
|
48
|
+
--session <id> Define o session_id manualmente.
|
|
49
|
+
--prefix <valor> Prefixo para gerar session_id automatico (padrao: work).
|
|
50
|
+
--weight <1-1000> Peso da sessao em BAILEYS_SESSION_WEIGHTS (padrao: 1).
|
|
51
|
+
--primary Define a nova sessao como BAILEYS_PRIMARY_SESSION_ID.
|
|
52
|
+
--reuse Permite usar um session_id ja existente.
|
|
53
|
+
--reset-auth Limpa credenciais atuais da sessao no MySQL antes do QR.
|
|
54
|
+
--clear-auth-files Junto com --reset-auth, remove app/connection/auth/*.json.
|
|
55
|
+
--no-connect Apenas atualiza .env (nao abre conexao para QR agora).
|
|
56
|
+
--no-pm2-sync Nao sincroniza/reinicia app PM2 automaticamente.
|
|
57
|
+
--pm2-app <nome> Nome do app PM2 alvo (padrao: <PM2_APP_NAME>-production).
|
|
58
|
+
--pm2-dry-run Exibe a sincronizacao PM2 sem executar restart real.
|
|
59
|
+
--no-healthcheck Nao executa healthcheck HTTP apos sincronizar PM2.
|
|
60
|
+
--help Mostra esta ajuda.
|
|
61
|
+
|
|
62
|
+
Exemplos:
|
|
63
|
+
npm run new:work
|
|
64
|
+
npm run new:work -- --session suporte_2 --primary
|
|
65
|
+
bash scripts/new-whatsapp-session.sh --prefix operador --no-connect
|
|
66
|
+
bash scripts/new-whatsapp-session.sh --no-connect --pm2-app omnizap-system-production
|
|
67
|
+
EOF
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
strip_wrapping_quotes() {
|
|
71
|
+
local value="${1:-}"
|
|
72
|
+
if [[ "$value" == \"*\" && "$value" == *\" ]]; then
|
|
73
|
+
value="${value:1:${#value}-2}"
|
|
74
|
+
elif [[ "$value" == \'*\' && "$value" == *\' ]]; then
|
|
75
|
+
value="${value:1:${#value}-2}"
|
|
76
|
+
fi
|
|
77
|
+
printf '%s' "$value"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
read_env_value() {
|
|
81
|
+
local key="$1"
|
|
82
|
+
local raw=""
|
|
83
|
+
|
|
84
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
85
|
+
raw="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d'=' -f2- || true)"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
raw="${raw//$'\r'/}"
|
|
89
|
+
strip_wrapping_quotes "$raw"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
split_entries() {
|
|
93
|
+
printf '%s' "${1:-}" | tr ',;\n' '\n' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' | awk 'NF'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
validate_session_id() {
|
|
97
|
+
local value="$1"
|
|
98
|
+
[[ -n "$value" ]] || return 1
|
|
99
|
+
[[ "${#value}" -le "$SESSION_ID_MAX_LENGTH" ]] || return 1
|
|
100
|
+
[[ "$value" =~ $SESSION_ID_PATTERN ]] || return 1
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
upsert_env_value() {
|
|
105
|
+
local key="$1"
|
|
106
|
+
local value="$2"
|
|
107
|
+
local temp_file
|
|
108
|
+
temp_file="$(mktemp)"
|
|
109
|
+
|
|
110
|
+
awk -v key="$key" -v value="$value" '
|
|
111
|
+
BEGIN {
|
|
112
|
+
replaced = 0;
|
|
113
|
+
prefix = key "=";
|
|
114
|
+
}
|
|
115
|
+
index($0, prefix) == 1 {
|
|
116
|
+
if (replaced == 0) {
|
|
117
|
+
print prefix value;
|
|
118
|
+
replaced = 1;
|
|
119
|
+
}
|
|
120
|
+
next;
|
|
121
|
+
}
|
|
122
|
+
{ print; }
|
|
123
|
+
END {
|
|
124
|
+
if (replaced == 0) {
|
|
125
|
+
print prefix value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
' "$ENV_FILE" >"$temp_file"
|
|
129
|
+
|
|
130
|
+
mv "$temp_file" "$ENV_FILE"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
is_valid_weight() {
|
|
134
|
+
local value="$1"
|
|
135
|
+
[[ "$value" =~ ^[0-9]+$ ]] || return 1
|
|
136
|
+
((value >= 1 && value <= 1000)) || return 1
|
|
137
|
+
return 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
normalize_bool() {
|
|
141
|
+
local value
|
|
142
|
+
value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
|
143
|
+
if [[ -z "$value" ]]; then
|
|
144
|
+
printf 'unset'
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
case "$value" in
|
|
148
|
+
1|true|yes|y|on)
|
|
149
|
+
printf 'true'
|
|
150
|
+
;;
|
|
151
|
+
0|false|no|n|off)
|
|
152
|
+
printf 'false'
|
|
153
|
+
;;
|
|
154
|
+
*)
|
|
155
|
+
printf 'unknown'
|
|
156
|
+
;;
|
|
157
|
+
esac
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pm2_get_status() {
|
|
161
|
+
local app_name="$1"
|
|
162
|
+
pm2 jlist 2>/dev/null | node -e "
|
|
163
|
+
const appName = String(process.argv[1] || '').trim();
|
|
164
|
+
let raw = '';
|
|
165
|
+
process.stdin.setEncoding('utf8');
|
|
166
|
+
process.stdin.on('data', (chunk) => {
|
|
167
|
+
raw += chunk;
|
|
168
|
+
});
|
|
169
|
+
process.stdin.on('end', () => {
|
|
170
|
+
try {
|
|
171
|
+
const list = JSON.parse(raw || '[]');
|
|
172
|
+
const target = Array.isArray(list) ? list.find((entry) => String(entry?.name || '') === appName) : null;
|
|
173
|
+
if (!target) {
|
|
174
|
+
process.stdout.write('missing');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const status = String(target?.pm2_env?.status || '').trim().toLowerCase();
|
|
178
|
+
process.stdout.write(status || 'unknown');
|
|
179
|
+
} catch {
|
|
180
|
+
process.stdout.write('error');
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
" "$app_name"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pm2_detect_app_name() {
|
|
187
|
+
local preferred="$1"
|
|
188
|
+
pm2 jlist 2>/dev/null | node -e "
|
|
189
|
+
const preferred = String(process.argv[1] || '').trim();
|
|
190
|
+
let raw = '';
|
|
191
|
+
process.stdin.setEncoding('utf8');
|
|
192
|
+
process.stdin.on('data', (chunk) => {
|
|
193
|
+
raw += chunk;
|
|
194
|
+
});
|
|
195
|
+
process.stdin.on('end', () => {
|
|
196
|
+
try {
|
|
197
|
+
const list = JSON.parse(raw || '[]');
|
|
198
|
+
const names = (Array.isArray(list) ? list : [])
|
|
199
|
+
.map((entry) => String(entry?.name || '').trim())
|
|
200
|
+
.filter(Boolean);
|
|
201
|
+
|
|
202
|
+
if (preferred && names.includes(preferred)) {
|
|
203
|
+
process.stdout.write(preferred);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (names.includes('omnizap-production')) {
|
|
208
|
+
process.stdout.write('omnizap-production');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const omnizapProduction = names.find((name) => /omnizap.*-production$/i.test(name));
|
|
213
|
+
if (omnizapProduction) {
|
|
214
|
+
process.stdout.write(omnizapProduction);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const anyProduction = names.find((name) => /-production$/i.test(name));
|
|
219
|
+
if (anyProduction) {
|
|
220
|
+
process.stdout.write(anyProduction);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
process.stdout.write('');
|
|
225
|
+
} catch {
|
|
226
|
+
process.stdout.write('');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
" "$preferred"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
resolve_pm2_target_app() {
|
|
233
|
+
if [[ -n "$PM2_TARGET_APP" ]]; then
|
|
234
|
+
printf '%s' "$PM2_TARGET_APP"
|
|
235
|
+
return 0
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
local pm2_base_name
|
|
239
|
+
pm2_base_name="${PM2_APP_NAME:-}"
|
|
240
|
+
if [[ -z "$pm2_base_name" ]]; then
|
|
241
|
+
pm2_base_name="$(read_env_value PM2_APP_NAME)"
|
|
242
|
+
fi
|
|
243
|
+
if [[ -n "$pm2_base_name" ]]; then
|
|
244
|
+
printf '%s' "${pm2_base_name}-production"
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
printf '%s' "omnizap-production"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
wait_pm2_online() {
|
|
252
|
+
local app_name="$1"
|
|
253
|
+
local waited=0
|
|
254
|
+
|
|
255
|
+
while (( waited <= PM2_WAIT_TIMEOUT_SECONDS )); do
|
|
256
|
+
local status
|
|
257
|
+
status="$(pm2_get_status "$app_name")"
|
|
258
|
+
if [[ "$status" == "online" ]]; then
|
|
259
|
+
log "PM2 app '$app_name' online."
|
|
260
|
+
return 0
|
|
261
|
+
fi
|
|
262
|
+
if [[ "$status" == "missing" ]]; then
|
|
263
|
+
fail "PM2 app '$app_name' nao encontrada durante sincronizacao."
|
|
264
|
+
fi
|
|
265
|
+
if [[ "$status" == "error" ]]; then
|
|
266
|
+
fail "Nao foi possivel consultar status do PM2 app '$app_name'."
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
sleep "$PM2_WAIT_INTERVAL_SECONDS"
|
|
270
|
+
waited=$((waited + PM2_WAIT_INTERVAL_SECONDS))
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
fail "Timeout aguardando PM2 app '$app_name' ficar online."
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
run_http_healthcheck() {
|
|
277
|
+
local metrics_enabled_raw
|
|
278
|
+
metrics_enabled_raw="$(read_env_value METRICS_ENABLED)"
|
|
279
|
+
local metrics_enabled_state
|
|
280
|
+
metrics_enabled_state="$(normalize_bool "$metrics_enabled_raw")"
|
|
281
|
+
if [[ "$metrics_enabled_state" == "false" ]]; then
|
|
282
|
+
log "Healthcheck ignorado: METRICS_ENABLED=false."
|
|
283
|
+
return 0
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
287
|
+
log "Healthcheck ignorado: comando 'curl' nao encontrado."
|
|
288
|
+
return 0
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
local host port url
|
|
292
|
+
host="$(read_env_value METRICS_HOST)"
|
|
293
|
+
port="$(read_env_value METRICS_PORT)"
|
|
294
|
+
[[ -n "$host" ]] || host="127.0.0.1"
|
|
295
|
+
if [[ "$host" == "0.0.0.0" || "$host" == "::" ]]; then
|
|
296
|
+
host="127.0.0.1"
|
|
297
|
+
fi
|
|
298
|
+
if [[ ! "$port" =~ ^[0-9]+$ ]]; then
|
|
299
|
+
port="9102"
|
|
300
|
+
fi
|
|
301
|
+
url="http://${host}:${port}/healthz"
|
|
302
|
+
|
|
303
|
+
local waited=0
|
|
304
|
+
local last_code="000"
|
|
305
|
+
while (( waited <= HEALTHCHECK_WAIT_SECONDS )); do
|
|
306
|
+
local code
|
|
307
|
+
code="$(curl --silent --show-error --max-time 5 --output /dev/null --write-out '%{http_code}' "$url" || true)"
|
|
308
|
+
if [[ "$code" == "200" ]]; then
|
|
309
|
+
log "Healthcheck HTTP ok em $url."
|
|
310
|
+
return 0
|
|
311
|
+
fi
|
|
312
|
+
last_code="$code"
|
|
313
|
+
sleep "$HEALTHCHECK_INTERVAL_SECONDS"
|
|
314
|
+
waited=$((waited + HEALTHCHECK_INTERVAL_SECONDS))
|
|
315
|
+
done
|
|
316
|
+
|
|
317
|
+
fail "Healthcheck falhou em $url (ultimo HTTP: $last_code)."
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
sync_pm2_if_needed() {
|
|
321
|
+
if [[ "$PM2_SYNC" != "1" ]]; then
|
|
322
|
+
log "Sincronizacao PM2 desativada (--no-pm2-sync)."
|
|
323
|
+
return 0
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
if ! command -v pm2 >/dev/null 2>&1; then
|
|
327
|
+
log "PM2 nao encontrado; sincronizacao automatica ignorada."
|
|
328
|
+
return 0
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
local preferred_target
|
|
332
|
+
preferred_target="$(resolve_pm2_target_app)"
|
|
333
|
+
local app_target
|
|
334
|
+
app_target="$(pm2_detect_app_name "$preferred_target")"
|
|
335
|
+
if [[ -z "$app_target" ]]; then
|
|
336
|
+
log "Nenhum app PM2 de producao detectado para sincronizar."
|
|
337
|
+
return 0
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
if [[ "$PM2_DRY_RUN" == "1" ]]; then
|
|
341
|
+
log "PM2 dry-run: executaria 'pm2 restart $app_target --update-env'."
|
|
342
|
+
return 0
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
log "Sincronizando PM2 app '$app_target'..."
|
|
346
|
+
pm2 restart "$app_target" --update-env >/dev/null
|
|
347
|
+
wait_pm2_online "$app_target"
|
|
348
|
+
|
|
349
|
+
if [[ "$RUN_HEALTHCHECK" == "1" ]]; then
|
|
350
|
+
run_http_healthcheck
|
|
351
|
+
else
|
|
352
|
+
log "Healthcheck desativado (--no-healthcheck)."
|
|
353
|
+
fi
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
while [[ $# -gt 0 ]]; do
|
|
357
|
+
case "$1" in
|
|
358
|
+
--session)
|
|
359
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --session"
|
|
360
|
+
SESSION_ID="$2"
|
|
361
|
+
shift 2
|
|
362
|
+
;;
|
|
363
|
+
--prefix)
|
|
364
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --prefix"
|
|
365
|
+
SESSION_PREFIX="$2"
|
|
366
|
+
shift 2
|
|
367
|
+
;;
|
|
368
|
+
--weight)
|
|
369
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --weight"
|
|
370
|
+
SESSION_WEIGHT="$2"
|
|
371
|
+
shift 2
|
|
372
|
+
;;
|
|
373
|
+
--primary)
|
|
374
|
+
SET_PRIMARY=1
|
|
375
|
+
shift
|
|
376
|
+
;;
|
|
377
|
+
--reuse)
|
|
378
|
+
ALLOW_REUSE=1
|
|
379
|
+
shift
|
|
380
|
+
;;
|
|
381
|
+
--reset-auth)
|
|
382
|
+
RESET_AUTH=1
|
|
383
|
+
shift
|
|
384
|
+
;;
|
|
385
|
+
--clear-auth-files)
|
|
386
|
+
CLEAR_AUTH_FILES=1
|
|
387
|
+
shift
|
|
388
|
+
;;
|
|
389
|
+
--no-connect)
|
|
390
|
+
CONNECT_NOW=0
|
|
391
|
+
shift
|
|
392
|
+
;;
|
|
393
|
+
--no-pm2-sync)
|
|
394
|
+
PM2_SYNC=0
|
|
395
|
+
shift
|
|
396
|
+
;;
|
|
397
|
+
--pm2-app)
|
|
398
|
+
[[ $# -ge 2 ]] || fail "faltou valor para --pm2-app"
|
|
399
|
+
PM2_TARGET_APP="$2"
|
|
400
|
+
shift 2
|
|
401
|
+
;;
|
|
402
|
+
--pm2-dry-run)
|
|
403
|
+
PM2_DRY_RUN=1
|
|
404
|
+
shift
|
|
405
|
+
;;
|
|
406
|
+
--no-healthcheck)
|
|
407
|
+
RUN_HEALTHCHECK=0
|
|
408
|
+
shift
|
|
409
|
+
;;
|
|
410
|
+
-h|--help)
|
|
411
|
+
usage
|
|
412
|
+
exit 0
|
|
413
|
+
;;
|
|
414
|
+
*)
|
|
415
|
+
fail "opcao invalida: $1 (use --help)"
|
|
416
|
+
;;
|
|
417
|
+
esac
|
|
418
|
+
done
|
|
419
|
+
|
|
420
|
+
[[ -f "$ENV_FILE" ]] || fail "arquivo .env nao encontrado em: $ENV_FILE"
|
|
421
|
+
is_valid_weight "$SESSION_WEIGHT" || fail "peso invalido em --weight: $SESSION_WEIGHT (use 1..1000)"
|
|
422
|
+
|
|
423
|
+
if [[ -n "$SESSION_ID" ]]; then
|
|
424
|
+
validate_session_id "$SESSION_ID" || fail "session_id invalido: \"$SESSION_ID\""
|
|
425
|
+
else
|
|
426
|
+
validate_session_id "$SESSION_PREFIX" || fail "prefixo invalido para session_id: \"$SESSION_PREFIX\""
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
declare -A SEEN_SESSION_IDS=()
|
|
430
|
+
declare -a SESSION_IDS=()
|
|
431
|
+
|
|
432
|
+
existing_session_ids_raw="$(read_env_value BAILEYS_SESSION_IDS)"
|
|
433
|
+
legacy_session_id_raw="$(read_env_value BAILEYS_AUTH_SESSION_ID)"
|
|
434
|
+
if [[ -z "$existing_session_ids_raw" ]]; then
|
|
435
|
+
existing_session_ids_raw="$legacy_session_id_raw"
|
|
436
|
+
fi
|
|
437
|
+
if [[ -z "$existing_session_ids_raw" ]]; then
|
|
438
|
+
existing_session_ids_raw="default"
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
while IFS= read -r candidate; do
|
|
442
|
+
[[ -n "$candidate" ]] || continue
|
|
443
|
+
if ! validate_session_id "$candidate"; then
|
|
444
|
+
log "ignorado session_id invalido no .env: $candidate"
|
|
445
|
+
continue
|
|
446
|
+
fi
|
|
447
|
+
if [[ -z "${SEEN_SESSION_IDS[$candidate]+x}" ]]; then
|
|
448
|
+
SEEN_SESSION_IDS["$candidate"]=1
|
|
449
|
+
SESSION_IDS+=("$candidate")
|
|
450
|
+
fi
|
|
451
|
+
done < <(split_entries "$existing_session_ids_raw")
|
|
452
|
+
|
|
453
|
+
if [[ "${#SESSION_IDS[@]}" -eq 0 ]]; then
|
|
454
|
+
SESSION_IDS=("default")
|
|
455
|
+
SEEN_SESSION_IDS["default"]=1
|
|
456
|
+
fi
|
|
457
|
+
|
|
458
|
+
if [[ -z "$SESSION_ID" ]]; then
|
|
459
|
+
base_id="${SESSION_PREFIX}-$(date -u +%Y%m%d%H%M%S)"
|
|
460
|
+
generated_id="$base_id"
|
|
461
|
+
suffix=1
|
|
462
|
+
while [[ -n "${SEEN_SESSION_IDS[$generated_id]+x}" ]]; do
|
|
463
|
+
suffix=$((suffix + 1))
|
|
464
|
+
generated_id="${base_id}-${suffix}"
|
|
465
|
+
done
|
|
466
|
+
SESSION_ID="$generated_id"
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
if [[ -n "${SEEN_SESSION_IDS[$SESSION_ID]+x}" && "$ALLOW_REUSE" != "1" ]]; then
|
|
470
|
+
fail "session_id \"$SESSION_ID\" ja existe. Use --reuse para permitir reutilizacao."
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
if [[ -z "${SEEN_SESSION_IDS[$SESSION_ID]+x}" ]]; then
|
|
474
|
+
SESSION_IDS+=("$SESSION_ID")
|
|
475
|
+
SEEN_SESSION_IDS["$SESSION_ID"]=1
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
current_primary_session_id="$(read_env_value BAILEYS_PRIMARY_SESSION_ID)"
|
|
479
|
+
if ! validate_session_id "$current_primary_session_id"; then
|
|
480
|
+
current_primary_session_id="${SESSION_IDS[0]}"
|
|
481
|
+
fi
|
|
482
|
+
if [[ -z "${SEEN_SESSION_IDS[$current_primary_session_id]+x}" ]]; then
|
|
483
|
+
current_primary_session_id="${SESSION_IDS[0]}"
|
|
484
|
+
fi
|
|
485
|
+
|
|
486
|
+
if [[ "$SET_PRIMARY" == "1" ]]; then
|
|
487
|
+
current_primary_session_id="$SESSION_ID"
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
declare -A SESSION_WEIGHTS=()
|
|
491
|
+
weights_raw="$(read_env_value BAILEYS_SESSION_WEIGHTS)"
|
|
492
|
+
while IFS= read -r raw_entry; do
|
|
493
|
+
[[ -n "$raw_entry" ]] || continue
|
|
494
|
+
separator='='
|
|
495
|
+
if [[ "$raw_entry" == *":"* && "$raw_entry" != *"="* ]]; then
|
|
496
|
+
separator=':'
|
|
497
|
+
fi
|
|
498
|
+
if [[ "$raw_entry" != *"$separator"* ]]; then
|
|
499
|
+
continue
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
raw_session="${raw_entry%%"$separator"*}"
|
|
503
|
+
raw_weight="${raw_entry#*"$separator"}"
|
|
504
|
+
if ! validate_session_id "$raw_session"; then
|
|
505
|
+
continue
|
|
506
|
+
fi
|
|
507
|
+
if is_valid_weight "$raw_weight"; then
|
|
508
|
+
SESSION_WEIGHTS["$raw_session"]="$raw_weight"
|
|
509
|
+
fi
|
|
510
|
+
done < <(split_entries "$weights_raw")
|
|
511
|
+
|
|
512
|
+
SESSION_WEIGHTS["$SESSION_ID"]="$SESSION_WEIGHT"
|
|
513
|
+
for existing_session in "${SESSION_IDS[@]}"; do
|
|
514
|
+
if [[ -z "${SESSION_WEIGHTS[$existing_session]+x}" ]]; then
|
|
515
|
+
SESSION_WEIGHTS["$existing_session"]=1
|
|
516
|
+
fi
|
|
517
|
+
done
|
|
518
|
+
|
|
519
|
+
session_ids_value="$(IFS=,; printf '%s' "${SESSION_IDS[*]}")"
|
|
520
|
+
declare -a weight_entries=()
|
|
521
|
+
for listed_session in "${SESSION_IDS[@]}"; do
|
|
522
|
+
weight_entries+=("${listed_session}=${SESSION_WEIGHTS[$listed_session]}")
|
|
523
|
+
done
|
|
524
|
+
weights_value="$(IFS=,; printf '%s' "${weight_entries[*]}")"
|
|
525
|
+
|
|
526
|
+
upsert_env_value "BAILEYS_SESSION_IDS" "$session_ids_value"
|
|
527
|
+
upsert_env_value "BAILEYS_PRIMARY_SESSION_ID" "$current_primary_session_id"
|
|
528
|
+
upsert_env_value "BAILEYS_SESSION_WEIGHTS" "$weights_value"
|
|
529
|
+
|
|
530
|
+
legacy_auth_session_id="$(read_env_value BAILEYS_AUTH_SESSION_ID)"
|
|
531
|
+
if [[ -z "$legacy_auth_session_id" || "$SET_PRIMARY" == "1" ]]; then
|
|
532
|
+
upsert_env_value "BAILEYS_AUTH_SESSION_ID" "$current_primary_session_id"
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
if [[ "$RESET_AUTH" == "1" ]]; then
|
|
536
|
+
reset_args=(--session "$SESSION_ID")
|
|
537
|
+
if [[ "$CLEAR_AUTH_FILES" == "1" ]]; then
|
|
538
|
+
reset_args+=(--clear-auth-files)
|
|
539
|
+
fi
|
|
540
|
+
bash "$PROJECT_ROOT/scripts/clear-whatsapp-session.sh" "${reset_args[@]}"
|
|
541
|
+
fi
|
|
542
|
+
|
|
543
|
+
log "Sessao preparada com sucesso."
|
|
544
|
+
log "session_id: $SESSION_ID"
|
|
545
|
+
log "BAILEYS_SESSION_IDS: $session_ids_value"
|
|
546
|
+
log "BAILEYS_PRIMARY_SESSION_ID: $current_primary_session_id"
|
|
547
|
+
sync_pm2_if_needed
|
|
548
|
+
|
|
549
|
+
if [[ "$CONNECT_NOW" == "1" ]]; then
|
|
550
|
+
require_cmd node
|
|
551
|
+
log "Inicializando schema do banco..."
|
|
552
|
+
node "$PROJECT_ROOT/database/init.js"
|
|
553
|
+
|
|
554
|
+
log "Abrindo conexao da sessao '$SESSION_ID' para leitura do QR Code..."
|
|
555
|
+
export BAILEYS_SESSION_IDS="$SESSION_ID"
|
|
556
|
+
export BAILEYS_PRIMARY_SESSION_ID="$SESSION_ID"
|
|
557
|
+
export BAILEYS_AUTH_SESSION_ID="$SESSION_ID"
|
|
558
|
+
|
|
559
|
+
log "Apos conectar no WhatsApp, use Ctrl+C para encerrar."
|
|
560
|
+
exec node "$PROJECT_ROOT/app/connection/socketController.js"
|
|
561
|
+
fi
|
|
562
|
+
|
|
563
|
+
log "Concluido sem abrir conexao. Para abrir QR agora use:"
|
|
564
|
+
log " BAILEYS_SESSION_IDS=$SESSION_ID BAILEYS_PRIMARY_SESSION_ID=$SESSION_ID BAILEYS_AUTH_SESSION_ID=$SESSION_ID node app/connection/socketController.js"
|