evo360-types 1.3.715 → 1.3.719
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/apps/evo-chat/forward/zod-schemas.d.ts +615 -0
- package/dist/apps/evo-chat/forward/zod-schemas.js +171 -0
- package/dist/apps/evo-chat/forward/zod-schemas.ts +193 -0
- package/dist/apps/evo-chat/thread-message/zod-schemas.d.ts +222 -0
- package/dist/apps/evo-chat/thread-message/zod-schemas.js +3 -0
- package/dist/apps/evo-chat/thread-message/zod-schemas.ts +3 -0
- package/dist/apps/evo-csq/zod-schemas.d.ts +30 -30
- package/dist/apps/evo-finops/zod-schemas.d.ts +2 -2
- package/dist/apps/evo-med/appointment/zod-schemas.d.ts +8 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.ts +1 -0
- package/dist/types/evo-chat/forward/index.d.ts +213 -0
- package/dist/types/evo-chat/forward/index.js +80 -0
- package/dist/types/evo-chat/forward/index.ts +303 -0
- package/dist/types/evo-chat/index.d.ts +3 -0
- package/dist/types/evo-chat/index.js +3 -0
- package/dist/types/evo-chat/index.ts +3 -0
- package/dist/types/evo-chat/thread-message/index.d.ts +4 -0
- package/dist/types/evo-chat/thread-message/index.ts +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FORWARD_TIMELINE_EVENT_TYPE = exports.FORWARD_STALE_AFTER_MS = exports.FORWARD_EMAIL_SUBJECT_MAX_CHARS = exports.FORWARD_INTRO_MAX_CHARS = exports.FORWARD_MAX_RECIPIENTS = exports.FORWARD_MAX_MESSAGES = exports.FORWARD_ITEMS_COLLECTION = exports.FORWARDS_COLLECTION = void 0;
|
|
4
|
+
exports.resolveForwardContent = resolveForwardContent;
|
|
5
|
+
exports.buildForwardItemId = buildForwardItemId;
|
|
6
|
+
/**
|
|
7
|
+
* feat-282 — encaminhamento de mensagens no Hub Omni.
|
|
8
|
+
*
|
|
9
|
+
* A Cloud API da Meta não tem forward: o encaminhamento é nosso. Cada ação do
|
|
10
|
+
* operador vira um LOTE (`IForwardBatch`) em
|
|
11
|
+
* `tenants/{t}/apps/evo-chat/forwards/{forwardId}`, com um item por
|
|
12
|
+
* (destinatário × mensagem) na subcoleção `items/` e, por destinatário, um item
|
|
13
|
+
* `intro` opcional (texto de apresentação). Cada entrega é uma mensagem NORMAL de
|
|
14
|
+
* saída na thread de destino, com o bloco `forward` (`IMessageForward`) apontando
|
|
15
|
+
* para a origem. A mensagem de origem só ganha `forward_count`.
|
|
16
|
+
*/
|
|
17
|
+
// ── Constants ──
|
|
18
|
+
exports.FORWARDS_COLLECTION = "forwards";
|
|
19
|
+
exports.FORWARD_ITEMS_COLLECTION = "items";
|
|
20
|
+
/** Tetos do lote (plan.md D6). Constantes de plataforma, sem config por tenant. */
|
|
21
|
+
exports.FORWARD_MAX_MESSAGES = 20;
|
|
22
|
+
exports.FORWARD_MAX_RECIPIENTS = 10;
|
|
23
|
+
exports.FORWARD_INTRO_MAX_CHARS = 1000;
|
|
24
|
+
/** Teto do assunto (RFC 5322), igual ao do composer de e-mail do Omni. */
|
|
25
|
+
exports.FORWARD_EMAIL_SUBJECT_MAX_CHARS = 998;
|
|
26
|
+
/** Lote `running` sem atualização há mais que isto é mostrado como interrompido. */
|
|
27
|
+
exports.FORWARD_STALE_AFTER_MS = 10 * 60 * 1000;
|
|
28
|
+
/** Evento de timeline gravado na thread de ORIGEM ao criar o lote. */
|
|
29
|
+
exports.FORWARD_TIMELINE_EVENT_TYPE = "messages_forwarded";
|
|
30
|
+
const MEDIA_KINDS = new Set(["image", "video", "audio", "document"]);
|
|
31
|
+
/** Canais que não entram como origem (e-mail, SMS). */
|
|
32
|
+
const UNSUPPORTED_SOURCE_CHANNELS = new Set(["email", "sms"]);
|
|
33
|
+
/**
|
|
34
|
+
* Decide se uma mensagem pode ser encaminhada e como vira conteúdo no destino.
|
|
35
|
+
*
|
|
36
|
+
* `dest = null` é a checagem agnóstica do modo de seleção no front (sem canal
|
|
37
|
+
* escolhido). Regras que dependem do destino: documento não vai para Instagram;
|
|
38
|
+
* áudio e vídeo não vão para e-mail. O teto de tamanho e o MIME aceito como
|
|
39
|
+
* anexo NÃO ficam aqui: dependem do objeto no Storage e da allowlist do provider.
|
|
40
|
+
*/
|
|
41
|
+
function resolveForwardContent(m, dest) {
|
|
42
|
+
const direction = String(m.direction ?? "").toLowerCase();
|
|
43
|
+
if (direction !== "in" && direction !== "out")
|
|
44
|
+
return { ok: false, reason: "type_not_supported" };
|
|
45
|
+
if (m.revoked === true)
|
|
46
|
+
return { ok: false, reason: "type_not_supported" };
|
|
47
|
+
if (m.deleted_at !== undefined && m.deleted_at !== null)
|
|
48
|
+
return { ok: false, reason: "type_not_supported" };
|
|
49
|
+
const channelType = String(m.channel_type ?? "").toLowerCase();
|
|
50
|
+
if (UNSUPPORTED_SOURCE_CHANNELS.has(channelType))
|
|
51
|
+
return { ok: false, reason: "type_not_supported" };
|
|
52
|
+
const type = String(m.type ?? "").toLowerCase();
|
|
53
|
+
if (type === "text") {
|
|
54
|
+
const text = (m.body?.text ?? "").trim();
|
|
55
|
+
if (!text)
|
|
56
|
+
return { ok: false, reason: "type_not_supported" };
|
|
57
|
+
return { ok: true, kind: "text", text };
|
|
58
|
+
}
|
|
59
|
+
if (type === "template") {
|
|
60
|
+
const text = (m.template?.rendered?.text ?? "").trim();
|
|
61
|
+
if (!text)
|
|
62
|
+
return { ok: false, reason: "type_not_supported" };
|
|
63
|
+
return { ok: true, kind: "text", text };
|
|
64
|
+
}
|
|
65
|
+
if (MEDIA_KINDS.has(type)) {
|
|
66
|
+
if (dest === "email" && (type === "audio" || type === "video"))
|
|
67
|
+
return { ok: false, reason: "type_not_supported" };
|
|
68
|
+
const uri = (m.media?.gcs_uri ?? "").trim();
|
|
69
|
+
if (!uri)
|
|
70
|
+
return { ok: false, reason: "media_unavailable" };
|
|
71
|
+
if (type === "document" && dest === "instagram")
|
|
72
|
+
return { ok: false, reason: "type_not_supported" };
|
|
73
|
+
return { ok: true, kind: type };
|
|
74
|
+
}
|
|
75
|
+
return { ok: false, reason: "type_not_supported" };
|
|
76
|
+
}
|
|
77
|
+
/** Id determinístico do item: um destinatário aparece uma vez no lote, então é único. */
|
|
78
|
+
function buildForwardItemId(recipientThreadId, messageId) {
|
|
79
|
+
return `${recipientThreadId}__${messageId ?? "intro"}`;
|
|
80
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import type { IFireDoc } from "../../shared";
|
|
2
|
+
import type { IThreadMessage } from "../thread-message";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* feat-282 — encaminhamento de mensagens no Hub Omni.
|
|
6
|
+
*
|
|
7
|
+
* A Cloud API da Meta não tem forward: o encaminhamento é nosso. Cada ação do
|
|
8
|
+
* operador vira um LOTE (`IForwardBatch`) em
|
|
9
|
+
* `tenants/{t}/apps/evo-chat/forwards/{forwardId}`, com um item por
|
|
10
|
+
* (destinatário × mensagem) na subcoleção `items/` e, por destinatário, um item
|
|
11
|
+
* `intro` opcional (texto de apresentação). Cada entrega é uma mensagem NORMAL de
|
|
12
|
+
* saída na thread de destino, com o bloco `forward` (`IMessageForward`) apontando
|
|
13
|
+
* para a origem. A mensagem de origem só ganha `forward_count`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ── Constants ──
|
|
17
|
+
|
|
18
|
+
export const FORWARDS_COLLECTION = "forwards" as const;
|
|
19
|
+
export const FORWARD_ITEMS_COLLECTION = "items" as const;
|
|
20
|
+
|
|
21
|
+
/** Tetos do lote (plan.md D6). Constantes de plataforma, sem config por tenant. */
|
|
22
|
+
export const FORWARD_MAX_MESSAGES = 20;
|
|
23
|
+
export const FORWARD_MAX_RECIPIENTS = 10;
|
|
24
|
+
export const FORWARD_INTRO_MAX_CHARS = 1000;
|
|
25
|
+
/** Teto do assunto (RFC 5322), igual ao do composer de e-mail do Omni. */
|
|
26
|
+
export const FORWARD_EMAIL_SUBJECT_MAX_CHARS = 998;
|
|
27
|
+
|
|
28
|
+
/** Lote `running` sem atualização há mais que isto é mostrado como interrompido. */
|
|
29
|
+
export const FORWARD_STALE_AFTER_MS = 10 * 60 * 1000;
|
|
30
|
+
|
|
31
|
+
/** Evento de timeline gravado na thread de ORIGEM ao criar o lote. */
|
|
32
|
+
export const FORWARD_TIMELINE_EVENT_TYPE = "messages_forwarded" as const;
|
|
33
|
+
|
|
34
|
+
// ── Enums ──
|
|
35
|
+
|
|
36
|
+
export type ForwardBatchStatus = "running" | "done" | "done_with_failures";
|
|
37
|
+
|
|
38
|
+
export type ForwardItemStatus = "pending" | "sending" | "sent" | "failed" | "skipped";
|
|
39
|
+
|
|
40
|
+
/** Motivos decididos na pré-validação (item nasce `skipped`, nenhuma chamada ao provedor). */
|
|
41
|
+
export type ForwardSkipReason =
|
|
42
|
+
| "window_closed"
|
|
43
|
+
| "type_not_supported"
|
|
44
|
+
| "media_too_large"
|
|
45
|
+
| "media_unavailable"
|
|
46
|
+
| "channel_not_supported"
|
|
47
|
+
| "attachments_over_limit"
|
|
48
|
+
| "recipient_address_missing"
|
|
49
|
+
| "email_identity_conflict";
|
|
50
|
+
|
|
51
|
+
/** Motivos de falha no envio (item passou por `sending`). */
|
|
52
|
+
export type ForwardFailReason =
|
|
53
|
+
| "provider_error"
|
|
54
|
+
| "window_closed"
|
|
55
|
+
| "send_budget_exceeded"
|
|
56
|
+
| "interrupted";
|
|
57
|
+
|
|
58
|
+
export type ForwardItemReason = ForwardSkipReason | ForwardFailReason;
|
|
59
|
+
|
|
60
|
+
export type ForwardContentKind = "text" | "image" | "video" | "audio" | "document";
|
|
61
|
+
|
|
62
|
+
/** Canais aceitos como DESTINO. E-mail é agregado: um e-mail por destinatário. SMS fica fora. */
|
|
63
|
+
export type ForwardDestinationKind = "whatsapp" | "instagram" | "email";
|
|
64
|
+
|
|
65
|
+
/** `hub-waba`, `hub-instagram` ou o provider do canal de e-mail (`smtp`, `amazon-ses`, ...). */
|
|
66
|
+
export type ForwardRecipientProvider = string;
|
|
67
|
+
|
|
68
|
+
export type ForwardSpecialKind = "doctor" | "secretary" | "employee";
|
|
69
|
+
|
|
70
|
+
// ── Batch ──
|
|
71
|
+
|
|
72
|
+
export interface IForwardActor {
|
|
73
|
+
user_id: string;
|
|
74
|
+
display_name: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IForwardSource {
|
|
78
|
+
thread_id: string;
|
|
79
|
+
ticket_id: string;
|
|
80
|
+
/** Departamento do ticket de origem: é onde o ticket novo do destino abre (Q4-A). */
|
|
81
|
+
department_id: string | null;
|
|
82
|
+
contact_id: string;
|
|
83
|
+
contact_name: string;
|
|
84
|
+
channel_id: string | null;
|
|
85
|
+
channel_type: string | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface IForwardRecipient {
|
|
89
|
+
thread_id: string;
|
|
90
|
+
contact_id: string;
|
|
91
|
+
contact_name: string;
|
|
92
|
+
channel_id: string;
|
|
93
|
+
channel_type: string;
|
|
94
|
+
provider: ForwardRecipientProvider;
|
|
95
|
+
special_kind?: ForwardSpecialKind | null;
|
|
96
|
+
/** Destino e-mail: endereço resolvido na prévia. O worker usa este, não resolve de novo. */
|
|
97
|
+
email_address?: string | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface IForwardDispatchState {
|
|
101
|
+
claimed_at: Date;
|
|
102
|
+
finished_at?: Date | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface IForwardCounts {
|
|
106
|
+
total: number;
|
|
107
|
+
sent: number;
|
|
108
|
+
failed: number;
|
|
109
|
+
skipped: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface IForwardBatch extends IFireDoc {
|
|
113
|
+
forward_id: string;
|
|
114
|
+
tenant: string;
|
|
115
|
+
created_by: IForwardActor;
|
|
116
|
+
source: IForwardSource;
|
|
117
|
+
/** Ordem cronológica da origem (o backend ordena por `created_at`, não pelo payload). */
|
|
118
|
+
message_ids: string[];
|
|
119
|
+
intro_text: string | null;
|
|
120
|
+
recipients: IForwardRecipient[];
|
|
121
|
+
status: ForwardBatchStatus;
|
|
122
|
+
counts: IForwardCounts;
|
|
123
|
+
/** Destinatários com pelo menos um item `pending` (os que recebem mensagem PubSub). */
|
|
124
|
+
recipients_to_dispatch: number;
|
|
125
|
+
recipients_done: number;
|
|
126
|
+
/** Chave = `thread_id` do destinatário. Claim do worker contra entrega duplicada. */
|
|
127
|
+
dispatch?: Record<string, IForwardDispatchState>;
|
|
128
|
+
finished_at?: Date | null;
|
|
129
|
+
/** Um assunto por lote; gravado só quando há destinatário e-mail. */
|
|
130
|
+
email_subject?: string | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Item ──
|
|
134
|
+
|
|
135
|
+
export interface IForwardItem {
|
|
136
|
+
/** `${recipient_thread_id}__${message_id}` ou `${recipient_thread_id}__intro`. */
|
|
137
|
+
item_id: string;
|
|
138
|
+
forward_id: string;
|
|
139
|
+
recipient_thread_id: string;
|
|
140
|
+
recipient_index: number;
|
|
141
|
+
/** 0 = intro; 1..N = mensagens na ordem da origem. */
|
|
142
|
+
seq: number;
|
|
143
|
+
kind: "intro" | "message";
|
|
144
|
+
message_id: string | null;
|
|
145
|
+
content_kind: ForwardContentKind;
|
|
146
|
+
status: ForwardItemStatus;
|
|
147
|
+
reason?: ForwardItemReason | null;
|
|
148
|
+
error?: string | null;
|
|
149
|
+
error_code?: number | null;
|
|
150
|
+
dest_message_id?: string | null;
|
|
151
|
+
/** Destino e-mail: o N de `[Anexo N]` no corpo. */
|
|
152
|
+
attachment_index?: number | null;
|
|
153
|
+
dest_notification_message_id?: string | null;
|
|
154
|
+
sent_at?: Date | null;
|
|
155
|
+
updated_at?: Date;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Plan (resposta do `dry_run` e do POST) ──
|
|
159
|
+
|
|
160
|
+
export type ForwardWindowState = "open" | "human_agent" | "closed";
|
|
161
|
+
|
|
162
|
+
export interface IForwardPlanItem {
|
|
163
|
+
item_id: string;
|
|
164
|
+
kind: "intro" | "message";
|
|
165
|
+
message_id: string | null;
|
|
166
|
+
status: "pending" | "skipped";
|
|
167
|
+
reason?: ForwardSkipReason;
|
|
168
|
+
attachment?: { index: number | null; filename: string; size_bytes: number | null } | null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface IForwardPlanEmailAttachments {
|
|
172
|
+
count: number;
|
|
173
|
+
bytes: number;
|
|
174
|
+
max_count: number;
|
|
175
|
+
max_file_bytes: number;
|
|
176
|
+
max_total_bytes: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface IForwardPlanEmail {
|
|
180
|
+
channel_id: string;
|
|
181
|
+
provider: string;
|
|
182
|
+
from_address: string | null;
|
|
183
|
+
address: string | null;
|
|
184
|
+
address_source: "typed" | "contact" | "lead" | null;
|
|
185
|
+
identity: "own" | "attach" | "conflict" | null;
|
|
186
|
+
identity_owner_name?: string | null;
|
|
187
|
+
/** `fill`: lead sem e-mail, que o worker preenche antes do envio. */
|
|
188
|
+
lead_identity: "match" | "fill" | "conflict" | null;
|
|
189
|
+
/** Só no conflito do lead, para o aviso. */
|
|
190
|
+
lead_email?: string | null;
|
|
191
|
+
subject: string;
|
|
192
|
+
attachments: IForwardPlanEmailAttachments;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface IForwardPlanRecipient {
|
|
196
|
+
thread_id: string;
|
|
197
|
+
channel_id: string;
|
|
198
|
+
channel_type: string;
|
|
199
|
+
contact_name: string;
|
|
200
|
+
window: ForwardWindowState;
|
|
201
|
+
needs_ticket: boolean;
|
|
202
|
+
items: IForwardPlanItem[];
|
|
203
|
+
email?: IForwardPlanEmail | null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface IForwardPlan {
|
|
207
|
+
recipients: IForwardPlanRecipient[];
|
|
208
|
+
counts: { total: number; pending: number; skipped: number };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Bloco gravado na mensagem de DESTINO ──
|
|
212
|
+
|
|
213
|
+
export interface IMessageForward {
|
|
214
|
+
forward_id: string;
|
|
215
|
+
/** Ponte para o item do lote (o status-processor reflete falha assíncrona por aqui). */
|
|
216
|
+
item_id: string;
|
|
217
|
+
/** Vazio no item `intro`: o front não mostra selo quando está vazio. */
|
|
218
|
+
source_message_id: string;
|
|
219
|
+
source_thread_id: string;
|
|
220
|
+
source_ticket_id: string;
|
|
221
|
+
source_contact_id: string;
|
|
222
|
+
source_contact_name: string;
|
|
223
|
+
source_channel_type: string | null;
|
|
224
|
+
source_direction: "in" | "out";
|
|
225
|
+
source_created_at: Date;
|
|
226
|
+
forwarded_by: IForwardActor;
|
|
227
|
+
forwarded_at: Date;
|
|
228
|
+
/**
|
|
229
|
+
* Destino e-mail: um e-mail agrega N mensagens. `source_message_id`/`item_id`
|
|
230
|
+
* apontam para a 1ª em ordem cronológica; estes trazem todas, na mesma ordem.
|
|
231
|
+
*/
|
|
232
|
+
source_message_ids?: string[];
|
|
233
|
+
item_ids?: string[];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Matriz tipo × canal (pura; usada pelo backend na pré-validação e pelo front na seleção) ──
|
|
237
|
+
|
|
238
|
+
export type ForwardableMessage = Pick<
|
|
239
|
+
IThreadMessage,
|
|
240
|
+
"type" | "direction" | "channel_type" | "body" | "media" | "template"
|
|
241
|
+
> & {
|
|
242
|
+
revoked?: unknown;
|
|
243
|
+
deleted_at?: unknown;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export type ForwardContentResolution =
|
|
247
|
+
| { ok: true; kind: ForwardContentKind; text?: string }
|
|
248
|
+
| { ok: false; reason: ForwardSkipReason };
|
|
249
|
+
|
|
250
|
+
const MEDIA_KINDS: ReadonlySet<string> = new Set(["image", "video", "audio", "document"]);
|
|
251
|
+
|
|
252
|
+
/** Canais que não entram como origem (e-mail, SMS). */
|
|
253
|
+
const UNSUPPORTED_SOURCE_CHANNELS: ReadonlySet<string> = new Set(["email", "sms"]);
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Decide se uma mensagem pode ser encaminhada e como vira conteúdo no destino.
|
|
257
|
+
*
|
|
258
|
+
* `dest = null` é a checagem agnóstica do modo de seleção no front (sem canal
|
|
259
|
+
* escolhido). Regras que dependem do destino: documento não vai para Instagram;
|
|
260
|
+
* áudio e vídeo não vão para e-mail. O teto de tamanho e o MIME aceito como
|
|
261
|
+
* anexo NÃO ficam aqui: dependem do objeto no Storage e da allowlist do provider.
|
|
262
|
+
*/
|
|
263
|
+
export function resolveForwardContent(
|
|
264
|
+
m: ForwardableMessage,
|
|
265
|
+
dest: ForwardDestinationKind | null,
|
|
266
|
+
): ForwardContentResolution {
|
|
267
|
+
const direction = String(m.direction ?? "").toLowerCase();
|
|
268
|
+
if (direction !== "in" && direction !== "out") return { ok: false, reason: "type_not_supported" };
|
|
269
|
+
if (m.revoked === true) return { ok: false, reason: "type_not_supported" };
|
|
270
|
+
if (m.deleted_at !== undefined && m.deleted_at !== null) return { ok: false, reason: "type_not_supported" };
|
|
271
|
+
|
|
272
|
+
const channelType = String(m.channel_type ?? "").toLowerCase();
|
|
273
|
+
if (UNSUPPORTED_SOURCE_CHANNELS.has(channelType)) return { ok: false, reason: "type_not_supported" };
|
|
274
|
+
|
|
275
|
+
const type = String(m.type ?? "").toLowerCase();
|
|
276
|
+
|
|
277
|
+
if (type === "text") {
|
|
278
|
+
const text = (m.body?.text ?? "").trim();
|
|
279
|
+
if (!text) return { ok: false, reason: "type_not_supported" };
|
|
280
|
+
return { ok: true, kind: "text", text };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (type === "template") {
|
|
284
|
+
const text = (m.template?.rendered?.text ?? "").trim();
|
|
285
|
+
if (!text) return { ok: false, reason: "type_not_supported" };
|
|
286
|
+
return { ok: true, kind: "text", text };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (MEDIA_KINDS.has(type)) {
|
|
290
|
+
if (dest === "email" && (type === "audio" || type === "video")) return { ok: false, reason: "type_not_supported" };
|
|
291
|
+
const uri = (m.media?.gcs_uri ?? "").trim();
|
|
292
|
+
if (!uri) return { ok: false, reason: "media_unavailable" };
|
|
293
|
+
if (type === "document" && dest === "instagram") return { ok: false, reason: "type_not_supported" };
|
|
294
|
+
return { ok: true, kind: type as ForwardContentKind };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return { ok: false, reason: "type_not_supported" };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Id determinístico do item: um destinatário aparece uma vez no lote, então é único. */
|
|
301
|
+
export function buildForwardItemId(recipientThreadId: string, messageId: string | null): string {
|
|
302
|
+
return `${recipientThreadId}__${messageId ?? "intro"}`;
|
|
303
|
+
}
|
|
@@ -14,11 +14,14 @@ export * from "./waba-history-import";
|
|
|
14
14
|
export * from "./ig-setup";
|
|
15
15
|
export * from "./quick-reply";
|
|
16
16
|
export * from "./search";
|
|
17
|
+
export * from "./forward";
|
|
17
18
|
export declare const EvoChatPermissions: {
|
|
18
19
|
readonly Read: "evo_chat_read";
|
|
19
20
|
readonly Write: "evo_chat_write";
|
|
20
21
|
readonly InboxManage: "evo_chat_inbox_manage";
|
|
21
22
|
readonly TicketClose: "evo_chat_ticket_close";
|
|
22
23
|
readonly TicketTransfer: "evo_chat_ticket_transfer";
|
|
24
|
+
/** feat-254 B2: baixar anexo de e-mail bloqueado pela política (quarentena). Sem papel padrão. */
|
|
25
|
+
readonly QuarantineRead: "evo_chat_quarantine_read";
|
|
23
26
|
};
|
|
24
27
|
export type EvoChatPermissions = (typeof EvoChatPermissions)[keyof typeof EvoChatPermissions];
|
|
@@ -31,6 +31,7 @@ __exportStar(require("./waba-history-import"), exports);
|
|
|
31
31
|
__exportStar(require("./ig-setup"), exports);
|
|
32
32
|
__exportStar(require("./quick-reply"), exports);
|
|
33
33
|
__exportStar(require("./search"), exports);
|
|
34
|
+
__exportStar(require("./forward"), exports);
|
|
34
35
|
// Permissões para o evo-chat
|
|
35
36
|
exports.EvoChatPermissions = {
|
|
36
37
|
Read: "evo_chat_read",
|
|
@@ -38,4 +39,6 @@ exports.EvoChatPermissions = {
|
|
|
38
39
|
InboxManage: "evo_chat_inbox_manage",
|
|
39
40
|
TicketClose: "evo_chat_ticket_close",
|
|
40
41
|
TicketTransfer: "evo_chat_ticket_transfer",
|
|
42
|
+
/** feat-254 B2: baixar anexo de e-mail bloqueado pela política (quarentena). Sem papel padrão. */
|
|
43
|
+
QuarantineRead: "evo_chat_quarantine_read",
|
|
41
44
|
};
|
|
@@ -14,6 +14,7 @@ export * from "./waba-history-import";
|
|
|
14
14
|
export * from "./ig-setup";
|
|
15
15
|
export * from "./quick-reply";
|
|
16
16
|
export * from "./search";
|
|
17
|
+
export * from "./forward";
|
|
17
18
|
|
|
18
19
|
// Permissões para o evo-chat
|
|
19
20
|
export const EvoChatPermissions = {
|
|
@@ -22,6 +23,8 @@ export const EvoChatPermissions = {
|
|
|
22
23
|
InboxManage: "evo_chat_inbox_manage",
|
|
23
24
|
TicketClose: "evo_chat_ticket_close",
|
|
24
25
|
TicketTransfer: "evo_chat_ticket_transfer",
|
|
26
|
+
/** feat-254 B2: baixar anexo de e-mail bloqueado pela política (quarentena). Sem papel padrão. */
|
|
27
|
+
QuarantineRead: "evo_chat_quarantine_read",
|
|
25
28
|
} as const;
|
|
26
29
|
|
|
27
30
|
export type EvoChatPermissions =
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IFireDoc } from "../../shared";
|
|
2
2
|
import type { IMessageCall } from "../call-session";
|
|
3
|
+
import type { IMessageForward } from "../forward";
|
|
3
4
|
export type EvoChatMessageType = 'text' | 'image' | 'audio' | 'video' | 'document' | 'sticker' | 'location' | 'contacts' | 'interactive' | 'reaction' | 'template' | 'system' | 'call';
|
|
4
5
|
export type MessageDirection = 'in' | 'out';
|
|
5
6
|
export interface IMessageStatus {
|
|
@@ -159,6 +160,9 @@ export interface IThreadMessage extends IFireDoc {
|
|
|
159
160
|
rendered?: IRenderedTemplateSnapshot;
|
|
160
161
|
};
|
|
161
162
|
context?: IMessageContext;
|
|
163
|
+
forward?: IMessageForward;
|
|
164
|
+
/** feat-282: na mensagem de ORIGEM, quantas entregas de encaminhamento já saíram dela. */
|
|
165
|
+
forward_count?: number;
|
|
162
166
|
call?: IMessageCall;
|
|
163
167
|
provider_message_id?: string;
|
|
164
168
|
status?: IMessageStatus;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IFireDoc } from "../../shared";
|
|
2
2
|
import type { IMessageCall } from "../call-session";
|
|
3
|
+
import type { IMessageForward } from "../forward";
|
|
3
4
|
|
|
4
5
|
// ── Message type/direction enums ──
|
|
5
6
|
|
|
@@ -197,6 +198,11 @@ export interface IThreadMessage extends IFireDoc {
|
|
|
197
198
|
};
|
|
198
199
|
context?: IMessageContext;
|
|
199
200
|
|
|
201
|
+
// feat-282: presente na mensagem de DESTINO de um encaminhamento (ver ../forward).
|
|
202
|
+
forward?: IMessageForward;
|
|
203
|
+
/** feat-282: na mensagem de ORIGEM, quantas entregas de encaminhamento já saíram dela. */
|
|
204
|
+
forward_count?: number;
|
|
205
|
+
|
|
200
206
|
// Call timeline projection (feat-029). Fonte de verdade em ICallSession.
|
|
201
207
|
call?: IMessageCall;
|
|
202
208
|
|