Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
127
omni_inbound/src/telegram.ts
Normal file
127
omni_inbound/src/telegram.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import type { JsonObject, OmniInboundEnvelopeV1 } from "./types";
|
||||
|
||||
const MAX_TEXT_LENGTH = 4096;
|
||||
|
||||
function asObject(value: unknown): JsonObject {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonObject) : {};
|
||||
}
|
||||
|
||||
function pickMessage(update: JsonObject): JsonObject {
|
||||
const candidates = [
|
||||
update.message,
|
||||
update.edited_message,
|
||||
update.business_message,
|
||||
update.edited_business_message,
|
||||
update.channel_post,
|
||||
update.edited_channel_post,
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const obj = asObject(candidate);
|
||||
if (Object.keys(obj).length > 0) return obj;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function pickEventType(update: JsonObject): string {
|
||||
if (update.business_message) return "business_message";
|
||||
if (update.edited_business_message) return "edited_business_message";
|
||||
if (update.business_connection) return "business_connection";
|
||||
if (update.deleted_business_messages) return "deleted_business_messages";
|
||||
if (update.message) return "message";
|
||||
if (update.edited_message) return "edited_message";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function isoFromUnix(value: unknown) {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
||||
return new Date(value * 1000).toISOString();
|
||||
}
|
||||
|
||||
function cropText(value: unknown) {
|
||||
if (typeof value !== "string") return null;
|
||||
return value.slice(0, MAX_TEXT_LENGTH);
|
||||
}
|
||||
|
||||
function requireString(value: unknown, fallback: string) {
|
||||
const v = String(value ?? "").trim();
|
||||
return v || fallback;
|
||||
}
|
||||
|
||||
function makeFallbackEventId(raw: unknown) {
|
||||
return createHash("sha256").update(JSON.stringify(raw ?? null)).digest("hex");
|
||||
}
|
||||
|
||||
export function parseTelegramBusinessUpdate(raw: unknown): OmniInboundEnvelopeV1 {
|
||||
const update = asObject(raw);
|
||||
const message = pickMessage(update);
|
||||
const receivedAt = new Date().toISOString();
|
||||
|
||||
const updateId = update.update_id;
|
||||
const messageId = message.message_id;
|
||||
const businessConnection = asObject(update.business_connection);
|
||||
|
||||
const providerEventId =
|
||||
(updateId != null && requireString(updateId, "")) ||
|
||||
(messageId != null && requireString(messageId, "")) ||
|
||||
makeFallbackEventId(raw);
|
||||
|
||||
const providerMessageId = messageId != null ? String(messageId) : null;
|
||||
|
||||
const chat = asObject(message.chat);
|
||||
const from = asObject(message.from);
|
||||
|
||||
const threadExternalId =
|
||||
chat.id != null
|
||||
? String(chat.id)
|
||||
: businessConnection.user_chat_id != null
|
||||
? String(businessConnection.user_chat_id)
|
||||
: null;
|
||||
|
||||
const contactExternalId = from.id != null ? String(from.id) : null;
|
||||
|
||||
const text = cropText(message.text) ?? cropText(message.caption);
|
||||
|
||||
const businessConnectionId =
|
||||
message.business_connection_id != null
|
||||
? String(message.business_connection_id)
|
||||
: businessConnection.id != null
|
||||
? String(businessConnection.id)
|
||||
: null;
|
||||
|
||||
const occurredAt =
|
||||
isoFromUnix(message.date) ??
|
||||
isoFromUnix(businessConnection.date) ??
|
||||
receivedAt;
|
||||
|
||||
const eventType = pickEventType(update);
|
||||
|
||||
const idempotencyKey = ["telegram_business", providerEventId, businessConnectionId || "no-bc"].join(":");
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
idempotencyKey,
|
||||
provider: "telegram_business",
|
||||
channel: "TELEGRAM",
|
||||
direction: "IN",
|
||||
providerEventId,
|
||||
providerMessageId,
|
||||
eventType,
|
||||
occurredAt,
|
||||
receivedAt,
|
||||
payloadRaw: raw,
|
||||
payloadNormalized: {
|
||||
threadExternalId,
|
||||
contactExternalId,
|
||||
text,
|
||||
businessConnectionId,
|
||||
updateId: updateId != null ? String(updateId) : null,
|
||||
chatTitle: typeof chat.title === "string" ? chat.title : null,
|
||||
fromUsername: typeof from.username === "string" ? from.username : null,
|
||||
fromFirstName: typeof from.first_name === "string" ? from.first_name : null,
|
||||
fromLastName: typeof from.last_name === "string" ? from.last_name : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user