@hed-hog/core 0.0.400 → 0.0.402
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/dist/access-log/access-log.interceptor.d.ts +10 -0
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
- package/dist/access-log/access-log.interceptor.js +15 -1
- package/dist/access-log/access-log.interceptor.js.map +1 -1
- package/dist/ai/ai.controller.d.ts +2 -0
- package/dist/ai/ai.controller.d.ts.map +1 -1
- package/dist/ai/ai.service.d.ts +86 -2
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +304 -23
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +12 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-prompt-resolver.service.js +26 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +3 -0
- package/dist/core.module.js.map +1 -1
- package/dist/document-kit/document-kit.module.d.ts +3 -0
- package/dist/document-kit/document-kit.module.d.ts.map +1 -0
- package/dist/document-kit/document-kit.module.js +24 -0
- package/dist/document-kit/document-kit.module.js.map +1 -0
- package/dist/document-kit/document-kit.registry.d.ts +55 -0
- package/dist/document-kit/document-kit.registry.d.ts.map +1 -0
- package/dist/document-kit/document-kit.registry.js +91 -0
- package/dist/document-kit/document-kit.registry.js.map +1 -0
- package/dist/document-kit/document-kit.service.d.ts +49 -0
- package/dist/document-kit/document-kit.service.d.ts.map +1 -0
- package/dist/document-kit/document-kit.service.js +135 -0
- package/dist/document-kit/document-kit.service.js.map +1 -0
- package/dist/file/file-upload-ticket.service.d.ts +1 -1
- package/dist/file/file.controller.d.ts +3 -3
- package/dist/file/file.service.d.ts +136 -13
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +297 -27
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/mcp-file-source.schema.d.ts.map +1 -1
- package/dist/file/mcp-file-source.schema.js +8 -5
- package/dist/file/mcp-file-source.schema.js.map +1 -1
- package/dist/file/provider/abstract.provider.d.ts +33 -0
- package/dist/file/provider/abstract.provider.d.ts.map +1 -1
- package/dist/file/provider/abstract.provider.js +9 -0
- package/dist/file/provider/abstract.provider.js.map +1 -1
- package/dist/file/provider/s3-multipart.util.d.ts +17 -0
- package/dist/file/provider/s3-multipart.util.d.ts.map +1 -0
- package/dist/file/provider/s3-multipart.util.js +88 -0
- package/dist/file/provider/s3-multipart.util.js.map +1 -0
- package/dist/file/provider/s3.provider.d.ts +9 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +16 -0
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts +10 -1
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +17 -0
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/integration-profile.controller.d.ts +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +26 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +1 -0
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +3 -3
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -1
- package/dist/integration-profile/testers/ai.testers.js +46 -8
- package/dist/integration-profile/testers/ai.testers.js.map +1 -1
- package/dist/integration-profile/testers/http-probe.d.ts.map +1 -1
- package/dist/integration-profile/testers/http-probe.js +5 -3
- package/dist/integration-profile/testers/http-probe.js.map +1 -1
- package/dist/integration-profile/testers/integration-tester.types.d.ts +5 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -1
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +16 -1
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp/mcp-server.service.d.ts.map +1 -1
- package/dist/mcp/mcp-server.service.js +10 -1
- package/dist/mcp/mcp-server.service.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +2 -0
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp/tools/core-access-log.mcp-tools.d.ts +17 -0
- package/dist/mcp/tools/core-access-log.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-access-log.mcp-tools.js +74 -0
- package/dist/mcp/tools/core-access-log.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-files.mcp-tools.js +4 -4
- package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -1
- package/dist/meeting/meeting-provider.registry.d.ts +72 -0
- package/dist/meeting/meeting-provider.registry.d.ts.map +1 -1
- package/dist/meeting/meeting-provider.registry.js.map +1 -1
- package/dist/oauth/oauth-token-broker.service.d.ts +50 -1
- package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -1
- package/dist/oauth/oauth-token-broker.service.js +105 -3
- package/dist/oauth/oauth-token-broker.service.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +6 -0
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +30 -4
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/pdf/document/charts/chart-bar.d.ts +24 -0
- package/dist/pdf/document/charts/chart-bar.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-bar.js +117 -0
- package/dist/pdf/document/charts/chart-bar.js.map +1 -0
- package/dist/pdf/document/charts/chart-donut.d.ts +16 -0
- package/dist/pdf/document/charts/chart-donut.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-donut.js +89 -0
- package/dist/pdf/document/charts/chart-donut.js.map +1 -0
- package/dist/pdf/document/charts/chart-gauge.d.ts +15 -0
- package/dist/pdf/document/charts/chart-gauge.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-gauge.js +60 -0
- package/dist/pdf/document/charts/chart-gauge.js.map +1 -0
- package/dist/pdf/document/charts/chart-line.d.ts +29 -0
- package/dist/pdf/document/charts/chart-line.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-line.js +89 -0
- package/dist/pdf/document/charts/chart-line.js.map +1 -0
- package/dist/pdf/document/charts/chart-primitives.d.ts +64 -0
- package/dist/pdf/document/charts/chart-primitives.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-primitives.js +110 -0
- package/dist/pdf/document/charts/chart-primitives.js.map +1 -0
- package/dist/pdf/document/charts/chart-radar.d.ts +13 -0
- package/dist/pdf/document/charts/chart-radar.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-radar.js +83 -0
- package/dist/pdf/document/charts/chart-radar.js.map +1 -0
- package/dist/pdf/document/charts/chart-scale.d.ts +39 -0
- package/dist/pdf/document/charts/chart-scale.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-scale.js +118 -0
- package/dist/pdf/document/charts/chart-scale.js.map +1 -0
- package/dist/pdf/document/charts/index.d.ts +8 -0
- package/dist/pdf/document/charts/index.d.ts.map +1 -0
- package/dist/pdf/document/charts/index.js +24 -0
- package/dist/pdf/document/charts/index.js.map +1 -0
- package/dist/pdf/document/index.d.ts +8 -0
- package/dist/pdf/document/index.d.ts.map +1 -0
- package/dist/pdf/document/index.js +24 -0
- package/dist/pdf/document/index.js.map +1 -0
- package/dist/pdf/document/pdf-document.chrome.d.ts +20 -0
- package/dist/pdf/document/pdf-document.chrome.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.chrome.js +36 -0
- package/dist/pdf/document/pdf-document.chrome.js.map +1 -0
- package/dist/pdf/document/pdf-document.css.d.ts +20 -0
- package/dist/pdf/document/pdf-document.css.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.css.js +327 -0
- package/dist/pdf/document/pdf-document.css.js.map +1 -0
- package/dist/pdf/document/pdf-document.renderer.d.ts +10 -0
- package/dist/pdf/document/pdf-document.renderer.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.renderer.js +226 -0
- package/dist/pdf/document/pdf-document.renderer.js.map +1 -0
- package/dist/pdf/document/pdf-document.service.d.ts +27 -0
- package/dist/pdf/document/pdf-document.service.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.service.js +81 -0
- package/dist/pdf/document/pdf-document.service.js.map +1 -0
- package/dist/pdf/document/pdf-document.theme.d.ts +41 -0
- package/dist/pdf/document/pdf-document.theme.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.theme.js +105 -0
- package/dist/pdf/document/pdf-document.theme.js.map +1 -0
- package/dist/pdf/document/pdf-document.types.d.ts +164 -0
- package/dist/pdf/document/pdf-document.types.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.types.js +18 -0
- package/dist/pdf/document/pdf-document.types.js.map +1 -0
- package/dist/pdf/pdf.module.d.ts.map +1 -1
- package/dist/pdf/pdf.module.js +3 -2
- package/dist/pdf/pdf.module.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +75 -1
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +167 -63
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/pdf/templates/commercial-proposal.template.d.ts +96 -0
- package/dist/pdf/templates/commercial-proposal.template.d.ts.map +1 -0
- package/dist/pdf/templates/commercial-proposal.template.js +276 -0
- package/dist/pdf/templates/commercial-proposal.template.js.map +1 -0
- package/dist/setting/favicon.controller.d.ts +26 -0
- package/dist/setting/favicon.controller.d.ts.map +1 -0
- package/dist/setting/favicon.controller.js +130 -0
- package/dist/setting/favicon.controller.js.map +1 -0
- package/dist/setting/setting.module.d.ts.map +1 -1
- package/dist/setting/setting.module.js +2 -1
- package/dist/setting/setting.module.js.map +1 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +3 -0
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/task/task.service.d.ts +6 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +44 -13
- package/dist/task/task.service.js.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +35 -2
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.service.js +107 -22
- package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -1
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/route.yaml +8 -0
- package/hedhog/data/setting_group.yaml +52 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +85 -2
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +576 -491
- package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +29 -50
- package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +146 -50
- package/hedhog/frontend/messages/en.json +81 -2
- package/hedhog/frontend/messages/pt.json +81 -2
- package/hedhog/table/file.yaml +3 -1
- package/package.json +5 -5
- package/src/access-log/access-log.interceptor.spec.ts +50 -0
- package/src/access-log/access-log.interceptor.ts +18 -0
- package/src/ai/ai-attachment.fragments.spec.ts +56 -3
- package/src/ai/ai.service.responses.spec.ts +429 -0
- package/src/ai/ai.service.ts +409 -27
- package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +44 -0
- package/src/ai-instruction/ai-prompt-resolver.service.ts +29 -0
- package/src/core.module.ts +3 -0
- package/src/document-kit/document-kit.module.ts +12 -0
- package/src/document-kit/document-kit.registry.ts +125 -0
- package/src/document-kit/document-kit.service.spec.ts +156 -0
- package/src/document-kit/document-kit.service.ts +173 -0
- package/src/file/file-direct-upload.spec.ts +344 -0
- package/src/file/file.service.spec.ts +168 -1
- package/src/file/file.service.ts +435 -36
- package/src/file/mcp-file-source.schema.ts +8 -5
- package/src/file/provider/abstract.provider.ts +57 -0
- package/src/file/provider/s3-multipart.spec.ts +123 -0
- package/src/file/provider/s3-multipart.util.ts +126 -0
- package/src/file/provider/s3.provider.ts +65 -1
- package/src/file/provider/spaces.provider.ts +67 -1
- package/src/index.ts +5 -0
- package/src/integration-profile/integration-profile.secrets.ts +26 -0
- package/src/integration-profile/integration-profile.service.ts +1 -0
- package/src/integration-profile/testers/ai.testers.ts +70 -5
- package/src/integration-profile/testers/http-probe.ts +5 -3
- package/src/integration-profile/testers/integration-tester.types.ts +5 -0
- package/src/integration-profile/testers/oauth.testers.spec.ts +13 -0
- package/src/integration-profile/testers/remaining.testers.spec.ts +65 -1
- package/src/integration-profile/testers/tester-coverage.spec.ts +12 -0
- package/src/mail/mail.service.spec.ts +25 -0
- package/src/mail/mail.service.ts +18 -1
- package/src/mcp/mcp-server.service.spec.ts +16 -0
- package/src/mcp/mcp-server.service.ts +10 -1
- package/src/mcp/mcp.module.ts +2 -0
- package/src/mcp/tools/core-access-log.mcp-tools.ts +66 -0
- package/src/mcp/tools/core-files.mcp-tools.ts +4 -4
- package/src/meeting/meeting-provider.registry.ts +80 -0
- package/src/oauth/oauth-token-broker.service.spec.ts +108 -0
- package/src/oauth/oauth-token-broker.service.ts +129 -2
- package/src/oauth/providers/microsoft-entra-id.provider.spec.ts +133 -0
- package/src/oauth/providers/microsoft-entra-id.provider.ts +29 -2
- package/src/pdf/document/charts/chart-bar.ts +217 -0
- package/src/pdf/document/charts/chart-donut.ts +163 -0
- package/src/pdf/document/charts/chart-gauge.ts +94 -0
- package/src/pdf/document/charts/chart-line.ts +159 -0
- package/src/pdf/document/charts/chart-primitives.ts +188 -0
- package/src/pdf/document/charts/chart-radar.ts +136 -0
- package/src/pdf/document/charts/chart-scale.spec.ts +93 -0
- package/src/pdf/document/charts/chart-scale.ts +141 -0
- package/src/pdf/document/charts/charts.spec.ts +157 -0
- package/src/pdf/document/charts/index.ts +7 -0
- package/src/pdf/document/index.ts +7 -0
- package/src/pdf/document/pdf-document.chrome.ts +38 -0
- package/src/pdf/document/pdf-document.css.ts +326 -0
- package/src/pdf/document/pdf-document.renderer.spec.ts +172 -0
- package/src/pdf/document/pdf-document.renderer.ts +317 -0
- package/src/pdf/document/pdf-document.service.ts +77 -0
- package/src/pdf/document/pdf-document.theme.ts +146 -0
- package/src/pdf/document/pdf-document.types.ts +149 -0
- package/src/pdf/pdf.module.ts +3 -2
- package/src/pdf/pdf.service.spec.ts +131 -0
- package/src/pdf/pdf.service.ts +256 -82
- package/src/pdf/templates/commercial-proposal.template.spec.ts +177 -0
- package/src/pdf/templates/commercial-proposal.template.ts +400 -0
- package/src/setting/favicon.controller.spec.ts +92 -0
- package/src/setting/favicon.controller.ts +135 -0
- package/src/setting/setting.module.ts +2 -1
- package/src/setting/setting.service.ts +3 -0
- package/src/task/task.service.spec.ts +77 -1
- package/src/task/task.service.ts +58 -14
- package/src/whatsapp-sender/whatsapp-sender.service.spec.ts +263 -0
- package/src/whatsapp-sender/whatsapp-sender.service.ts +154 -16
|
@@ -32,8 +32,17 @@ export type WhatsappSendInput = {
|
|
|
32
32
|
phoneNumberId?: string;
|
|
33
33
|
/** Template aprovado pela Meta — sem ele o envio só chega na janela de 24h. */
|
|
34
34
|
template?: { name: string; language?: string; params?: string[] };
|
|
35
|
+
/**
|
|
36
|
+
* Providers aceitos para este envio. Vazio aceita qualquer um. Quem manda em
|
|
37
|
+
* lote para alunos restringe ao oficial: disparo em massa pela Evolution
|
|
38
|
+
* (WhatsApp Web) derruba e bane o número.
|
|
39
|
+
*/
|
|
40
|
+
providers?: readonly string[];
|
|
35
41
|
};
|
|
36
42
|
|
|
43
|
+
/** Só a Cloud API da Meta — para envio em volume a quem não é da equipe. */
|
|
44
|
+
export const WHATSAPP_OFFICIAL_ONLY: readonly string[] = ['whatsapp-official'];
|
|
45
|
+
|
|
37
46
|
type ResolvedProfile = {
|
|
38
47
|
id: number;
|
|
39
48
|
slug: string;
|
|
@@ -46,13 +55,27 @@ type ResolvedProfile = {
|
|
|
46
55
|
* resultado diferente. Um HTTP 4xx da Graph API (id inexistente, sem
|
|
47
56
|
* permissão, token ruim) não muda em cinco minutos; um 5xx ou falha de rede
|
|
48
57
|
* pode ser uma instabilidade passageira do provedor.
|
|
58
|
+
*
|
|
59
|
+
* `isTransient` repete o `retryable` no formato que o `TransientError.is` da
|
|
60
|
+
* fila reconhece por duck-typing (o core não depende do queue): com ele as
|
|
61
|
+
* tentativas intermediárias vão ao Sentry como warning, e só a última como erro.
|
|
62
|
+
*
|
|
63
|
+
* `reason` marca as falhas que valem para a instância inteira, não para a
|
|
64
|
+
* mensagem: com a sessão fechada ou a credencial recusada, a próxima mensagem
|
|
65
|
+
* do mesmo lote tem o mesmo destino, e quem envia em série pode parar ali.
|
|
49
66
|
*/
|
|
67
|
+
export type WhatsappSendErrorReason = 'session_closed' | 'unauthorized';
|
|
68
|
+
|
|
50
69
|
export class WhatsappSendError extends BadRequestException {
|
|
70
|
+
readonly isTransient: boolean;
|
|
71
|
+
|
|
51
72
|
constructor(
|
|
52
73
|
message: string,
|
|
53
74
|
public readonly retryable: boolean,
|
|
75
|
+
public readonly reason?: WhatsappSendErrorReason,
|
|
54
76
|
) {
|
|
55
77
|
super(message);
|
|
78
|
+
this.isTransient = retryable;
|
|
56
79
|
}
|
|
57
80
|
}
|
|
58
81
|
|
|
@@ -73,7 +96,7 @@ export class WhatsappSenderService {
|
|
|
73
96
|
) {}
|
|
74
97
|
|
|
75
98
|
async send(input: WhatsappSendInput): Promise<void> {
|
|
76
|
-
const profile = await this.resolveProfile(input.profileSlug);
|
|
99
|
+
const profile = await this.resolveProfile(input.profileSlug, input.providers);
|
|
77
100
|
|
|
78
101
|
switch (profile.providerSlug) {
|
|
79
102
|
case 'evolution-api':
|
|
@@ -88,9 +111,12 @@ export class WhatsappSenderService {
|
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
/** Diz se há perfil utilizável, para o chamador decidir por um fallback sem tentar o envio. */
|
|
91
|
-
async isConfigured(
|
|
114
|
+
async isConfigured(
|
|
115
|
+
profileSlug?: string,
|
|
116
|
+
providers?: readonly string[],
|
|
117
|
+
): Promise<boolean> {
|
|
92
118
|
try {
|
|
93
|
-
const profile = await this.resolveProfile(profileSlug);
|
|
119
|
+
const profile = await this.resolveProfile(profileSlug, providers);
|
|
94
120
|
|
|
95
121
|
if (profile.providerSlug === 'evolution-api') {
|
|
96
122
|
const { baseUrl, token, instance } = this.readEvolutionConfig(profile);
|
|
@@ -150,8 +176,12 @@ export class WhatsappSenderService {
|
|
|
150
176
|
return normalized;
|
|
151
177
|
}
|
|
152
178
|
|
|
153
|
-
private async resolveProfile(
|
|
179
|
+
private async resolveProfile(
|
|
180
|
+
profileSlug?: string,
|
|
181
|
+
providers?: readonly string[],
|
|
182
|
+
): Promise<ResolvedProfile> {
|
|
154
183
|
const slug = String(profileSlug ?? '').trim();
|
|
184
|
+
const allowed = providers?.length ? [...providers] : null;
|
|
155
185
|
|
|
156
186
|
const profile = slug
|
|
157
187
|
? await this.prisma.integration_profile.findUnique({
|
|
@@ -162,7 +192,11 @@ export class WhatsappSenderService {
|
|
|
162
192
|
},
|
|
163
193
|
})
|
|
164
194
|
: await this.prisma.integration_profile.findFirst({
|
|
165
|
-
where: {
|
|
195
|
+
where: {
|
|
196
|
+
is_active: true,
|
|
197
|
+
integration_type: { slug: 'whatsapp' },
|
|
198
|
+
...(allowed ? { integration_provider: { slug: { in: allowed } } } : {}),
|
|
199
|
+
},
|
|
166
200
|
orderBy: { updated_at: 'desc' },
|
|
167
201
|
include: {
|
|
168
202
|
integration_provider: { select: { slug: true } },
|
|
@@ -174,7 +208,9 @@ export class WhatsappSenderService {
|
|
|
174
208
|
throw new NotFoundException(
|
|
175
209
|
slug
|
|
176
210
|
? `Perfil de integração "${slug}" não encontrado.`
|
|
177
|
-
:
|
|
211
|
+
: allowed
|
|
212
|
+
? `Nenhum perfil de WhatsApp ativo do provider ${allowed.join(' ou ')} encontrado.`
|
|
213
|
+
: 'Nenhum perfil de integração de WhatsApp ativo encontrado.',
|
|
178
214
|
);
|
|
179
215
|
}
|
|
180
216
|
|
|
@@ -184,10 +220,21 @@ export class WhatsappSenderService {
|
|
|
184
220
|
);
|
|
185
221
|
}
|
|
186
222
|
|
|
223
|
+
const providerSlug = String((profile as any).integration_provider?.slug ?? '');
|
|
224
|
+
|
|
225
|
+
// Perfil escolhido à mão que não é do provider exigido: é configuração, não
|
|
226
|
+
// instabilidade — reenviar daria no mesmo.
|
|
227
|
+
if (allowed && !allowed.includes(providerSlug)) {
|
|
228
|
+
throw new WhatsappSendError(
|
|
229
|
+
`Perfil de integração "${profile.slug}" usa o provider "${providerSlug}", e este envio só aceita ${allowed.join(' ou ')}.`,
|
|
230
|
+
false,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
187
234
|
return {
|
|
188
235
|
id: profile.id,
|
|
189
236
|
slug: profile.slug,
|
|
190
|
-
providerSlug
|
|
237
|
+
providerSlug,
|
|
191
238
|
config: ((profile.config ?? {}) as Record<string, unknown>),
|
|
192
239
|
};
|
|
193
240
|
}
|
|
@@ -242,17 +289,62 @@ export class WhatsappSenderService {
|
|
|
242
289
|
const detail = await response.text().catch(() => '');
|
|
243
290
|
|
|
244
291
|
if (response.status === 401) {
|
|
245
|
-
throw new
|
|
292
|
+
throw new WhatsappSendError(
|
|
246
293
|
`Credenciais do WhatsApp inválidas (401). Verifique o token do perfil "${profile.slug}".`,
|
|
294
|
+
false,
|
|
295
|
+
'unauthorized',
|
|
247
296
|
);
|
|
248
297
|
}
|
|
249
298
|
|
|
250
|
-
|
|
299
|
+
// Número sem conta no WhatsApp: a Evolution responde 400 com
|
|
300
|
+
// `exists: false`. É dado do cadastro, não instabilidade — reenviar não
|
|
301
|
+
// muda nada, e o chamador precisa saber disso para não insistir.
|
|
302
|
+
if (isEvolutionNumberMissing(detail)) {
|
|
303
|
+
throw new WhatsappSendError(
|
|
304
|
+
`O número ${target} não tem WhatsApp (Evolution: exists=false).`,
|
|
305
|
+
false,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Sessão da instância caída (celular deslogado ou Baileys reconectando):
|
|
310
|
+
// a Evolution responde 500 "Connection Closed". Segue retentável porque a
|
|
311
|
+
// reconexão costuma ser sozinha; se persistir, quem ler a falha precisa
|
|
312
|
+
// saber que a ação é reconectar a instância, não mexer no envio.
|
|
313
|
+
if (isEvolutionSessionClosed(detail)) {
|
|
314
|
+
throw new WhatsappSendError(
|
|
315
|
+
`A instância "${instance}" da Evolution está desconectada do WhatsApp (Connection Closed). Reconecte a instância (QR code) do perfil "${profile.slug}".`,
|
|
316
|
+
true,
|
|
317
|
+
'session_closed',
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Mesmo critério do provider oficial: 4xx é recusa estrutural, 5xx e
|
|
322
|
+
// 429 podem passar numa próxima tentativa.
|
|
323
|
+
throw new WhatsappSendError(
|
|
251
324
|
`Evolution API recusou o envio (HTTP ${response.status}): ${detail.slice(0, 300)}`,
|
|
325
|
+
response.status >= 500 || response.status === 429,
|
|
252
326
|
);
|
|
253
327
|
}
|
|
254
328
|
}
|
|
255
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Número padrão do perfil, mantido pela library `whatsapp` (sync com o WABA
|
|
332
|
+
* ou registro na primeira mensagem recebida).
|
|
333
|
+
*
|
|
334
|
+
* A tabela não é do core: num projeto sem a library `whatsapp` o delegate não
|
|
335
|
+
* existe, e o envio segue sem padrão — cai no erro de phone_number_id ausente.
|
|
336
|
+
*/
|
|
337
|
+
private async resolveDefaultPhoneNumberId(profileId: number): Promise<string> {
|
|
338
|
+
const delegate = (this.prisma as any).whatsapp_phone_number;
|
|
339
|
+
if (!delegate?.findFirst) return '';
|
|
340
|
+
|
|
341
|
+
const found = await delegate.findFirst({
|
|
342
|
+
where: { integration_profile_id: profileId, is_default: true, status: 'active' },
|
|
343
|
+
select: { phone_number_id: true },
|
|
344
|
+
});
|
|
345
|
+
return String(found?.phone_number_id ?? '').trim();
|
|
346
|
+
}
|
|
347
|
+
|
|
256
348
|
private async sendWithOfficial(
|
|
257
349
|
profile: ResolvedProfile,
|
|
258
350
|
input: WhatsappSendInput,
|
|
@@ -278,15 +370,16 @@ export class WhatsappSenderService {
|
|
|
278
370
|
);
|
|
279
371
|
}
|
|
280
372
|
|
|
281
|
-
// O phone_number_id não vive no perfil: um WABA pode ter vários números
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
373
|
+
// O phone_number_id não vive no perfil: um WABA pode ter vários números.
|
|
374
|
+
// Quem envia pode dizer por qual deles a mensagem sai; sem isso, vale o
|
|
375
|
+
// número padrão do perfil.
|
|
376
|
+
const phoneNumberId =
|
|
377
|
+
String(input.phoneNumberId ?? profile.config.phone_number_id ?? '').trim() ||
|
|
378
|
+
(await this.resolveDefaultPhoneNumberId(profile.id));
|
|
286
379
|
|
|
287
380
|
if (!phoneNumberId) {
|
|
288
381
|
throw new BadRequestException(
|
|
289
|
-
|
|
382
|
+
`Perfil de WhatsApp "${profile.slug}" não tem número padrão: informe o phone_number_id do número remetente ou mande uma mensagem para o número, que ele é registrado.`,
|
|
290
383
|
);
|
|
291
384
|
}
|
|
292
385
|
|
|
@@ -308,7 +401,7 @@ export class WhatsappSenderService {
|
|
|
308
401
|
type: 'body',
|
|
309
402
|
parameters: (input.template.params ?? [input.text]).map((text) => ({
|
|
310
403
|
type: 'text',
|
|
311
|
-
text,
|
|
404
|
+
text: toTemplateParam(text),
|
|
312
405
|
})),
|
|
313
406
|
},
|
|
314
407
|
],
|
|
@@ -359,6 +452,51 @@ export class WhatsappSenderService {
|
|
|
359
452
|
}
|
|
360
453
|
}
|
|
361
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Reconhece o 400 da Evolution para destino inexistente:
|
|
457
|
+
* `{"response":{"message":[{"exists":false,"number":"55..."}]}}`.
|
|
458
|
+
*/
|
|
459
|
+
function isEvolutionNumberMissing(detail: string): boolean {
|
|
460
|
+
try {
|
|
461
|
+
const messages = JSON.parse(detail)?.response?.message;
|
|
462
|
+
return (
|
|
463
|
+
Array.isArray(messages) &&
|
|
464
|
+
messages.some((item) => item && typeof item === 'object' && item.exists === false)
|
|
465
|
+
);
|
|
466
|
+
} catch {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Reconhece o 500 da Evolution com a sessão do WhatsApp fechada:
|
|
473
|
+
* `{"response":{"message":"Connection Closed"}}` (às vezes com a string num array).
|
|
474
|
+
*/
|
|
475
|
+
function isEvolutionSessionClosed(detail: string): boolean {
|
|
476
|
+
try {
|
|
477
|
+
const message = JSON.parse(detail)?.response?.message;
|
|
478
|
+
const texts = Array.isArray(message) ? message : [message];
|
|
479
|
+
return texts.some(
|
|
480
|
+
(item) => typeof item === 'string' && /connection closed/i.test(item),
|
|
481
|
+
);
|
|
482
|
+
} catch {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* A Meta recusa parâmetro de template com quebra de linha, tab ou mais de
|
|
489
|
+
* quatro espaços seguidos (erro 132018) — e o texto que os módulos montam para
|
|
490
|
+
* o WhatsApp vem em parágrafos. Vazio também é recusado.
|
|
491
|
+
*/
|
|
492
|
+
export function toTemplateParam(text: unknown): string {
|
|
493
|
+
const flat = String(text ?? '')
|
|
494
|
+
.replace(/\s*[\r\n\t]+\s*/g, ' · ')
|
|
495
|
+
.replace(/ {2,}/g, ' ')
|
|
496
|
+
.trim();
|
|
497
|
+
return flat || '-';
|
|
498
|
+
}
|
|
499
|
+
|
|
362
500
|
/** Evolution espera base64 puro; um data URL vindo do chamador quebraria o envio. */
|
|
363
501
|
function stripDataUrlPrefix(image: string): string {
|
|
364
502
|
const match = /^data:[^;]+;base64,(.*)$/i.exec(image);
|