@ai-devkit/channel-connector 0.6.0 → 0.8.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 (35) hide show
  1. package/.swcrc +27 -0
  2. package/dist/ChannelManager.d.ts +1 -1
  3. package/dist/ChannelManager.d.ts.map +1 -1
  4. package/dist/ChannelManager.js +9 -19
  5. package/dist/ChannelManager.js.map +1 -1
  6. package/dist/ConfigStore.d.ts +1 -1
  7. package/dist/ConfigStore.d.ts.map +1 -1
  8. package/dist/ConfigStore.js +25 -57
  9. package/dist/ConfigStore.js.map +1 -1
  10. package/dist/__tests__/ChannelManager.test.js +70 -0
  11. package/dist/__tests__/ChannelManager.test.js.map +1 -0
  12. package/dist/__tests__/ConfigStore.test.js +146 -0
  13. package/dist/__tests__/ConfigStore.test.js.map +1 -0
  14. package/dist/__tests__/adapters/TelegramAdapter.test.js +240 -0
  15. package/dist/__tests__/adapters/TelegramAdapter.test.js.map +1 -0
  16. package/dist/__tests__/utils/telegramHtml.test.js +86 -0
  17. package/dist/__tests__/utils/telegramHtml.test.js.map +1 -0
  18. package/dist/adapters/ChannelAdapter.d.ts +1 -1
  19. package/dist/adapters/ChannelAdapter.d.ts.map +1 -1
  20. package/dist/adapters/ChannelAdapter.js +7 -2
  21. package/dist/adapters/ChannelAdapter.js.map +1 -1
  22. package/dist/adapters/TelegramAdapter.d.ts +2 -2
  23. package/dist/adapters/TelegramAdapter.d.ts.map +1 -1
  24. package/dist/adapters/TelegramAdapter.js +32 -47
  25. package/dist/adapters/TelegramAdapter.js.map +1 -1
  26. package/dist/index.d.ts +6 -6
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -11
  29. package/dist/index.js.map +1 -1
  30. package/dist/types.js +7 -2
  31. package/dist/types.js.map +1 -1
  32. package/dist/utils/telegramHtml.js +36 -35
  33. package/dist/utils/telegramHtml.js.map +1 -1
  34. package/package.json +17 -12
  35. package/vitest.config.ts +20 -0
@@ -0,0 +1,240 @@
1
+ import * as telegrafModule from 'telegraf';
2
+ import { TelegramAdapter } from '../../adapters/TelegramAdapter.js';
3
+ // Mock telegraf
4
+ vi.mock('telegraf', ()=>{
5
+ const handlers = {};
6
+ const mockBot = {
7
+ launch: vi.fn().mockResolvedValue(undefined),
8
+ stop: vi.fn().mockResolvedValue(undefined),
9
+ on: vi.fn((event, handler)=>{
10
+ handlers[event] = handler;
11
+ }),
12
+ telegram: {
13
+ sendMessage: vi.fn().mockResolvedValue(undefined),
14
+ getMe: vi.fn().mockResolvedValue({
15
+ username: 'test_bot'
16
+ })
17
+ },
18
+ _handlers: handlers,
19
+ _triggerText: async (chatId, userId, text)=>{
20
+ const ctx = {
21
+ message: {
22
+ chat: {
23
+ id: chatId
24
+ },
25
+ from: {
26
+ id: userId
27
+ },
28
+ text,
29
+ date: Math.floor(Date.now() / 1000)
30
+ },
31
+ reply: vi.fn().mockResolvedValue(undefined)
32
+ };
33
+ if (handlers['text']) {
34
+ await handlers['text'](ctx);
35
+ }
36
+ return ctx;
37
+ }
38
+ };
39
+ return {
40
+ Telegraf: vi.fn(function() {
41
+ return mockBot;
42
+ }),
43
+ __mockBot: mockBot
44
+ };
45
+ });
46
+ function getMockBot() {
47
+ return telegrafModule.__mockBot;
48
+ }
49
+ describe('TelegramAdapter', ()=>{
50
+ let adapter;
51
+ beforeEach(()=>{
52
+ vi.clearAllMocks();
53
+ adapter = new TelegramAdapter({
54
+ botToken: 'test-token-123'
55
+ });
56
+ });
57
+ describe('type', ()=>{
58
+ it('should return "telegram"', ()=>{
59
+ expect(adapter.type).toBe('telegram');
60
+ });
61
+ });
62
+ describe('start', ()=>{
63
+ it('should launch the telegraf bot', async ()=>{
64
+ const bot = getMockBot();
65
+ await adapter.start();
66
+ expect(bot.launch).toHaveBeenCalled();
67
+ });
68
+ });
69
+ describe('stop', ()=>{
70
+ it('should stop the telegraf bot', async ()=>{
71
+ const bot = getMockBot();
72
+ await adapter.start();
73
+ await adapter.stop();
74
+ expect(bot.stop).toHaveBeenCalled();
75
+ });
76
+ });
77
+ describe('onMessage', ()=>{
78
+ it('should silently ignore messages when no handler is registered', async ()=>{
79
+ // Don't register a handler
80
+ await adapter.start();
81
+ const bot = getMockBot();
82
+ const ctx = await bot._triggerText(12345, 67890, 'hello');
83
+ // Should not throw or reply
84
+ expect(ctx.reply).not.toHaveBeenCalled();
85
+ });
86
+ it('should handle non-Error thrown by handler', async ()=>{
87
+ const handler = vi.fn().mockRejectedValue('string error');
88
+ adapter.onMessage(handler);
89
+ await adapter.start();
90
+ const bot = getMockBot();
91
+ const ctx = await bot._triggerText(12345, 67890, 'hello');
92
+ expect(ctx.reply).toHaveBeenCalledWith('Error processing message: Unknown error');
93
+ });
94
+ it('should call handler with IncomingMessage on incoming text', async ()=>{
95
+ const handler = vi.fn().mockResolvedValue(undefined);
96
+ adapter.onMessage(handler);
97
+ await adapter.start();
98
+ const bot = getMockBot();
99
+ await bot._triggerText(12345, 67890, 'hello agent');
100
+ expect(handler).toHaveBeenCalledTimes(1);
101
+ const msg = handler.mock.calls[0][0];
102
+ expect(msg.channelType).toBe('telegram');
103
+ expect(msg.chatId).toBe('12345');
104
+ expect(msg.userId).toBe('67890');
105
+ expect(msg.text).toBe('hello agent');
106
+ expect(msg.timestamp).toBeInstanceOf(Date);
107
+ });
108
+ it('should handle handler errors gracefully', async ()=>{
109
+ const handler = vi.fn().mockRejectedValue(new Error('handler failed'));
110
+ adapter.onMessage(handler);
111
+ await adapter.start();
112
+ const bot = getMockBot();
113
+ const ctx = await bot._triggerText(12345, 67890, 'hello');
114
+ // Should not throw, and should reply with error
115
+ expect(ctx.reply).toHaveBeenCalledWith(expect.stringContaining('Error processing message'));
116
+ });
117
+ });
118
+ describe('sendMessage', ()=>{
119
+ it('should send plain text with parse_mode HTML', async ()=>{
120
+ const bot = getMockBot();
121
+ await adapter.sendMessage('12345', 'hello from bot');
122
+ expect(bot.telegram.sendMessage).toHaveBeenCalledWith('12345', 'hello from bot', {
123
+ parse_mode: 'HTML'
124
+ });
125
+ });
126
+ it('should render markdown as Telegram HTML', async ()=>{
127
+ const bot = getMockBot();
128
+ await adapter.sendMessage('12345', '**bold** and *italic* and `code`');
129
+ const sent = bot.telegram.sendMessage.mock.calls[0][1];
130
+ expect(sent).toContain('<b>bold</b>');
131
+ expect(sent).toContain('<i>italic</i>');
132
+ expect(sent).toContain('<code>code</code>');
133
+ expect(bot.telegram.sendMessage.mock.calls[0][2]).toEqual({
134
+ parse_mode: 'HTML'
135
+ });
136
+ });
137
+ it('should chunk messages exceeding 4096 chars', async ()=>{
138
+ const bot = getMockBot();
139
+ const line = 'A'.repeat(100) + '\n';
140
+ const longMessage = line.repeat(50); // 5050 chars
141
+ await adapter.sendMessage('12345', longMessage);
142
+ expect(bot.telegram.sendMessage.mock.calls.length).toBeGreaterThan(1);
143
+ for (const call of bot.telegram.sendMessage.mock.calls){
144
+ expect(call[1].length).toBeLessThanOrEqual(4096);
145
+ expect(call[2]).toEqual({
146
+ parse_mode: 'HTML'
147
+ });
148
+ }
149
+ });
150
+ it('should hard split at 4096 when no newlines available', async ()=>{
151
+ const bot = getMockBot();
152
+ const longMessage = 'A'.repeat(5000);
153
+ await adapter.sendMessage('12345', longMessage);
154
+ expect(bot.telegram.sendMessage.mock.calls.length).toBe(2);
155
+ expect(bot.telegram.sendMessage.mock.calls[0][1].length).toBe(4096);
156
+ expect(bot.telegram.sendMessage.mock.calls[1][1].length).toBe(904);
157
+ });
158
+ it('should prefer paragraph (\\n\\n) over single \\n when chunking', async ()=>{
159
+ const bot = getMockBot();
160
+ // 4 paragraphs of ~1500 chars each, total > 4096
161
+ const paragraph = 'A'.repeat(1500);
162
+ const message = `${paragraph}\n\n${paragraph}\n\n${paragraph}\n\n${paragraph}`;
163
+ await adapter.sendMessage('12345', message);
164
+ // First chunk should end at a \n\n boundary, not mid-paragraph
165
+ const firstChunk = bot.telegram.sendMessage.mock.calls[0][1];
166
+ expect(firstChunk.endsWith('\n\n')).toBe(true);
167
+ });
168
+ it('should send short messages in a single call', async ()=>{
169
+ const bot = getMockBot();
170
+ await adapter.sendMessage('12345', 'short message');
171
+ expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);
172
+ });
173
+ it('should retry as plain text when Telegram rejects HTML with parse-entities error', async ()=>{
174
+ const bot = getMockBot();
175
+ const parseError = Object.assign(new Error('400: Bad Request'), {
176
+ description: "Bad Request: can't parse entities: Unsupported start tag \"foo\""
177
+ });
178
+ bot.telegram.sendMessage.mockRejectedValueOnce(parseError).mockResolvedValueOnce(undefined);
179
+ await adapter.sendMessage('12345', '**hello**');
180
+ expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(2);
181
+ // First call: rendered HTML with parse_mode
182
+ const [, htmlChunk, htmlOpts] = bot.telegram.sendMessage.mock.calls[0];
183
+ expect(htmlChunk).toContain('<b>hello</b>');
184
+ expect(htmlOpts).toEqual({
185
+ parse_mode: 'HTML'
186
+ });
187
+ // Second call: same content, plain text (tags stripped, no parse_mode)
188
+ const [, plainChunk, plainOpts] = bot.telegram.sendMessage.mock.calls[1];
189
+ expect(plainChunk).toBe('hello');
190
+ expect(plainOpts).toBeUndefined();
191
+ });
192
+ it('should detect parse-entities error from "message" field too', async ()=>{
193
+ const bot = getMockBot();
194
+ // Some error shapes carry the marker on `message` rather than `description`
195
+ const parseError = new Error("can't parse entities");
196
+ bot.telegram.sendMessage.mockRejectedValueOnce(parseError).mockResolvedValueOnce(undefined);
197
+ await adapter.sendMessage('12345', '**hi**');
198
+ expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(2);
199
+ });
200
+ it('should decode HTML entities when falling back to plain text', async ()=>{
201
+ const bot = getMockBot();
202
+ const parseError = Object.assign(new Error('400'), {
203
+ description: "Bad Request: can't parse entities"
204
+ });
205
+ bot.telegram.sendMessage.mockRejectedValueOnce(parseError).mockResolvedValueOnce(undefined);
206
+ // Source has chars that escapeHtml encodes; fallback should decode them
207
+ await adapter.sendMessage('12345', 'a < b && c > d');
208
+ const [, plainChunk] = bot.telegram.sendMessage.mock.calls[1];
209
+ expect(plainChunk).toContain('a < b && c > d');
210
+ expect(plainChunk).not.toContain('&lt;');
211
+ expect(plainChunk).not.toContain('&amp;');
212
+ });
213
+ it('should propagate non-parse-entities errors without falling back', async ()=>{
214
+ const bot = getMockBot();
215
+ const otherError = Object.assign(new Error('403'), {
216
+ description: 'Forbidden: bot was blocked by the user'
217
+ });
218
+ bot.telegram.sendMessage.mockRejectedValueOnce(otherError);
219
+ await expect(adapter.sendMessage('12345', 'hi')).rejects.toBe(otherError);
220
+ // Only the HTML attempt should have happened — no fallback retry
221
+ expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);
222
+ });
223
+ });
224
+ describe('isHealthy', ()=>{
225
+ it('should return true after start', async ()=>{
226
+ await adapter.start();
227
+ expect(await adapter.isHealthy()).toBe(true);
228
+ });
229
+ it('should return false before start', async ()=>{
230
+ expect(await adapter.isHealthy()).toBe(false);
231
+ });
232
+ it('should return false after stop', async ()=>{
233
+ await adapter.start();
234
+ await adapter.stop();
235
+ expect(await adapter.isHealthy()).toBe(false);
236
+ });
237
+ });
238
+ });
239
+
240
+ //# sourceMappingURL=TelegramAdapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/adapters/TelegramAdapter.test.ts"],"sourcesContent":["import * as telegrafModule from 'telegraf';\nimport { TelegramAdapter } from '../../adapters/TelegramAdapter.js';\nimport type { IncomingMessage } from '../../types.js';\n\n// Mock telegraf\nvi.mock('telegraf', () => {\n const handlers: Record<string, (...args: any[]) => any> = {};\n const mockBot = {\n launch: vi.fn().mockResolvedValue(undefined),\n stop: vi.fn().mockResolvedValue(undefined),\n on: vi.fn((event: string, handler: (...args: any[]) => any) => {\n handlers[event] = handler;\n }),\n telegram: {\n sendMessage: vi.fn().mockResolvedValue(undefined),\n getMe: vi.fn().mockResolvedValue({ username: 'test_bot' }),\n },\n _handlers: handlers,\n _triggerText: async (chatId: number, userId: number, text: string) => {\n const ctx = {\n message: {\n chat: { id: chatId },\n from: { id: userId },\n text,\n date: Math.floor(Date.now() / 1000),\n },\n reply: vi.fn().mockResolvedValue(undefined),\n };\n if (handlers['text']) {\n await handlers['text'](ctx);\n }\n return ctx;\n },\n };\n return {\n Telegraf: vi.fn(function () { return mockBot; }),\n __mockBot: mockBot,\n };\n});\n\nfunction getMockBot() {\n return (telegrafModule as unknown as { __mockBot: ReturnType<typeof vi.fn>['mock'] & Record<string, unknown> }).__mockBot;\n}\n\ndescribe('TelegramAdapter', () => {\n let adapter: TelegramAdapter;\n\n beforeEach(() => {\n vi.clearAllMocks();\n adapter = new TelegramAdapter({ botToken: 'test-token-123' });\n });\n\n describe('type', () => {\n it('should return \"telegram\"', () => {\n expect(adapter.type).toBe('telegram');\n });\n });\n\n describe('start', () => {\n it('should launch the telegraf bot', async () => {\n const bot = getMockBot();\n await adapter.start();\n expect(bot.launch).toHaveBeenCalled();\n });\n });\n\n describe('stop', () => {\n it('should stop the telegraf bot', async () => {\n const bot = getMockBot();\n await adapter.start();\n await adapter.stop();\n expect(bot.stop).toHaveBeenCalled();\n });\n });\n\n describe('onMessage', () => {\n it('should silently ignore messages when no handler is registered', async () => {\n // Don't register a handler\n await adapter.start();\n\n const bot = getMockBot();\n const ctx = await bot._triggerText(12345, 67890, 'hello');\n\n // Should not throw or reply\n expect(ctx.reply).not.toHaveBeenCalled();\n });\n\n it('should handle non-Error thrown by handler', async () => {\n const handler = vi.fn().mockRejectedValue('string error');\n adapter.onMessage(handler);\n await adapter.start();\n\n const bot = getMockBot();\n const ctx = await bot._triggerText(12345, 67890, 'hello');\n\n expect(ctx.reply).toHaveBeenCalledWith(\n 'Error processing message: Unknown error'\n );\n });\n\n it('should call handler with IncomingMessage on incoming text', async () => {\n const handler = vi.fn().mockResolvedValue(undefined);\n adapter.onMessage(handler);\n await adapter.start();\n\n const bot = getMockBot();\n await bot._triggerText(12345, 67890, 'hello agent');\n\n expect(handler).toHaveBeenCalledTimes(1);\n const msg: IncomingMessage = handler.mock.calls[0][0];\n expect(msg.channelType).toBe('telegram');\n expect(msg.chatId).toBe('12345');\n expect(msg.userId).toBe('67890');\n expect(msg.text).toBe('hello agent');\n expect(msg.timestamp).toBeInstanceOf(Date);\n });\n\n it('should handle handler errors gracefully', async () => {\n const handler = vi.fn().mockRejectedValue(new Error('handler failed'));\n adapter.onMessage(handler);\n await adapter.start();\n\n const bot = getMockBot();\n const ctx = await bot._triggerText(12345, 67890, 'hello');\n\n // Should not throw, and should reply with error\n expect(ctx.reply).toHaveBeenCalledWith(\n expect.stringContaining('Error processing message')\n );\n });\n });\n\n describe('sendMessage', () => {\n it('should send plain text with parse_mode HTML', async () => {\n const bot = getMockBot();\n await adapter.sendMessage('12345', 'hello from bot');\n\n expect(bot.telegram.sendMessage).toHaveBeenCalledWith(\n '12345',\n 'hello from bot',\n { parse_mode: 'HTML' }\n );\n });\n\n it('should render markdown as Telegram HTML', async () => {\n const bot = getMockBot();\n await adapter.sendMessage('12345', '**bold** and *italic* and `code`');\n\n const sent = bot.telegram.sendMessage.mock.calls[0][1];\n expect(sent).toContain('<b>bold</b>');\n expect(sent).toContain('<i>italic</i>');\n expect(sent).toContain('<code>code</code>');\n expect(bot.telegram.sendMessage.mock.calls[0][2]).toEqual({\n parse_mode: 'HTML',\n });\n });\n\n it('should chunk messages exceeding 4096 chars', async () => {\n const bot = getMockBot();\n const line = 'A'.repeat(100) + '\\n';\n const longMessage = line.repeat(50); // 5050 chars\n\n await adapter.sendMessage('12345', longMessage);\n\n expect(bot.telegram.sendMessage.mock.calls.length).toBeGreaterThan(1);\n for (const call of bot.telegram.sendMessage.mock.calls) {\n expect(call[1].length).toBeLessThanOrEqual(4096);\n expect(call[2]).toEqual({ parse_mode: 'HTML' });\n }\n });\n\n it('should hard split at 4096 when no newlines available', async () => {\n const bot = getMockBot();\n const longMessage = 'A'.repeat(5000);\n\n await adapter.sendMessage('12345', longMessage);\n\n expect(bot.telegram.sendMessage.mock.calls.length).toBe(2);\n expect(bot.telegram.sendMessage.mock.calls[0][1].length).toBe(4096);\n expect(bot.telegram.sendMessage.mock.calls[1][1].length).toBe(904);\n });\n\n it('should prefer paragraph (\\\\n\\\\n) over single \\\\n when chunking', async () => {\n const bot = getMockBot();\n // 4 paragraphs of ~1500 chars each, total > 4096\n const paragraph = 'A'.repeat(1500);\n const message = `${paragraph}\\n\\n${paragraph}\\n\\n${paragraph}\\n\\n${paragraph}`;\n\n await adapter.sendMessage('12345', message);\n\n // First chunk should end at a \\n\\n boundary, not mid-paragraph\n const firstChunk = bot.telegram.sendMessage.mock.calls[0][1];\n expect(firstChunk.endsWith('\\n\\n')).toBe(true);\n });\n\n it('should send short messages in a single call', async () => {\n const bot = getMockBot();\n await adapter.sendMessage('12345', 'short message');\n\n expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);\n });\n\n it('should retry as plain text when Telegram rejects HTML with parse-entities error', async () => {\n const bot = getMockBot();\n const parseError = Object.assign(new Error('400: Bad Request'), {\n description: \"Bad Request: can't parse entities: Unsupported start tag \\\"foo\\\"\",\n });\n bot.telegram.sendMessage\n .mockRejectedValueOnce(parseError)\n .mockResolvedValueOnce(undefined);\n\n await adapter.sendMessage('12345', '**hello**');\n\n expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(2);\n\n // First call: rendered HTML with parse_mode\n const [, htmlChunk, htmlOpts] = bot.telegram.sendMessage.mock.calls[0];\n expect(htmlChunk).toContain('<b>hello</b>');\n expect(htmlOpts).toEqual({ parse_mode: 'HTML' });\n\n // Second call: same content, plain text (tags stripped, no parse_mode)\n const [, plainChunk, plainOpts] = bot.telegram.sendMessage.mock.calls[1];\n expect(plainChunk).toBe('hello');\n expect(plainOpts).toBeUndefined();\n });\n\n it('should detect parse-entities error from \"message\" field too', async () => {\n const bot = getMockBot();\n // Some error shapes carry the marker on `message` rather than `description`\n const parseError = new Error(\"can't parse entities\");\n bot.telegram.sendMessage\n .mockRejectedValueOnce(parseError)\n .mockResolvedValueOnce(undefined);\n\n await adapter.sendMessage('12345', '**hi**');\n\n expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(2);\n });\n\n it('should decode HTML entities when falling back to plain text', async () => {\n const bot = getMockBot();\n const parseError = Object.assign(new Error('400'), {\n description: \"Bad Request: can't parse entities\",\n });\n bot.telegram.sendMessage\n .mockRejectedValueOnce(parseError)\n .mockResolvedValueOnce(undefined);\n\n // Source has chars that escapeHtml encodes; fallback should decode them\n await adapter.sendMessage('12345', 'a < b && c > d');\n\n const [, plainChunk] = bot.telegram.sendMessage.mock.calls[1];\n expect(plainChunk).toContain('a < b && c > d');\n expect(plainChunk).not.toContain('&lt;');\n expect(plainChunk).not.toContain('&amp;');\n });\n\n it('should propagate non-parse-entities errors without falling back', async () => {\n const bot = getMockBot();\n const otherError = Object.assign(new Error('403'), {\n description: 'Forbidden: bot was blocked by the user',\n });\n bot.telegram.sendMessage.mockRejectedValueOnce(otherError);\n\n await expect(adapter.sendMessage('12345', 'hi')).rejects.toBe(otherError);\n\n // Only the HTML attempt should have happened — no fallback retry\n expect(bot.telegram.sendMessage).toHaveBeenCalledTimes(1);\n });\n });\n\n describe('isHealthy', () => {\n it('should return true after start', async () => {\n await adapter.start();\n expect(await adapter.isHealthy()).toBe(true);\n });\n\n it('should return false before start', async () => {\n expect(await adapter.isHealthy()).toBe(false);\n });\n\n it('should return false after stop', async () => {\n await adapter.start();\n await adapter.stop();\n expect(await adapter.isHealthy()).toBe(false);\n });\n });\n});\n"],"names":["telegrafModule","TelegramAdapter","vi","mock","handlers","mockBot","launch","fn","mockResolvedValue","undefined","stop","on","event","handler","telegram","sendMessage","getMe","username","_handlers","_triggerText","chatId","userId","text","ctx","message","chat","id","from","date","Math","floor","Date","now","reply","Telegraf","__mockBot","getMockBot","describe","adapter","beforeEach","clearAllMocks","botToken","it","expect","type","toBe","bot","start","toHaveBeenCalled","not","mockRejectedValue","onMessage","toHaveBeenCalledWith","toHaveBeenCalledTimes","msg","calls","channelType","timestamp","toBeInstanceOf","Error","stringContaining","parse_mode","sent","toContain","toEqual","line","repeat","longMessage","length","toBeGreaterThan","call","toBeLessThanOrEqual","paragraph","firstChunk","endsWith","parseError","Object","assign","description","mockRejectedValueOnce","mockResolvedValueOnce","htmlChunk","htmlOpts","plainChunk","plainOpts","toBeUndefined","otherError","rejects","isHealthy"],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { markdownToTelegramHtml } from '../../utils/telegramHtml.js';
2
+ describe('markdownToTelegramHtml', ()=>{
3
+ it('renders bold, italic, strikethrough', ()=>{
4
+ const out = markdownToTelegramHtml('**b** _i_ ~~s~~');
5
+ expect(out).toContain('<b>b</b>');
6
+ expect(out).toContain('<i>i</i>');
7
+ expect(out).toContain('<s>s</s>');
8
+ });
9
+ it('renders inline code and fenced code with language', ()=>{
10
+ const md = 'Run `npm test`\n\n```ts\nconst x = 1;\n```';
11
+ const out = markdownToTelegramHtml(md);
12
+ expect(out).toContain('<code>npm test</code>');
13
+ expect(out).toContain('<pre><code class="language-ts">const x = 1;</code></pre>');
14
+ });
15
+ it('renders links and converts headings to bold', ()=>{
16
+ const md = '# Title\n\nSee [docs](https://x.com).';
17
+ const out = markdownToTelegramHtml(md);
18
+ expect(out).toContain('<b>Title</b>');
19
+ expect(out).toContain('<a href="https://x.com">docs</a>');
20
+ });
21
+ it('renders images as alt-text links', ()=>{
22
+ const out = markdownToTelegramHtml('![diagram](https://x.com/d.png)');
23
+ expect(out).toBe('<a href="https://x.com/d.png">diagram</a>');
24
+ });
25
+ it('falls back to URL when image has no alt text', ()=>{
26
+ const out = markdownToTelegramHtml('![](https://x.com/d.png)');
27
+ expect(out).toContain('<a href="https://x.com/d.png">https://x.com/d.png</a>');
28
+ });
29
+ it('renders unordered lists with bullets', ()=>{
30
+ const out = markdownToTelegramHtml('- one\n- two\n- three');
31
+ expect(out).toContain('• one');
32
+ expect(out).toContain('• two');
33
+ expect(out).toContain('• three');
34
+ expect(out).not.toContain('<ul>');
35
+ });
36
+ it('renders ordered lists with numbers', ()=>{
37
+ const out = markdownToTelegramHtml('1. one\n2. two');
38
+ expect(out).toContain('1. one');
39
+ expect(out).toContain('2. two');
40
+ expect(out).not.toContain('<ol>');
41
+ });
42
+ it('renders tables as ASCII inside <pre>', ()=>{
43
+ const md = '| a | b |\n|---|---|\n| 1 | 2 |';
44
+ const out = markdownToTelegramHtml(md);
45
+ expect(out.startsWith('<pre>')).toBe(true);
46
+ expect(out).toContain('a');
47
+ expect(out).toContain('b');
48
+ expect(out).toContain('1');
49
+ expect(out).toContain('2');
50
+ expect(out).not.toContain('<table>');
51
+ });
52
+ it('uses <blockquote> for quotes', ()=>{
53
+ const out = markdownToTelegramHtml('> quoted');
54
+ expect(out).toContain('<blockquote>');
55
+ expect(out).toContain('quoted');
56
+ expect(out).toContain('</blockquote>');
57
+ });
58
+ it('escapes HTML special chars in plain text', ()=>{
59
+ const out = markdownToTelegramHtml('a < b && c > d');
60
+ expect(out).toContain('&lt;');
61
+ expect(out).toContain('&amp;');
62
+ expect(out).toContain('&gt;');
63
+ expect(out).not.toContain(' < ');
64
+ });
65
+ it('escapes HTML special chars inside code', ()=>{
66
+ const out = markdownToTelegramHtml('`<script>`');
67
+ expect(out).toContain('<code>&lt;script&gt;</code>');
68
+ });
69
+ it('strips raw HTML blocks', ()=>{
70
+ const out = markdownToTelegramHtml('hello\n\n<div>raw</div>\n\nworld');
71
+ expect(out).not.toContain('<div>');
72
+ expect(out).toContain('hello');
73
+ expect(out).toContain('world');
74
+ });
75
+ it('renders horizontal rule as a divider', ()=>{
76
+ const out = markdownToTelegramHtml('above\n\n---\n\nbelow');
77
+ expect(out).toContain('above');
78
+ expect(out).toContain('———');
79
+ expect(out).toContain('below');
80
+ });
81
+ it('passes plain text through unchanged', ()=>{
82
+ expect(markdownToTelegramHtml('hello world')).toBe('hello world');
83
+ });
84
+ });
85
+
86
+ //# sourceMappingURL=telegramHtml.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/utils/telegramHtml.test.ts"],"sourcesContent":["import { markdownToTelegramHtml } from '../../utils/telegramHtml.js';\n\ndescribe('markdownToTelegramHtml', () => {\n it('renders bold, italic, strikethrough', () => {\n const out = markdownToTelegramHtml('**b** _i_ ~~s~~');\n expect(out).toContain('<b>b</b>');\n expect(out).toContain('<i>i</i>');\n expect(out).toContain('<s>s</s>');\n });\n\n it('renders inline code and fenced code with language', () => {\n const md = 'Run `npm test`\\n\\n```ts\\nconst x = 1;\\n```';\n const out = markdownToTelegramHtml(md);\n expect(out).toContain('<code>npm test</code>');\n expect(out).toContain('<pre><code class=\"language-ts\">const x = 1;</code></pre>');\n });\n\n it('renders links and converts headings to bold', () => {\n const md = '# Title\\n\\nSee [docs](https://x.com).';\n const out = markdownToTelegramHtml(md);\n expect(out).toContain('<b>Title</b>');\n expect(out).toContain('<a href=\"https://x.com\">docs</a>');\n });\n\n it('renders images as alt-text links', () => {\n const out = markdownToTelegramHtml('![diagram](https://x.com/d.png)');\n expect(out).toBe('<a href=\"https://x.com/d.png\">diagram</a>');\n });\n\n it('falls back to URL when image has no alt text', () => {\n const out = markdownToTelegramHtml('![](https://x.com/d.png)');\n expect(out).toContain('<a href=\"https://x.com/d.png\">https://x.com/d.png</a>');\n });\n\n it('renders unordered lists with bullets', () => {\n const out = markdownToTelegramHtml('- one\\n- two\\n- three');\n expect(out).toContain('• one');\n expect(out).toContain('• two');\n expect(out).toContain('• three');\n expect(out).not.toContain('<ul>');\n });\n\n it('renders ordered lists with numbers', () => {\n const out = markdownToTelegramHtml('1. one\\n2. two');\n expect(out).toContain('1. one');\n expect(out).toContain('2. two');\n expect(out).not.toContain('<ol>');\n });\n\n it('renders tables as ASCII inside <pre>', () => {\n const md = '| a | b |\\n|---|---|\\n| 1 | 2 |';\n const out = markdownToTelegramHtml(md);\n expect(out.startsWith('<pre>')).toBe(true);\n expect(out).toContain('a');\n expect(out).toContain('b');\n expect(out).toContain('1');\n expect(out).toContain('2');\n expect(out).not.toContain('<table>');\n });\n\n it('uses <blockquote> for quotes', () => {\n const out = markdownToTelegramHtml('> quoted');\n expect(out).toContain('<blockquote>');\n expect(out).toContain('quoted');\n expect(out).toContain('</blockquote>');\n });\n\n it('escapes HTML special chars in plain text', () => {\n const out = markdownToTelegramHtml('a < b && c > d');\n expect(out).toContain('&lt;');\n expect(out).toContain('&amp;');\n expect(out).toContain('&gt;');\n expect(out).not.toContain(' < ');\n });\n\n it('escapes HTML special chars inside code', () => {\n const out = markdownToTelegramHtml('`<script>`');\n expect(out).toContain('<code>&lt;script&gt;</code>');\n });\n\n it('strips raw HTML blocks', () => {\n const out = markdownToTelegramHtml('hello\\n\\n<div>raw</div>\\n\\nworld');\n expect(out).not.toContain('<div>');\n expect(out).toContain('hello');\n expect(out).toContain('world');\n });\n\n it('renders horizontal rule as a divider', () => {\n const out = markdownToTelegramHtml('above\\n\\n---\\n\\nbelow');\n expect(out).toContain('above');\n expect(out).toContain('———');\n expect(out).toContain('below');\n });\n\n it('passes plain text through unchanged', () => {\n expect(markdownToTelegramHtml('hello world')).toBe('hello world');\n });\n});\n"],"names":["markdownToTelegramHtml","describe","it","out","expect","toContain","md","toBe","not","startsWith"],"mappings":"AAAA,SAASA,sBAAsB,QAAQ,8BAA8B;AAErEC,SAAS,0BAA0B;IAC/BC,GAAG,uCAAuC;QACtC,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,qDAAqD;QACpD,MAAMI,KAAK;QACX,MAAMH,MAAMH,uBAAuBM;QACnCF,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,+CAA+C;QAC9C,MAAMI,KAAK;QACX,MAAMH,MAAMH,uBAAuBM;QACnCF,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,oCAAoC;QACnC,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKI,IAAI,CAAC;IACrB;IAEAL,GAAG,gDAAgD;QAC/C,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,wCAAwC;QACvC,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKK,GAAG,CAACH,SAAS,CAAC;IAC9B;IAEAH,GAAG,sCAAsC;QACrC,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKK,GAAG,CAACH,SAAS,CAAC;IAC9B;IAEAH,GAAG,wCAAwC;QACvC,MAAMI,KAAK;QACX,MAAMH,MAAMH,uBAAuBM;QACnCF,OAAOD,IAAIM,UAAU,CAAC,UAAUF,IAAI,CAAC;QACrCH,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKK,GAAG,CAACH,SAAS,CAAC;IAC9B;IAEAH,GAAG,gCAAgC;QAC/B,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,4CAA4C;QAC3C,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKK,GAAG,CAACH,SAAS,CAAC;IAC9B;IAEAH,GAAG,0CAA0C;QACzC,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,0BAA0B;QACzB,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKK,GAAG,CAACH,SAAS,CAAC;QAC1BD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,wCAAwC;QACvC,MAAMC,MAAMH,uBAAuB;QACnCI,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;QACtBD,OAAOD,KAAKE,SAAS,CAAC;IAC1B;IAEAH,GAAG,uCAAuC;QACtCE,OAAOJ,uBAAuB,gBAAgBO,IAAI,CAAC;IACvD;AACJ"}
@@ -1,4 +1,4 @@
1
- import type { IncomingMessage } from '../types';
1
+ import type { IncomingMessage } from '../types.js';
2
2
  /**
3
3
  * Interface for messaging platform adapters.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/ChannelAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB;;;;OAIG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzD;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAElE,oDAAoD;IACpD,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC"}
1
+ {"version":3,"file":"ChannelAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/ChannelAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB;;;;OAIG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzD;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAElE,oDAAoD;IACpD,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC"}
@@ -1,3 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ /**
2
+ * Interface for messaging platform adapters.
3
+ *
4
+ * Implementations connect to a specific platform (Telegram, Slack, etc.)
5
+ * and provide a generic send/receive abstraction.
6
+ */ export { };
7
+
3
8
  //# sourceMappingURL=ChannelAdapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelAdapter.js","sourceRoot":"","sources":["../../src/adapters/ChannelAdapter.ts"],"names":[],"mappings":""}
1
+ {"version":3,"sources":["../../src/adapters/ChannelAdapter.ts"],"sourcesContent":["import type { IncomingMessage } from '../types.js';\n\n/**\n * Interface for messaging platform adapters.\n *\n * Implementations connect to a specific platform (Telegram, Slack, etc.)\n * and provide a generic send/receive abstraction.\n */\nexport interface ChannelAdapter {\n /** Identifier for this channel type (e.g., 'telegram') */\n readonly type: string;\n\n /** Start listening for incoming messages */\n start(): Promise<void>;\n\n /** Stop listening and clean up resources */\n stop(): Promise<void>;\n\n /**\n * Send a message to a specific chat.\n * Implementations should handle platform-specific limits\n * (e.g., chunking at 4096 chars for Telegram).\n */\n sendMessage(chatId: string, text: string): Promise<void>;\n\n /**\n * Register a handler for incoming text messages.\n * Fire-and-forget — handler returns void.\n * Responses are sent separately via sendMessage().\n */\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void;\n\n /** Check if the adapter is connected and healthy */\n isHealthy(): Promise<boolean>;\n}\n"],"names":[],"mappings":"AAEA;;;;;CAKC,GACD,WA0BC"}
@@ -1,5 +1,5 @@
1
- import type { ChannelAdapter } from './ChannelAdapter';
2
- import type { IncomingMessage } from '../types';
1
+ import type { ChannelAdapter } from './ChannelAdapter.js';
2
+ import type { IncomingMessage } from '../types.js';
3
3
  export declare const TELEGRAM_CHANNEL_TYPE = "telegram";
4
4
  export declare const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;
5
5
  export interface TelegramAdapterOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"TelegramAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/TelegramAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAChD,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAGhD,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,cAAyB;IAEtC,OAAO,CAAC,GAAG,CAAW;IACtB,OAAO,CAAC,cAAc,CAA0D;IAChF,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,sBAAsB;IAIrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B;;;;;OAKG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe9D,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI3D,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGtC"}
1
+ {"version":3,"file":"TelegramAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/TelegramAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAChD,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAGhD,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,cAAyB;IAEtC,OAAO,CAAC,GAAG,CAAW;IACtB,OAAO,CAAC,cAAc,CAA0D;IAChF,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,sBAAsB;IAIrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B;;;;;OAKG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe9D,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI3D,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGtC"}
@@ -1,36 +1,31 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TelegramAdapter = exports.TELEGRAM_MAX_MESSAGE_LENGTH = exports.TELEGRAM_CHANNEL_TYPE = void 0;
4
- const telegraf_1 = require("telegraf");
5
- const telegramHtml_1 = require("../utils/telegramHtml");
6
- exports.TELEGRAM_CHANNEL_TYPE = 'telegram';
7
- exports.TELEGRAM_MAX_MESSAGE_LENGTH = 4096;
1
+ import { Telegraf } from 'telegraf';
2
+ import { markdownToTelegramHtml } from '../utils/telegramHtml.js';
3
+ export const TELEGRAM_CHANNEL_TYPE = 'telegram';
4
+ export const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;
8
5
  const TELEGRAM_PARSE_MODE = 'HTML';
9
6
  /**
10
7
  * Telegram Bot API adapter using telegraf with long polling.
11
- */
12
- class TelegramAdapter {
13
- constructor(options) {
14
- this.type = exports.TELEGRAM_CHANNEL_TYPE;
15
- this.messageHandler = null;
16
- this.running = false;
17
- this.bot = new telegraf_1.Telegraf(options.botToken);
8
+ */ export class TelegramAdapter {
9
+ type = TELEGRAM_CHANNEL_TYPE;
10
+ bot;
11
+ messageHandler = null;
12
+ running = false;
13
+ constructor(options){
14
+ this.bot = new Telegraf(options.botToken);
18
15
  }
19
16
  async start() {
20
- this.bot.on('text', async (ctx) => {
21
- if (!this.messageHandler)
22
- return;
17
+ this.bot.on('text', async (ctx)=>{
18
+ if (!this.messageHandler) return;
23
19
  const msg = {
24
- channelType: exports.TELEGRAM_CHANNEL_TYPE,
20
+ channelType: TELEGRAM_CHANNEL_TYPE,
25
21
  chatId: String(ctx.message.chat.id),
26
22
  userId: String(ctx.message.from.id),
27
23
  text: ctx.message.text,
28
- timestamp: new Date(ctx.message.date * 1000),
24
+ timestamp: new Date(ctx.message.date * 1000)
29
25
  };
30
26
  try {
31
27
  await this.messageHandler(msg);
32
- }
33
- catch (error) {
28
+ } catch (error) {
34
29
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
35
30
  await ctx.reply(`Error processing message: ${errorMessage}`);
36
31
  }
@@ -47,17 +42,16 @@ class TelegramAdapter {
47
42
  * Long messages are chunked at paragraph boundaries when possible; very
48
43
  * long single blocks (e.g. a `<pre>` over 4096 chars) may still split
49
44
  * mid-tag and produce a partial render in the second chunk.
50
- */
51
- async sendMessage(chatId, text) {
52
- const html = (0, telegramHtml_1.markdownToTelegramHtml)(text);
53
- const chunks = chunkMessage(html, exports.TELEGRAM_MAX_MESSAGE_LENGTH);
54
- for (const chunk of chunks) {
45
+ */ async sendMessage(chatId, text) {
46
+ const html = markdownToTelegramHtml(text);
47
+ const chunks = chunkMessage(html, TELEGRAM_MAX_MESSAGE_LENGTH);
48
+ for (const chunk of chunks){
55
49
  try {
56
- await this.bot.telegram.sendMessage(chatId, chunk, { parse_mode: TELEGRAM_PARSE_MODE });
57
- }
58
- catch (error) {
59
- if (!isParseEntitiesError(error))
60
- throw error;
50
+ await this.bot.telegram.sendMessage(chatId, chunk, {
51
+ parse_mode: TELEGRAM_PARSE_MODE
52
+ });
53
+ } catch (error) {
54
+ if (!isParseEntitiesError(error)) throw error;
61
55
  // Telegram rejected the rendered HTML — fall back to plain text
62
56
  // so the user still gets the content (just unformatted).
63
57
  await this.bot.telegram.sendMessage(chatId, htmlToPlainText(chunk));
@@ -71,30 +65,22 @@ class TelegramAdapter {
71
65
  return this.running;
72
66
  }
73
67
  }
74
- exports.TelegramAdapter = TelegramAdapter;
75
68
  function isParseEntitiesError(error) {
76
- if (!error || typeof error !== 'object')
77
- return false;
69
+ if (!error || typeof error !== 'object') return false;
78
70
  const description = error.description;
79
71
  const message = error.message;
80
72
  return ((description ?? '') + (message ?? '')).includes("can't parse entities");
81
73
  }
82
74
  function htmlToPlainText(html) {
83
- return html
84
- .replace(/<[^>]+>/g, '')
85
- .replace(/&lt;/g, '<')
86
- .replace(/&gt;/g, '>')
87
- .replace(/&quot;/g, '"')
88
- .replace(/&amp;/g, '&');
75
+ return html.replace(/<[^>]+>/g, '').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&amp;/g, '&');
89
76
  }
90
77
  /**
91
78
  * Split text into chunks of maxLen or fewer characters. Prefers paragraph
92
79
  * boundaries (\n\n), then single newlines (\n), then hard-splits at maxLen.
93
- */
94
- function chunkMessage(text, maxLen) {
80
+ */ function chunkMessage(text, maxLen) {
95
81
  const chunks = [];
96
82
  let remaining = text;
97
- while (remaining.length > 0) {
83
+ while(remaining.length > 0){
98
84
  if (remaining.length <= maxLen) {
99
85
  chunks.push(remaining);
100
86
  break;
@@ -104,11 +90,9 @@ function chunkMessage(text, maxLen) {
104
90
  let splitAt;
105
91
  if (lastParagraph > 0) {
106
92
  splitAt = lastParagraph + 2;
107
- }
108
- else if (lastNewline > 0) {
93
+ } else if (lastNewline > 0) {
109
94
  splitAt = lastNewline + 1;
110
- }
111
- else {
95
+ } else {
112
96
  splitAt = maxLen;
113
97
  }
114
98
  chunks.push(remaining.slice(0, splitAt));
@@ -116,4 +100,5 @@ function chunkMessage(text, maxLen) {
116
100
  }
117
101
  return chunks;
118
102
  }
103
+
119
104
  //# sourceMappingURL=TelegramAdapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TelegramAdapter.js","sourceRoot":"","sources":["../../src/adapters/TelegramAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/adapters/TelegramAdapter.ts"],"sourcesContent":["import { Telegraf } from 'telegraf';\nimport type { ChannelAdapter } from './ChannelAdapter.js';\nimport { markdownToTelegramHtml } from '../utils/telegramHtml.js';\nimport type { IncomingMessage } from '../types.js';\n\nexport const TELEGRAM_CHANNEL_TYPE = 'telegram';\nexport const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;\nconst TELEGRAM_PARSE_MODE = 'HTML' as const;\n\nexport interface TelegramAdapterOptions {\n botToken: string;\n}\n\n/**\n * Telegram Bot API adapter using telegraf with long polling.\n */\nexport class TelegramAdapter implements ChannelAdapter {\n readonly type = TELEGRAM_CHANNEL_TYPE;\n\n private bot: Telegraf;\n private messageHandler: ((msg: IncomingMessage) => Promise<void>) | null = null;\n private running = false;\n\n constructor(options: TelegramAdapterOptions) {\n this.bot = new Telegraf(options.botToken);\n }\n\n async start(): Promise<void> {\n this.bot.on('text', async (ctx) => {\n if (!this.messageHandler) return;\n\n const msg: IncomingMessage = {\n channelType: TELEGRAM_CHANNEL_TYPE,\n chatId: String(ctx.message.chat.id),\n userId: String(ctx.message.from.id),\n text: ctx.message.text,\n timestamp: new Date(ctx.message.date * 1000),\n };\n\n try {\n await this.messageHandler(msg);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n await ctx.reply(`Error processing message: ${errorMessage}`);\n }\n });\n\n await this.bot.launch();\n this.running = true;\n }\n\n async stop(): Promise<void> {\n this.running = false;\n await this.bot.stop();\n }\n\n /**\n * Input is treated as markdown and rendered as Telegram-compatible HTML.\n * Long messages are chunked at paragraph boundaries when possible; very\n * long single blocks (e.g. a `<pre>` over 4096 chars) may still split\n * mid-tag and produce a partial render in the second chunk.\n */\n async sendMessage(chatId: string, text: string): Promise<void> {\n const html = markdownToTelegramHtml(text);\n const chunks = chunkMessage(html, TELEGRAM_MAX_MESSAGE_LENGTH);\n for (const chunk of chunks) {\n try {\n await this.bot.telegram.sendMessage(chatId, chunk, { parse_mode: TELEGRAM_PARSE_MODE });\n } catch (error) {\n if (!isParseEntitiesError(error)) throw error;\n // Telegram rejected the rendered HTML — fall back to plain text\n // so the user still gets the content (just unformatted).\n await this.bot.telegram.sendMessage(chatId, htmlToPlainText(chunk));\n }\n }\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.messageHandler = handler;\n }\n\n async isHealthy(): Promise<boolean> {\n return this.running;\n }\n}\n\nfunction isParseEntitiesError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false;\n const description = (error as { description?: string }).description;\n const message = (error as { message?: string }).message;\n return ((description ?? '') + (message ?? '')).includes(\"can't parse entities\");\n}\n\nfunction htmlToPlainText(html: string): string {\n return html\n .replace(/<[^>]+>/g, '')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&amp;/g, '&');\n}\n\n/**\n * Split text into chunks of maxLen or fewer characters. Prefers paragraph\n * boundaries (\\n\\n), then single newlines (\\n), then hard-splits at maxLen.\n */\nfunction chunkMessage(text: string, maxLen: number): string[] {\n const chunks: string[] = [];\n let remaining = text;\n\n while (remaining.length > 0) {\n if (remaining.length <= maxLen) {\n chunks.push(remaining);\n break;\n }\n\n const lastParagraph = remaining.lastIndexOf('\\n\\n', maxLen - 2);\n const lastNewline = remaining.lastIndexOf('\\n', maxLen - 1);\n\n let splitAt: number;\n if (lastParagraph > 0) {\n splitAt = lastParagraph + 2;\n } else if (lastNewline > 0) {\n splitAt = lastNewline + 1;\n } else {\n splitAt = maxLen;\n }\n\n chunks.push(remaining.slice(0, splitAt));\n remaining = remaining.slice(splitAt);\n }\n\n return chunks;\n}\n"],"names":["Telegraf","markdownToTelegramHtml","TELEGRAM_CHANNEL_TYPE","TELEGRAM_MAX_MESSAGE_LENGTH","TELEGRAM_PARSE_MODE","TelegramAdapter","type","bot","messageHandler","running","options","botToken","start","on","ctx","msg","channelType","chatId","String","message","chat","id","userId","from","text","timestamp","Date","date","error","errorMessage","Error","reply","launch","stop","sendMessage","html","chunks","chunkMessage","chunk","telegram","parse_mode","isParseEntitiesError","htmlToPlainText","onMessage","handler","isHealthy","description","includes","replace","maxLen","remaining","length","push","lastParagraph","lastIndexOf","lastNewline","splitAt","slice"],"mappings":"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"}