@rizvanua/contact-chat 0.1.0

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +496 -0
  3. package/dist/index.cjs +704 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +4 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.js +688 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/react/index.cjs +326 -0
  10. package/dist/react/index.cjs.map +1 -0
  11. package/dist/react/index.d.cts +118 -0
  12. package/dist/react/index.d.ts +118 -0
  13. package/dist/react/index.js +306 -0
  14. package/dist/react/index.js.map +1 -0
  15. package/dist/server/index.cjs +704 -0
  16. package/dist/server/index.cjs.map +1 -0
  17. package/dist/server/index.d.cts +98 -0
  18. package/dist/server/index.d.ts +98 -0
  19. package/dist/server/index.js +688 -0
  20. package/dist/server/index.js.map +1 -0
  21. package/dist/server/nextjs.cjs +10 -0
  22. package/dist/server/nextjs.cjs.map +1 -0
  23. package/dist/server/nextjs.d.cts +17 -0
  24. package/dist/server/nextjs.d.ts +17 -0
  25. package/dist/server/nextjs.js +8 -0
  26. package/dist/server/nextjs.js.map +1 -0
  27. package/dist/stores/index.cjs +244 -0
  28. package/dist/stores/index.cjs.map +1 -0
  29. package/dist/stores/index.d.cts +58 -0
  30. package/dist/stores/index.d.ts +58 -0
  31. package/dist/stores/index.js +241 -0
  32. package/dist/stores/index.js.map +1 -0
  33. package/dist/transports/index.cjs +156 -0
  34. package/dist/transports/index.cjs.map +1 -0
  35. package/dist/transports/index.d.cts +48 -0
  36. package/dist/transports/index.d.ts +48 -0
  37. package/dist/transports/index.js +151 -0
  38. package/dist/transports/index.js.map +1 -0
  39. package/dist/types-BcoqxSLg.d.cts +42 -0
  40. package/dist/types-BcoqxSLg.d.ts +42 -0
  41. package/dist/ui/index.cjs +609 -0
  42. package/dist/ui/index.cjs.map +1 -0
  43. package/dist/ui/index.d.cts +127 -0
  44. package/dist/ui/index.d.ts +127 -0
  45. package/dist/ui/index.js +604 -0
  46. package/dist/ui/index.js.map +1 -0
  47. package/package.json +146 -0
@@ -0,0 +1,151 @@
1
+ // src/transports/telegram.ts
2
+ var TelegramError = class extends Error {
3
+ constructor(message, description) {
4
+ super(message);
5
+ this.description = description;
6
+ }
7
+ description;
8
+ };
9
+ function isMissingThreadError(error) {
10
+ if (!(error instanceof TelegramError)) return false;
11
+ const detail = (error.description ?? error.message).toLowerCase();
12
+ return detail.includes("thread not found") || detail.includes("topic_deleted") || detail.includes("topic was deleted") || detail.includes("thread_not_found");
13
+ }
14
+ var API_BASE = "https://api.telegram.org";
15
+ function describeBrowser(userAgent) {
16
+ if (!userAgent) return "Unknown browser";
17
+ if (/edg\//i.test(userAgent)) return "Edge";
18
+ if (/opr\//i.test(userAgent)) return "Opera";
19
+ if (/chrome\//i.test(userAgent)) return "Chrome";
20
+ if (/firefox\//i.test(userAgent)) return "Firefox";
21
+ if (/safari\//i.test(userAgent)) return "Safari";
22
+ return "Unknown browser";
23
+ }
24
+ function buildTopicName(input) {
25
+ const parts = new Intl.DateTimeFormat("en-GB", {
26
+ timeZone: "Europe/Kyiv",
27
+ day: "2-digit",
28
+ month: "2-digit",
29
+ hour: "2-digit",
30
+ minute: "2-digit",
31
+ hour12: false
32
+ }).formatToParts(input.now);
33
+ const at = (type) => parts.find((part) => part.type === type)?.value ?? "00";
34
+ return [
35
+ input.name,
36
+ at("day"),
37
+ at("month"),
38
+ at("hour"),
39
+ at("minute"),
40
+ describeBrowser(input.userAgent)
41
+ ].join("-").slice(0, 128);
42
+ }
43
+ function telegramTransport(opts) {
44
+ const {
45
+ botToken,
46
+ chatId,
47
+ webhookSecret,
48
+ topicIconColor = 13338331,
49
+ buildTopicName: buildTopicNameFn = buildTopicName,
50
+ fetch: fetchFn = globalThis.fetch
51
+ } = opts;
52
+ if (!botToken) {
53
+ throw new Error("telegramTransport: botToken is required.");
54
+ }
55
+ if (!chatId) {
56
+ throw new Error("telegramTransport: chatId is required.");
57
+ }
58
+ if (!webhookSecret) {
59
+ throw new Error(
60
+ "telegramTransport: webhookSecret is required. Pass the same value you configured with Telegram via /setWebhook."
61
+ );
62
+ }
63
+ async function callApi(method, body) {
64
+ const response = await fetchFn(`${API_BASE}/bot${botToken}/${method}`, {
65
+ method: "POST",
66
+ headers: { "Content-Type": "application/json" },
67
+ body: JSON.stringify(body),
68
+ cache: "no-store"
69
+ });
70
+ const payload = await response.json();
71
+ if (!response.ok || !payload.ok) {
72
+ throw new TelegramError(
73
+ `${method} failed: ${payload.description ?? response.status}`,
74
+ payload.description
75
+ );
76
+ }
77
+ return payload.result;
78
+ }
79
+ return {
80
+ async createThread(input) {
81
+ const name = buildTopicNameFn(input);
82
+ const result = await callApi(
83
+ "createForumTopic",
84
+ {
85
+ chat_id: chatId,
86
+ name: name.slice(0, 128),
87
+ // Telegram snaps icon_color to a fixed six-colour palette and silently
88
+ // substitutes the nearest allowed value. 0xCB86DB is the purple one;
89
+ // an arbitrary brand hex such as 0x9333EA comes back as blue.
90
+ icon_color: topicIconColor
91
+ }
92
+ );
93
+ return result.message_thread_id;
94
+ },
95
+ async send(threadId, text) {
96
+ await callApi("sendMessage", {
97
+ chat_id: chatId,
98
+ message_thread_id: threadId,
99
+ text
100
+ });
101
+ },
102
+ /**
103
+ * Retitles an existing topic. Needed when a session created before the name
104
+ * gate shipped supplies a name for the first time — the topic already exists,
105
+ * so it has to be renamed rather than created.
106
+ */
107
+ async renameThread(threadId, input) {
108
+ const name = buildTopicNameFn(input);
109
+ await callApi("editForumTopic", {
110
+ chat_id: chatId,
111
+ message_thread_id: threadId,
112
+ name: name.slice(0, 128)
113
+ });
114
+ },
115
+ isMissingThreadError,
116
+ /**
117
+ * Narrows an inbound webhook update to an owner reply, or null.
118
+ *
119
+ * Every inbound filter lives here: not a message, sent by a bot, missing a
120
+ * message_thread_id (i.e. posted in the group's General area rather than a
121
+ * topic), or from an unexpected chat all return null.
122
+ */
123
+ parseInboundReply(update) {
124
+ if (typeof update !== "object" || update === null) return null;
125
+ const message = update.message;
126
+ if (typeof message !== "object" || message === null) return null;
127
+ const {
128
+ message_id: messageId,
129
+ message_thread_id: threadId,
130
+ text,
131
+ chat,
132
+ from
133
+ } = message;
134
+ if (from?.is_bot === true) return null;
135
+ if (typeof threadId !== "number") return null;
136
+ if (typeof messageId !== "number") return null;
137
+ if (typeof text !== "string" || text.trim().length === 0) return null;
138
+ if (!chatId || String(chat?.id) !== chatId) return null;
139
+ return { threadId, text, messageId };
140
+ },
141
+ verifyWebhook(request) {
142
+ const header = request.headers.get("x-telegram-bot-api-secret-token");
143
+ if (!header) return false;
144
+ return header === webhookSecret;
145
+ }
146
+ };
147
+ }
148
+
149
+ export { TelegramError, buildTopicName, describeBrowser, telegramTransport };
150
+ //# sourceMappingURL=index.js.map
151
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/transports/telegram.ts"],"names":[],"mappings":";AAEO,IAAM,aAAA,GAAN,cAA4B,KAAA,CAAM;AAAA,EACvC,WAAA,CACE,SAES,WAAA,EACT;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AAFJ,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AAAA,EAGX;AAAA,EAHW,WAAA;AAIb;AAOO,SAAS,qBAAqB,KAAA,EAAyB;AAC5D,EAAA,IAAI,EAAE,KAAA,YAAiB,aAAA,CAAA,EAAgB,OAAO,KAAA;AAC9C,EAAA,MAAM,MAAA,GAAA,CAAU,KAAA,CAAM,WAAA,IAAe,KAAA,CAAM,SAAS,WAAA,EAAY;AAChE,EAAA,OACE,MAAA,CAAO,QAAA,CAAS,kBAAkB,CAAA,IAClC,OAAO,QAAA,CAAS,eAAe,CAAA,IAC/B,MAAA,CAAO,QAAA,CAAS,mBAAmB,CAAA,IACnC,MAAA,CAAO,SAAS,kBAAkB,CAAA;AAEtC;AAEA,IAAM,QAAA,GAAW,0BAAA;AAEV,SAAS,gBAAgB,SAAA,EAAkC;AAChE,EAAA,IAAI,CAAC,WAAW,OAAO,iBAAA;AACvB,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,MAAA;AACrC,EAAA,IAAI,QAAA,CAAS,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,OAAA;AACrC,EAAA,IAAI,WAAA,CAAY,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,QAAA;AACxC,EAAA,IAAI,YAAA,CAAa,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,SAAA;AACzC,EAAA,IAAI,WAAA,CAAY,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,QAAA;AACxC,EAAA,OAAO,iBAAA;AACT;AAQO,SAAS,eAAe,KAAA,EAA+B;AAC5D,EAAA,MAAM,KAAA,GAAQ,IAAI,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS;AAAA,IAC7C,QAAA,EAAU,aAAA;AAAA,IACV,GAAA,EAAK,SAAA;AAAA,IACL,KAAA,EAAO,SAAA;AAAA,IACP,IAAA,EAAM,SAAA;AAAA,IACN,MAAA,EAAQ,SAAA;AAAA,IACR,MAAA,EAAQ;AAAA,GACT,CAAA,CAAE,aAAA,CAAc,KAAA,CAAM,GAAG,CAAA;AAE1B,EAAA,MAAM,EAAA,GAAK,CAAC,IAAA,KACV,KAAA,CAAM,IAAA,CAAK,CAAC,IAAA,KAAS,IAAA,CAAK,IAAA,KAAS,IAAI,CAAA,EAAG,KAAA,IAAS,IAAA;AAErD,EAAA,OAAO;AAAA,IACL,KAAA,CAAM,IAAA;AAAA,IACN,GAAG,KAAK,CAAA;AAAA,IACR,GAAG,OAAO,CAAA;AAAA,IACV,GAAG,MAAM,CAAA;AAAA,IACT,GAAG,QAAQ,CAAA;AAAA,IACX,eAAA,CAAgB,MAAM,SAAS;AAAA,IAE9B,IAAA,CAAK,GAAG,CAAA,CACR,KAAA,CAAM,GAAG,GAAG,CAAA;AACjB;AAeO,SAAS,kBAAkB,IAAA,EAA+C;AAC/E,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAA;AAAA,IACA,cAAA,GAAiB,QAAA;AAAA,IACjB,gBAAgB,gBAAA,GAAmB,cAAA;AAAA,IACnC,KAAA,EAAO,UAAU,UAAA,CAAW;AAAA,GAC9B,GAAI,IAAA;AAEJ,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,EAC5D;AACA,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,EAC1D;AACA,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAEA,EAAA,eAAe,OAAA,CACb,QACA,IAAA,EACY;AACZ,IAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,CAAA,EAAG,QAAQ,CAAA,IAAA,EAAO,QAAQ,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA,EAAI;AAAA,MACrE,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAAA,MACzB,KAAA,EAAO;AAAA,KACR,CAAA;AAED,IAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AAMrC,IAAA,IAAI,CAAC,QAAA,CAAS,EAAA,IAAM,CAAC,QAAQ,EAAA,EAAI;AAC/B,MAAA,MAAM,IAAI,aAAA;AAAA,QACR,GAAG,MAAM,CAAA,SAAA,EAAY,OAAA,CAAQ,WAAA,IAAe,SAAS,MAAM,CAAA,CAAA;AAAA,QAC3D,OAAA,CAAQ;AAAA,OACV;AAAA,IACF;AAEA,IAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,EACjB;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,aAAa,KAAA,EAAwC;AACzD,MAAA,MAAM,IAAA,GAAO,iBAAiB,KAAK,CAAA;AACnC,MAAA,MAAM,SAAS,MAAM,OAAA;AAAA,QACnB,kBAAA;AAAA,QACA;AAAA,UACE,OAAA,EAAS,MAAA;AAAA,UACT,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AAAA;AAAA;AAAA;AAAA,UAIvB,UAAA,EAAY;AAAA;AACd,OACF;AACA,MAAA,OAAO,MAAA,CAAO,iBAAA;AAAA,IAChB,CAAA;AAAA,IAEA,MAAM,IAAA,CAAK,QAAA,EAAkB,IAAA,EAA6B;AAExD,MAAA,MAAM,QAAQ,aAAA,EAAe;AAAA,QAC3B,OAAA,EAAS,MAAA;AAAA,QACT,iBAAA,EAAmB,QAAA;AAAA,QACnB;AAAA,OACD,CAAA;AAAA,IACH,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,MAAM,YAAA,CAAa,QAAA,EAAkB,KAAA,EAAsC;AACzE,MAAA,MAAM,IAAA,GAAO,iBAAiB,KAAK,CAAA;AACnC,MAAA,MAAM,QAAQ,gBAAA,EAAkB;AAAA,QAC9B,OAAA,EAAS,MAAA;AAAA,QACT,iBAAA,EAAmB,QAAA;AAAA,QACnB,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,GAAG;AAAA,OACxB,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,oBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,kBACE,MAAA,EAC8D;AAC9D,MAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,MAAM,OAAO,IAAA;AAE1D,MAAA,MAAM,UAAW,MAAA,CAAiC,OAAA;AAClD,MAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,KAAY,MAAM,OAAO,IAAA;AAE5D,MAAA,MAAM;AAAA,QACJ,UAAA,EAAY,SAAA;AAAA,QACZ,iBAAA,EAAmB,QAAA;AAAA,QACnB,IAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA,OACF,GAAI,OAAA;AAQJ,MAAA,IAAI,IAAA,EAAM,MAAA,KAAW,IAAA,EAAM,OAAO,IAAA;AAClC,MAAA,IAAI,OAAO,QAAA,KAAa,QAAA,EAAU,OAAO,IAAA;AACzC,MAAA,IAAI,OAAO,SAAA,KAAc,QAAA,EAAU,OAAO,IAAA;AAC1C,MAAA,IAAI,OAAO,SAAS,QAAA,IAAY,IAAA,CAAK,MAAK,CAAE,MAAA,KAAW,GAAG,OAAO,IAAA;AAEjE,MAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAM,EAAE,CAAA,KAAM,QAAQ,OAAO,IAAA;AAEnD,MAAA,OAAO,EAAE,QAAA,EAAU,IAAA,EAAM,SAAA,EAAU;AAAA,IACrC,CAAA;AAAA,IAEA,cAAc,OAAA,EAA2B;AACvC,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,iCAAiC,CAAA;AACpE,MAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AACpB,MAAA,OAAO,MAAA,KAAW,aAAA;AAAA,IACpB;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import type { ChatTransport, TopicNameInput } from '../core/transport-interface.js';\n\nexport class TelegramError extends Error {\n constructor(\n message: string,\n /** Telegram's own `description`, kept so callers can classify the failure. */\n readonly description?: string\n ) {\n super(message);\n }\n}\n\n/**\n * True when Telegram rejected the send because the forum topic no longer\n * exists — typically because the owner deleted it while tidying the group.\n * The session's threadId is then permanently dead and must be replaced.\n */\nexport function isMissingThreadError(error: unknown): boolean {\n if (!(error instanceof TelegramError)) return false;\n const detail = (error.description ?? error.message).toLowerCase();\n return (\n detail.includes('thread not found') ||\n detail.includes('topic_deleted') ||\n detail.includes('topic was deleted') ||\n detail.includes('thread_not_found')\n );\n}\n\nconst API_BASE = 'https://api.telegram.org';\n\nexport function describeBrowser(userAgent: string | null): string {\n if (!userAgent) return 'Unknown browser';\n if (/edg\\//i.test(userAgent)) return 'Edge';\n if (/opr\\//i.test(userAgent)) return 'Opera';\n if (/chrome\\//i.test(userAgent)) return 'Chrome';\n if (/firefox\\//i.test(userAgent)) return 'Firefox';\n if (/safari\\//i.test(userAgent)) return 'Safari';\n return 'Unknown browser';\n}\n\n/**\n * Produces e.g. \"Roman-04-08-17-42-Chrome\".\n *\n * Date and time come from Intl with an explicit timeZone: the server runs UTC,\n * so reading Date methods directly would stamp titles three hours off Kyiv.\n */\nexport function buildTopicName(input: TopicNameInput): string {\n const parts = new Intl.DateTimeFormat('en-GB', {\n timeZone: 'Europe/Kyiv',\n day: '2-digit',\n month: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n }).formatToParts(input.now);\n\n const at = (type: Intl.DateTimeFormatPartTypes) =>\n parts.find((part) => part.type === type)?.value ?? '00';\n\n return [\n input.name,\n at('day'),\n at('month'),\n at('hour'),\n at('minute'),\n describeBrowser(input.userAgent),\n ]\n .join('-')\n .slice(0, 128);\n}\n\nexport interface TelegramTransportOptions {\n botToken: string;\n chatId: string;\n /** The transport verifies inbound webhooks by comparing this against the x-telegram-bot-api-secret-token header. */\n webhookSecret: string;\n /** Telegram's fixed six-colour palette. Default 0xcb86db (purple). */\n topicIconColor?: number;\n /** Override the default topic-name format. Default: `${name}-DD-MM-HH-MM-${browser}` in Europe/Kyiv, sliced to 128 chars. */\n buildTopicName?: (input: TopicNameInput) => string;\n /** Optional fetch override for tests / edge runtimes. Default: globalThis.fetch. */\n fetch?: typeof fetch;\n}\n\nexport function telegramTransport(opts: TelegramTransportOptions): ChatTransport {\n const {\n botToken,\n chatId,\n webhookSecret,\n topicIconColor = 0xcb86db,\n buildTopicName: buildTopicNameFn = buildTopicName,\n fetch: fetchFn = globalThis.fetch,\n } = opts;\n\n if (!botToken) {\n throw new Error('telegramTransport: botToken is required.');\n }\n if (!chatId) {\n throw new Error('telegramTransport: chatId is required.');\n }\n if (!webhookSecret) {\n throw new Error(\n 'telegramTransport: webhookSecret is required. Pass the same value you configured with Telegram via /setWebhook.',\n );\n }\n\n async function callApi<T>(\n method: string,\n body: Record<string, unknown>\n ): Promise<T> {\n const response = await fetchFn(`${API_BASE}/bot${botToken}/${method}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n cache: 'no-store',\n });\n\n const payload = (await response.json()) as {\n ok: boolean;\n result?: T;\n description?: string;\n };\n\n if (!response.ok || !payload.ok) {\n throw new TelegramError(\n `${method} failed: ${payload.description ?? response.status}`,\n payload.description\n );\n }\n\n return payload.result as T;\n }\n\n return {\n async createThread(input: TopicNameInput): Promise<number> {\n const name = buildTopicNameFn(input);\n const result = await callApi<{ message_thread_id: number }>(\n 'createForumTopic',\n {\n chat_id: chatId,\n name: name.slice(0, 128),\n // Telegram snaps icon_color to a fixed six-colour palette and silently\n // substitutes the nearest allowed value. 0xCB86DB is the purple one;\n // an arbitrary brand hex such as 0x9333EA comes back as blue.\n icon_color: topicIconColor,\n }\n );\n return result.message_thread_id;\n },\n\n async send(threadId: number, text: string): Promise<void> {\n // No parse_mode: visitor text must never be parsed as Markdown or HTML.\n await callApi('sendMessage', {\n chat_id: chatId,\n message_thread_id: threadId,\n text,\n });\n },\n\n /**\n * Retitles an existing topic. Needed when a session created before the name\n * gate shipped supplies a name for the first time — the topic already exists,\n * so it has to be renamed rather than created.\n */\n async renameThread(threadId: number, input: TopicNameInput): Promise<void> {\n const name = buildTopicNameFn(input);\n await callApi('editForumTopic', {\n chat_id: chatId,\n message_thread_id: threadId,\n name: name.slice(0, 128),\n });\n },\n\n isMissingThreadError,\n\n /**\n * Narrows an inbound webhook update to an owner reply, or null.\n *\n * Every inbound filter lives here: not a message, sent by a bot, missing a\n * message_thread_id (i.e. posted in the group's General area rather than a\n * topic), or from an unexpected chat all return null.\n */\n parseInboundReply(\n update: unknown\n ): { threadId: number; text: string; messageId: number } | null {\n if (typeof update !== 'object' || update === null) return null;\n\n const message = (update as { message?: unknown }).message;\n if (typeof message !== 'object' || message === null) return null;\n\n const {\n message_id: messageId,\n message_thread_id: threadId,\n text,\n chat,\n from,\n } = message as {\n message_id?: unknown;\n message_thread_id?: unknown;\n text?: unknown;\n chat?: { id?: unknown };\n from?: { is_bot?: unknown };\n };\n\n if (from?.is_bot === true) return null;\n if (typeof threadId !== 'number') return null;\n if (typeof messageId !== 'number') return null;\n if (typeof text !== 'string' || text.trim().length === 0) return null;\n\n if (!chatId || String(chat?.id) !== chatId) return null;\n\n return { threadId, text, messageId };\n },\n\n verifyWebhook(request: Request): boolean {\n const header = request.headers.get('x-telegram-bot-api-secret-token');\n if (!header) return false;\n return header === webhookSecret;\n },\n };\n}\n"]}
@@ -0,0 +1,42 @@
1
+ type ChatAuthor = 'visitor' | 'owner';
2
+ interface ChatMessage {
3
+ id: string;
4
+ from: ChatAuthor;
5
+ text: string;
6
+ ts: number;
7
+ }
8
+ interface ChatSession {
9
+ topicId: number;
10
+ createdAt: number;
11
+ msgCount: number;
12
+ name?: string;
13
+ }
14
+ interface SendRequestBody {
15
+ sessionId: string;
16
+ text: string;
17
+ name?: string;
18
+ honeypot?: string;
19
+ }
20
+ interface SendResponseBody {
21
+ message: ChatMessage;
22
+ cursor: number;
23
+ }
24
+ interface PollResponseBody {
25
+ messages: ChatMessage[];
26
+ cursor: number;
27
+ }
28
+ type ChatErrorCode = 'validation' | 'rate_limited' | 'store_unavailable' | 'telegram_unavailable';
29
+ interface ChatErrorBody {
30
+ code: ChatErrorCode;
31
+ error: string;
32
+ }
33
+ interface ChatLimits {
34
+ maxMessageLength: number;
35
+ maxMessagesPerSession: number;
36
+ sessionTtlSeconds: number;
37
+ ratePerMinute: number;
38
+ ratePerDay: number;
39
+ }
40
+ declare const DEFAULT_CHAT_LIMITS: ChatLimits;
41
+
42
+ export { type ChatMessage as C, DEFAULT_CHAT_LIMITS as D, type PollResponseBody as P, type SendRequestBody as S, type ChatSession as a, type ChatLimits as b, type ChatAuthor as c, type ChatErrorBody as d, type ChatErrorCode as e, type SendResponseBody as f };
@@ -0,0 +1,42 @@
1
+ type ChatAuthor = 'visitor' | 'owner';
2
+ interface ChatMessage {
3
+ id: string;
4
+ from: ChatAuthor;
5
+ text: string;
6
+ ts: number;
7
+ }
8
+ interface ChatSession {
9
+ topicId: number;
10
+ createdAt: number;
11
+ msgCount: number;
12
+ name?: string;
13
+ }
14
+ interface SendRequestBody {
15
+ sessionId: string;
16
+ text: string;
17
+ name?: string;
18
+ honeypot?: string;
19
+ }
20
+ interface SendResponseBody {
21
+ message: ChatMessage;
22
+ cursor: number;
23
+ }
24
+ interface PollResponseBody {
25
+ messages: ChatMessage[];
26
+ cursor: number;
27
+ }
28
+ type ChatErrorCode = 'validation' | 'rate_limited' | 'store_unavailable' | 'telegram_unavailable';
29
+ interface ChatErrorBody {
30
+ code: ChatErrorCode;
31
+ error: string;
32
+ }
33
+ interface ChatLimits {
34
+ maxMessageLength: number;
35
+ maxMessagesPerSession: number;
36
+ sessionTtlSeconds: number;
37
+ ratePerMinute: number;
38
+ ratePerDay: number;
39
+ }
40
+ declare const DEFAULT_CHAT_LIMITS: ChatLimits;
41
+
42
+ export { type ChatMessage as C, DEFAULT_CHAT_LIMITS as D, type PollResponseBody as P, type SendRequestBody as S, type ChatSession as a, type ChatLimits as b, type ChatAuthor as c, type ChatErrorBody as d, type ChatErrorCode as e, type SendResponseBody as f };