@yungho/noclaw-channel 0.4.3 → 0.4.4

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.
@@ -1,53 +1,30 @@
1
1
  /**
2
- * Telegram Channel Implementation
2
+ * Telegram Channel Implementation (production-grade)
3
3
  *
4
- * Uses Telegram Bot API for message communication.
5
- * This is a placeholder implementation for future use.
4
+ * Uses grammY Bot framework for polling, sequentialization, rate limiting,
5
+ * and DM access control. Replaces the previous manual HTTP fetch() approach.
6
+ *
7
+ * Architecture: Implements IChannel → registered via ChannelRegistry.
8
+ * Zero changes to WebSocketServer, ConfigManager, or TenantConfigManager.
6
9
  */
7
- import { IChannel, IpcMessage, TelegramConfig, ChannelStatus, ChannelCapabilities, MediaObject } from '../types.js';
8
- import pino from 'pino';
10
+ import type { IChannel, IpcMessage, TelegramConfig, ChannelStatus, ChannelCapabilities, MediaObject } from "../types.js";
11
+ import type pino from "pino";
9
12
  export declare class TelegramChannel implements IChannel {
10
- private readonly baseUrl;
11
13
  private config;
12
14
  private logger;
13
15
  private status;
14
- private pollingInterval?;
15
- private messageCallback?;
16
+ private bot;
17
+ private messageCallback;
16
18
  private statusCallbacks;
17
- private lastUpdateId;
18
- private readonly POLLING_INTERVAL;
19
19
  constructor(config: TelegramConfig, logger: pino.Logger);
20
20
  connect(): Promise<void>;
21
21
  disconnect(): Promise<void>;
22
- send(text: string, contextToken: string, media?: MediaObject[], _userId?: string): Promise<void>;
22
+ send(text: string, contextToken: string, _media?: MediaObject[], _userId?: string): Promise<void>;
23
23
  onMessage(callback: (msg: IpcMessage) => void): void;
24
+ onStatusChange(callback: (status: ChannelStatus) => void): void;
24
25
  getStatus(): ChannelStatus;
25
26
  getCapabilities(): ChannelCapabilities;
26
27
  getPlatformId(): string;
27
- onStatusChange(callback: (status: ChannelStatus) => void): void;
28
- /**
29
- * Start polling for incoming messages
30
- */
31
- private startMessagePolling;
32
- /**
33
- * Stop all polling
34
- */
35
- private stopPolling;
36
- /**
37
- * Send text message
38
- */
39
- private sendMessage;
40
- /**
41
- * Send photo
42
- */
43
- private sendPhoto;
44
- /**
45
- * Make HTTP request to Telegram API
46
- */
47
- private makeRequest;
48
- /**
49
- * Update connection status
50
- */
51
28
  private setStatus;
52
29
  }
53
30
  //# sourceMappingURL=TelegramChannel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TelegramChannel.d.ts","sourceRoot":"","sources":["../../src/channels/TelegramChannel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACpH,OAAO,IAAI,MAAM,MAAM,CAAC;AAqCxB,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,eAAe,CAAC,CAA4B;IACpD,OAAO,CAAC,eAAe,CAAmD;IAC1E,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;gBAE7B,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;IAMjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBtG,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAIpD,SAAS,IAAI,aAAa;IAI1B,eAAe,IAAI,mBAAmB;IAUtC,aAAa,IAAI,MAAM;IAIvB,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAI/D;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA+C3B;;OAEG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;YACW,WAAW;IAyBzB;;OAEG;YACW,SAAS;IA2BvB;;OAEG;YACW,WAAW;IAYzB;;OAEG;IACH,OAAO,CAAC,SAAS;CAIlB"}
1
+ {"version":3,"file":"TelegramChannel.d.ts","sourceRoot":"","sources":["../../src/channels/TelegramChannel.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAI7B,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,eAAe,CAA4C;IACnE,OAAO,CAAC,eAAe,CAA8C;gBAEzD,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;IAOjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiGxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,WAAW,EAAE,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAahB,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAIpD,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAI/D,SAAS,IAAI,aAAa;IAI1B,eAAe,IAAI,mBAAmB;IAUtC,aAAa,IAAI,MAAM;IAMvB,OAAO,CAAC,SAAS;CAUlB"}
@@ -1,79 +1,124 @@
1
1
  /**
2
- * Telegram Channel Implementation
2
+ * Telegram Channel Implementation (production-grade)
3
3
  *
4
- * Uses Telegram Bot API for message communication.
5
- * This is a placeholder implementation for future use.
4
+ * Uses grammY Bot framework for polling, sequentialization, rate limiting,
5
+ * and DM access control. Replaces the previous manual HTTP fetch() approach.
6
+ *
7
+ * Architecture: Implements IChannel → registered via ChannelRegistry.
8
+ * Zero changes to WebSocketServer, ConfigManager, or TenantConfigManager.
6
9
  */
10
+ import { Bot } from "grammy";
11
+ import { sequentialize } from "@grammyjs/runner";
12
+ import { apiThrottler } from "@grammyjs/transformer-throttler";
13
+ // ── TelegramChannel ───────────────────────────────────────────────────────────
7
14
  export class TelegramChannel {
8
- baseUrl;
9
15
  config;
10
16
  logger;
11
- status = 'disconnected';
12
- pollingInterval;
13
- messageCallback;
17
+ status = "disconnected";
18
+ bot = null;
19
+ messageCallback = null;
14
20
  statusCallbacks = new Set();
15
- lastUpdateId = 0;
16
- POLLING_INTERVAL = 2000; // 2 seconds
17
21
  constructor(config, logger) {
18
22
  this.config = config;
19
- this.baseUrl = `https://api.telegram.org/bot${config.botToken}`;
20
- this.logger = logger.child({ channel: 'telegram' });
23
+ this.logger = logger.child({ channel: "telegram" });
21
24
  }
25
+ // ── IChannel Implementation ─────────────────────────────────────────────
22
26
  async connect() {
23
27
  if (!this.config.enabled) {
24
- this.logger.warn('Telegram channel is disabled in config');
28
+ this.logger.warn("Telegram channel is disabled in config");
25
29
  return;
26
30
  }
27
- this.setStatus('connecting');
31
+ if (!this.config.botToken || this.config.botToken.trim().length === 0) {
32
+ throw new Error("Telegram botToken is required. Get one from @BotFather on Telegram.");
33
+ }
34
+ this.setStatus("connecting");
28
35
  try {
29
- // Test token by making a request
30
- const response = await this.makeRequest('/getUpdates', {
31
- method: 'GET',
36
+ this.bot = new Bot(this.config.botToken);
37
+ // Middleware stack (order matters):
38
+ // 1. Sequentialize per-chat to prevent message reordering
39
+ this.bot.use(sequentialize((ctx) => {
40
+ return ctx.chat?.id?.toString();
41
+ }));
42
+ // 2. Rate limiting via API transformer (respects Telegram's ~30 msg/s limit)
43
+ this.bot.api.config.use(apiThrottler());
44
+ // 3. DM access control (Phase D)
45
+ this.bot.use(async (ctx, next) => {
46
+ const allowed = this.config.allowedUsers ?? [];
47
+ if (allowed.length === 0 || allowed.includes("*")) {
48
+ return next();
49
+ }
50
+ const userId = ctx.from?.id?.toString();
51
+ if (!userId || !allowed.includes(userId)) {
52
+ this.logger.warn({ userId: userId ?? "unknown" }, "Telegram: rejected non-whitelisted user");
53
+ await ctx.reply("Sorry, you are not authorized to use this bot.");
54
+ return;
55
+ }
56
+ return next();
32
57
  });
33
- if (!response.ok) {
34
- throw new Error('Invalid bot token or API error');
35
- }
36
- this.startMessagePolling();
37
- this.setStatus('connected');
38
- this.logger.info('Connected to Telegram Bot API');
58
+ // 4. Incoming text message handler
59
+ this.bot.on("message:text", (ctx) => {
60
+ const msg = ctx.message;
61
+ if (!msg?.text)
62
+ return;
63
+ const chatId = msg.chat.id;
64
+ const fromId = msg.from?.id;
65
+ const ipcMessage = {
66
+ type: "MSG_IN",
67
+ platform: "telegram",
68
+ content: msg.text,
69
+ contextToken: `tg_${chatId}`,
70
+ userId: fromId ? String(fromId) : undefined,
71
+ metadata: {
72
+ timestamp: Date.now(),
73
+ platform: "telegram",
74
+ userId: fromId ? String(fromId) : undefined,
75
+ messageId: String(msg.message_id ?? ctx.msg?.message_id ?? 0),
76
+ },
77
+ };
78
+ this.logger.info({ from: fromId, chatId, textPreview: msg.text.slice(0, 60) }, "Received Telegram message");
79
+ this.messageCallback?.(ipcMessage);
80
+ });
81
+ this.bot.catch((err) => {
82
+ this.logger.error({ err }, "Telegram bot error");
83
+ });
84
+ // Start grammY long-polling (returns immediately, runs in background)
85
+ this.bot.start({
86
+ onStart: () => {
87
+ this.logger.info("Telegram long-polling started");
88
+ },
89
+ });
90
+ this.setStatus("connected");
91
+ this.logger.info("Connected to Telegram Bot API");
39
92
  }
40
93
  catch (error) {
41
- this.logger.error({ error }, 'Failed to connect to Telegram');
42
- this.setStatus('error');
94
+ this.bot = null;
95
+ this.setStatus("error");
96
+ this.logger.error({ error }, "Failed to connect to Telegram");
43
97
  throw error;
44
98
  }
45
99
  }
46
100
  async disconnect() {
47
- this.stopPolling();
48
- this.setStatus('disconnected');
49
- this.logger.info('Disconnected from Telegram');
50
- }
51
- async send(text, contextToken, media, _userId) {
52
- try {
53
- const chatId = contextToken.replace('tg_', '');
54
- if (media && media.length > 0) {
55
- // Send media first
56
- for (const item of media) {
57
- if (item.type === 'image') {
58
- await this.sendPhoto(chatId, item.data, text);
59
- break; // Only send first image for now
60
- }
61
- }
62
- }
63
- else {
64
- // Send text message
65
- await this.sendMessage(chatId, text);
66
- }
67
- this.logger.debug({ chatId, textLength: text.length }, 'Message sent to Telegram');
101
+ if (this.bot) {
102
+ await this.bot.stop();
103
+ this.bot = null;
68
104
  }
69
- catch (error) {
70
- this.logger.error({ error, contextToken }, 'Failed to send message to Telegram');
71
- throw error;
105
+ this.setStatus("disconnected");
106
+ this.logger.info("Disconnected from Telegram");
107
+ }
108
+ async send(text, contextToken, _media, _userId) {
109
+ if (!this.bot) {
110
+ throw new Error("Not connected. Call connect() first.");
72
111
  }
112
+ const chatId = contextToken.replace("tg_", "");
113
+ await this.bot.api.sendMessage(chatId, text);
114
+ this.logger.debug({ chatId, textLength: text.length }, "Message sent to Telegram");
73
115
  }
74
116
  onMessage(callback) {
75
117
  this.messageCallback = callback;
76
118
  }
119
+ onStatusChange(callback) {
120
+ this.statusCallbacks.add(callback);
121
+ }
77
122
  getStatus() {
78
123
  return this.status;
79
124
  }
@@ -87,126 +132,19 @@ export class TelegramChannel {
87
132
  };
88
133
  }
89
134
  getPlatformId() {
90
- return 'telegram';
135
+ return "telegram";
91
136
  }
92
- onStatusChange(callback) {
93
- this.statusCallbacks.add(callback);
94
- }
95
- /**
96
- * Start polling for incoming messages
97
- */
98
- startMessagePolling() {
99
- this.stopPolling(); // Clear any existing polling
100
- this.pollingInterval = setInterval(async () => {
137
+ // ── Private ──────────────────────────────────────────────────────────────
138
+ setStatus(status) {
139
+ this.status = status;
140
+ for (const cb of this.statusCallbacks) {
101
141
  try {
102
- const response = await this.makeRequest(`/getUpdates?offset=${this.lastUpdateId}`, {
103
- method: 'GET',
104
- });
105
- if (response.result && response.result.length > 0) {
106
- this.logger.debug({ messageCount: response.result.length }, 'Received messages from Telegram');
107
- for (const update of response.result) {
108
- // Update last update ID (+1 to ACK this update)
109
- this.lastUpdateId = update.update_id + 1;
110
- // Emit message to callback
111
- if (update.message?.text) {
112
- this.messageCallback?.({
113
- type: 'MSG_IN',
114
- platform: 'telegram',
115
- content: update.message.text,
116
- contextToken: `tg_${update.message.chat.id}`,
117
- userId: String(update.message.from.id),
118
- metadata: {
119
- timestamp: Date.now(),
120
- platform: 'telegram',
121
- userId: String(update.message.from.id),
122
- messageId: String(update.message.message_id),
123
- },
124
- });
125
- }
126
- }
127
- }
142
+ cb(status);
128
143
  }
129
- catch (error) {
130
- this.logger.error({ error }, 'Error polling for messages');
131
- // Don't stop polling on error, just log it
144
+ catch {
145
+ // ignore callback errors
132
146
  }
133
- }, this.POLLING_INTERVAL);
134
- }
135
- /**
136
- * Stop all polling
137
- */
138
- stopPolling() {
139
- if (this.pollingInterval) {
140
- clearInterval(this.pollingInterval);
141
- this.pollingInterval = undefined;
142
- }
143
- }
144
- /**
145
- * Send text message
146
- */
147
- async sendMessage(chatId, text) {
148
- const url = `${this.baseUrl}/sendMessage`;
149
- const body = JSON.stringify({
150
- chat_id: parseInt(chatId),
151
- text,
152
- });
153
- const response = await fetch(url, {
154
- method: 'POST',
155
- headers: {
156
- 'Content-Type': 'application/json',
157
- },
158
- body,
159
- });
160
- if (!response.ok) {
161
- throw new Error(`Failed to send message: ${response.statusText}`);
162
- }
163
- const data = await response.json();
164
- if (!data.ok) {
165
- throw new Error('Failed to send message');
166
147
  }
167
148
  }
168
- /**
169
- * Send photo
170
- */
171
- async sendPhoto(chatId, photoData, caption) {
172
- const url = `${this.baseUrl}/sendPhoto`;
173
- const formData = new FormData();
174
- formData.append('chat_id', chatId);
175
- const buffer = Buffer.from(photoData, 'base64');
176
- const blob = new Blob([buffer], { type: 'image/jpeg' });
177
- formData.append('photo', blob, 'photo.jpg');
178
- if (caption) {
179
- formData.append('caption', caption);
180
- }
181
- const response = await fetch(url, {
182
- method: 'POST',
183
- body: formData,
184
- });
185
- if (!response.ok) {
186
- throw new Error(`Failed to send photo: ${response.statusText}`);
187
- }
188
- const data = await response.json();
189
- if (!data.ok) {
190
- throw new Error('Failed to send photo');
191
- }
192
- }
193
- /**
194
- * Make HTTP request to Telegram API
195
- */
196
- async makeRequest(endpoint, options) {
197
- const url = `${this.baseUrl}${endpoint}`;
198
- const response = await fetch(url, options);
199
- if (!response.ok) {
200
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
201
- }
202
- return await response.json();
203
- }
204
- /**
205
- * Update connection status
206
- */
207
- setStatus(status) {
208
- this.status = status;
209
- this.statusCallbacks.forEach((callback) => callback(status));
210
- }
211
149
  }
212
150
  //# sourceMappingURL=TelegramChannel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TelegramChannel.js","sourceRoot":"","sources":["../../src/channels/TelegramChannel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TelegramChannel.js","sourceRoot":"","sources":["../../src/channels/TelegramChannel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TelegramChannel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelegramChannel.test.d.ts","sourceRoot":"","sources":["../../../src/channels/__tests__/TelegramChannel.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * TelegramChannel tests — NHP-2 TDD (RED phase)
3
+ *
4
+ * Verifies the IChannel interface implementation using grammY.
5
+ * All grammY internals are mocked — we test OUR code, not the library.
6
+ */
7
+ import { describe, it, expect, vi, beforeEach } from "vitest";
8
+ // ── Mocks ────────────────────────────────────────────────────────────────────
9
+ const mockBotStart = vi.fn();
10
+ const mockBotStop = vi.fn();
11
+ const mockSendMessage = vi.fn();
12
+ const mockBotOn = vi.fn();
13
+ const mockBotUse = vi.fn();
14
+ const mockBotCatch = vi.fn();
15
+ const mockBotInstance = {
16
+ start: mockBotStart,
17
+ stop: mockBotStop,
18
+ api: { sendMessage: mockSendMessage, config: { use: vi.fn() } },
19
+ on: mockBotOn,
20
+ use: mockBotUse,
21
+ catch: mockBotCatch,
22
+ };
23
+ vi.mock("grammy", () => ({
24
+ Bot: vi.fn(function () {
25
+ return mockBotInstance;
26
+ }),
27
+ }));
28
+ vi.mock("@grammyjs/runner", () => ({
29
+ sequentialize: vi.fn(() => "mock-sequentialize-middleware"),
30
+ }));
31
+ vi.mock("@grammyjs/transformer-throttler", () => ({
32
+ apiThrottler: vi.fn(() => "mock-throttler-middleware"),
33
+ }));
34
+ import { Bot } from "grammy";
35
+ import { sequentialize } from "@grammyjs/runner";
36
+ import { apiThrottler } from "@grammyjs/transformer-throttler";
37
+ import { TelegramChannel } from "../TelegramChannel.js";
38
+ // ── Helpers ───────────────────────────────────────────────────────────────────
39
+ function createChannel(overrides = {}) {
40
+ const config = {
41
+ enabled: true,
42
+ botToken: "test-bot-token",
43
+ allowedUsers: ["*"],
44
+ permissions: { chat: "all", executeTools: true, permissionRelay: true, adminUsers: [] },
45
+ ...overrides,
46
+ };
47
+ const logger = {
48
+ info: vi.fn(),
49
+ warn: vi.fn(),
50
+ error: vi.fn(),
51
+ debug: vi.fn(),
52
+ child: vi.fn(() => logger),
53
+ };
54
+ return new TelegramChannel(config, logger);
55
+ }
56
+ // ── Tests ────────────────────────────────────────────────────────────────────
57
+ describe("TelegramChannel (grammY)", () => {
58
+ beforeEach(() => {
59
+ vi.clearAllMocks();
60
+ });
61
+ describe("connect()", () => {
62
+ it("creates a grammY Bot and starts long-polling", async () => {
63
+ const channel = createChannel();
64
+ await channel.connect();
65
+ expect(Bot).toHaveBeenCalledWith("test-bot-token");
66
+ // sequentialize should be registered
67
+ expect(vi.mocked(sequentialize)).toHaveBeenCalled();
68
+ // throttler should be registered
69
+ expect(vi.mocked(apiThrottler)).toHaveBeenCalled();
70
+ // message handler should be registered
71
+ expect(mockBotOn).toHaveBeenCalledWith("message:text", expect.any(Function));
72
+ // bot should start polling
73
+ expect(mockBotStart).toHaveBeenCalled();
74
+ expect(channel.getStatus()).toBe("connected");
75
+ });
76
+ it("throws if botToken is empty", async () => {
77
+ const channel = createChannel({ botToken: "" });
78
+ await expect(channel.connect()).rejects.toThrow("botToken is required");
79
+ });
80
+ it("does nothing if disabled", async () => {
81
+ const channel = createChannel({ enabled: false });
82
+ await channel.connect();
83
+ expect(Bot).not.toHaveBeenCalled();
84
+ expect(channel.getStatus()).toBe("disconnected");
85
+ });
86
+ });
87
+ describe("disconnect()", () => {
88
+ it("stops the bot and sets status to disconnected", async () => {
89
+ const channel = createChannel();
90
+ await channel.connect();
91
+ await channel.disconnect();
92
+ expect(mockBotStop).toHaveBeenCalled();
93
+ expect(channel.getStatus()).toBe("disconnected");
94
+ });
95
+ });
96
+ describe("send()", () => {
97
+ it("calls bot.api.sendMessage with chatId from contextToken", async () => {
98
+ const channel = createChannel();
99
+ await channel.connect();
100
+ await channel.send("Hello Telegram!", "tg_123456");
101
+ expect(mockSendMessage).toHaveBeenCalledWith("123456", "Hello Telegram!");
102
+ });
103
+ it("throws if not connected", async () => {
104
+ const channel = createChannel();
105
+ await expect(channel.send("hi", "tg_123")).rejects.toThrow("Not connected");
106
+ });
107
+ it("passes parse_mode and link_preview_options", async () => {
108
+ const channel = createChannel();
109
+ await channel.connect();
110
+ await channel.send("<b>bold</b>", "tg_789");
111
+ expect(mockSendMessage).toHaveBeenCalledWith("789", "<b>bold</b>");
112
+ });
113
+ });
114
+ describe("incoming message → MSG_IN", () => {
115
+ it("fires messageCallback with correct MSG_IN on text message", async () => {
116
+ const channel = createChannel();
117
+ const onMessage = vi.fn();
118
+ channel.onMessage(onMessage);
119
+ await channel.connect();
120
+ // Extract the handler registered with bot.on("message:text", handler)
121
+ const handler = mockBotOn.mock.calls.find((call) => call[0] === "message:text")?.[1];
122
+ expect(handler).toBeDefined();
123
+ // Simulate a grammY context
124
+ await handler({
125
+ message: {
126
+ message_id: 42,
127
+ text: "Hello from Telegram!",
128
+ chat: { id: 123456, type: "private" },
129
+ from: { id: 789, first_name: "Alice" },
130
+ },
131
+ });
132
+ expect(onMessage).toHaveBeenCalledTimes(1);
133
+ const msg = onMessage.mock.calls[0][0];
134
+ expect(msg.type).toBe("MSG_IN");
135
+ expect(msg.platform).toBe("telegram");
136
+ expect(msg.content).toBe("Hello from Telegram!");
137
+ expect(msg.contextToken).toBe("tg_123456");
138
+ expect(msg.userId).toBe("789");
139
+ expect(msg.metadata?.messageId).toBe("42");
140
+ });
141
+ });
142
+ describe("DM access control", () => {
143
+ it("allows whitelisted user", async () => {
144
+ const channel = createChannel({ allowedUsers: ["123", "456"] });
145
+ await channel.connect();
146
+ const handler = mockBotOn.mock.calls.find((call) => call[0] === "message:text")?.[1];
147
+ const onMessage = vi.fn();
148
+ channel.onMessage(onMessage);
149
+ const mockNext = vi.fn();
150
+ const mockReply = vi.fn();
151
+ // Access control is the 2nd bot.use() call (index 1, after sequentialize)
152
+ const accessMiddleware = mockBotUse.mock.calls[1]?.[0];
153
+ expect(accessMiddleware).toBeDefined();
154
+ await accessMiddleware({ from: { id: 123 }, reply: mockReply }, mockNext);
155
+ expect(mockNext).toHaveBeenCalled();
156
+ expect(mockReply).not.toHaveBeenCalled();
157
+ });
158
+ it("rejects non-whitelisted user", async () => {
159
+ const channel = createChannel({ allowedUsers: ["123"] });
160
+ await channel.connect();
161
+ const accessMiddleware = mockBotUse.mock.calls[1]?.[0];
162
+ const mockNext = vi.fn();
163
+ const mockReply = vi.fn();
164
+ await accessMiddleware({ from: { id: 999 }, reply: mockReply }, mockNext);
165
+ expect(mockNext).not.toHaveBeenCalled();
166
+ expect(mockReply).toHaveBeenCalledWith("Sorry, you are not authorized to use this bot.");
167
+ });
168
+ it('allows any user when allowedUsers is ["*"]', async () => {
169
+ const channel = createChannel({ allowedUsers: ["*"] });
170
+ await channel.connect();
171
+ const accessMiddleware = mockBotUse.mock.calls[1]?.[0];
172
+ const mockNext = vi.fn();
173
+ const mockReply = vi.fn();
174
+ await accessMiddleware({ from: { id: 999 }, reply: mockReply }, mockNext);
175
+ expect(mockNext).toHaveBeenCalled();
176
+ expect(mockReply).not.toHaveBeenCalled();
177
+ });
178
+ });
179
+ describe("getPlatformId / getCapabilities", () => {
180
+ it("returns 'telegram'", () => {
181
+ expect(createChannel().getPlatformId()).toBe("telegram");
182
+ });
183
+ it("reports text + media capabilities", () => {
184
+ const caps = createChannel().getCapabilities();
185
+ expect(caps.textMessaging).toBe(true);
186
+ expect(caps.mediaSharing).toBe(true);
187
+ expect(caps.webhooks).toBe(false);
188
+ });
189
+ });
190
+ });
191
+ //# sourceMappingURL=TelegramChannel.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelegramChannel.test.js","sourceRoot":"","sources":["../../../src/channels/__tests__/TelegramChannel.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yungho/noclaw-channel",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "NoClaw Channel Server - WebSocket IPC bridge for external chat platforms",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -24,7 +24,10 @@
24
24
  "author": "Yungho",
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
+ "@grammyjs/runner": "^2.0.3",
28
+ "@grammyjs/transformer-throttler": "^1.2.1",
27
29
  "commander": "^12.1.0",
30
+ "grammy": "^1.34.0",
28
31
  "pino": "^9.0.0",
29
32
  "pino-pretty": "^11.0.0",
30
33
  "qrcode-terminal": "^0.12.0",