@ai-devkit/channel-connector 0.10.1 → 0.13.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.
- package/README.md +4 -1
- package/dist/ConfigStore.d.ts.map +1 -1
- package/dist/ConfigStore.js +1 -0
- package/dist/ConfigStore.js.map +1 -1
- package/dist/__tests__/ChannelManager.test.js +3 -1
- package/dist/__tests__/ChannelManager.test.js.map +1 -1
- package/dist/__tests__/ConfigStore.test.js +33 -4
- package/dist/__tests__/ConfigStore.test.js.map +1 -1
- package/dist/__tests__/adapters/SlackAdapter.test.js +429 -0
- package/dist/__tests__/adapters/SlackAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/TelegramAdapter.test.js +131 -0
- package/dist/__tests__/adapters/TelegramAdapter.test.js.map +1 -1
- package/dist/__tests__/utils/SlackDeliveryQueue.test.js +127 -0
- package/dist/__tests__/utils/SlackDeliveryQueue.test.js.map +1 -0
- package/dist/__tests__/utils/slackMarkdown.test.js +44 -0
- package/dist/__tests__/utils/slackMarkdown.test.js.map +1 -0
- package/dist/adapters/ChannelAdapter.d.ts +8 -2
- package/dist/adapters/ChannelAdapter.d.ts.map +1 -1
- package/dist/adapters/ChannelAdapter.js +3 -6
- package/dist/adapters/ChannelAdapter.js.map +1 -1
- package/dist/adapters/SlackAdapter.d.ts +71 -0
- package/dist/adapters/SlackAdapter.d.ts.map +1 -0
- package/dist/adapters/SlackAdapter.js +205 -0
- package/dist/adapters/SlackAdapter.js.map +1 -0
- package/dist/adapters/TelegramAdapter.d.ts +19 -1
- package/dist/adapters/TelegramAdapter.d.ts.map +1 -1
- package/dist/adapters/TelegramAdapter.js +69 -0
- package/dist/adapters/TelegramAdapter.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +86 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -3
- package/dist/types.js.map +1 -1
- package/dist/utils/SlackDeliveryQueue.d.ts +33 -0
- package/dist/utils/SlackDeliveryQueue.d.ts.map +1 -0
- package/dist/utils/SlackDeliveryQueue.js +83 -0
- package/dist/utils/SlackDeliveryQueue.js.map +1 -0
- package/dist/utils/slackMarkdown.d.ts +5 -0
- package/dist/utils/slackMarkdown.d.ts.map +1 -0
- package/dist/utils/slackMarkdown.js +150 -0
- package/dist/utils/slackMarkdown.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { InteractiveChannelAdapter } from './ChannelAdapter.js';
|
|
2
|
+
import type { ChannelQuestion, IncomingInteraction, IncomingMessage, SentMessage, SlackConfig } from '../types.js';
|
|
3
|
+
export declare const SLACK_CHANNEL_TYPE = "slack";
|
|
4
|
+
interface SlackAuthClient {
|
|
5
|
+
authTest(): Promise<{
|
|
6
|
+
ok?: boolean;
|
|
7
|
+
app_id?: string;
|
|
8
|
+
user_id?: string;
|
|
9
|
+
team_id?: string;
|
|
10
|
+
team?: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export interface SlackIdentity {
|
|
14
|
+
appId?: string;
|
|
15
|
+
botUserId: string;
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
workspaceName?: string;
|
|
18
|
+
}
|
|
19
|
+
interface SlackConnectionsClient {
|
|
20
|
+
open(): Promise<{
|
|
21
|
+
ok?: boolean;
|
|
22
|
+
url?: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export declare function validateSlackAppToken(appToken: string, client?: SlackConnectionsClient): Promise<void>;
|
|
26
|
+
export declare function validateSlackCredentials(botToken: string, client?: SlackAuthClient): Promise<SlackIdentity>;
|
|
27
|
+
interface SocketClientLike {
|
|
28
|
+
on(event: string, listener: (payload?: unknown) => void): unknown;
|
|
29
|
+
start(): Promise<unknown>;
|
|
30
|
+
disconnect(): Promise<unknown>;
|
|
31
|
+
}
|
|
32
|
+
interface WebClientLike {
|
|
33
|
+
chat: {
|
|
34
|
+
postMessage(input: Record<string, unknown>): Promise<{
|
|
35
|
+
ok?: boolean;
|
|
36
|
+
ts?: string;
|
|
37
|
+
}>;
|
|
38
|
+
update?(input: Record<string, unknown>): Promise<unknown>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface SlackAdapterDependencies {
|
|
42
|
+
socketClient?: SocketClientLike;
|
|
43
|
+
webClient?: WebClientLike;
|
|
44
|
+
}
|
|
45
|
+
export declare class SlackAdapter implements InteractiveChannelAdapter {
|
|
46
|
+
private readonly config;
|
|
47
|
+
readonly type = "slack";
|
|
48
|
+
private readonly socket;
|
|
49
|
+
private readonly web;
|
|
50
|
+
private readonly delivery;
|
|
51
|
+
private readonly recentEventIds;
|
|
52
|
+
private messageHandler;
|
|
53
|
+
private interactionHandler;
|
|
54
|
+
private running;
|
|
55
|
+
private listenersRegistered;
|
|
56
|
+
constructor(config: SlackConfig, dependencies?: SlackAdapterDependencies);
|
|
57
|
+
start(): Promise<void>;
|
|
58
|
+
stop(): Promise<void>;
|
|
59
|
+
onMessage(handler: (message: IncomingMessage) => Promise<void>): void;
|
|
60
|
+
onInteraction(handler: (interaction: IncomingInteraction) => Promise<void>): void;
|
|
61
|
+
sendMessage(chatId: string, text: string): Promise<SentMessage>;
|
|
62
|
+
isHealthy(): Promise<boolean>;
|
|
63
|
+
sendQuestion(chatId: string, question: ChannelQuestion): Promise<SentMessage>;
|
|
64
|
+
finalizeInteraction(chatId: string, messageId: string): Promise<void>;
|
|
65
|
+
private handleSlackEvent;
|
|
66
|
+
private isValidDirectMessage;
|
|
67
|
+
private rememberEvent;
|
|
68
|
+
private handleInteraction;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=SlackAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlackAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/SlackAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { SocketModeClient } from '@slack/socket-mode';
|
|
2
|
+
import { WebClient } from '@slack/web-api';
|
|
3
|
+
import { SlackDeliveryQueue } from '../utils/SlackDeliveryQueue.js';
|
|
4
|
+
import { escapeSlackText } from '../utils/slackMarkdown.js';
|
|
5
|
+
export const SLACK_CHANNEL_TYPE = 'slack';
|
|
6
|
+
export async function validateSlackAppToken(appToken, client) {
|
|
7
|
+
const connections = client ?? new WebClient(appToken).apps.connections;
|
|
8
|
+
const result = await connections.open();
|
|
9
|
+
if (!result.ok || !result.url) throw new Error('Slack app token cannot open Socket Mode');
|
|
10
|
+
}
|
|
11
|
+
export async function validateSlackCredentials(botToken, client) {
|
|
12
|
+
const authClient = client ?? {
|
|
13
|
+
authTest: ()=>new WebClient(botToken).auth.test()
|
|
14
|
+
};
|
|
15
|
+
const identity = await authClient.authTest();
|
|
16
|
+
if (!identity.ok || !identity.user_id || !identity.team_id) {
|
|
17
|
+
throw new Error('Slack bot token returned incomplete identity');
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
...identity.app_id ? {
|
|
21
|
+
appId: identity.app_id
|
|
22
|
+
} : {},
|
|
23
|
+
botUserId: identity.user_id,
|
|
24
|
+
workspaceId: identity.team_id,
|
|
25
|
+
workspaceName: identity.team
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export class SlackAdapter {
|
|
29
|
+
config;
|
|
30
|
+
type = SLACK_CHANNEL_TYPE;
|
|
31
|
+
socket;
|
|
32
|
+
web;
|
|
33
|
+
delivery;
|
|
34
|
+
recentEventIds = new Map();
|
|
35
|
+
messageHandler = null;
|
|
36
|
+
interactionHandler = null;
|
|
37
|
+
running = false;
|
|
38
|
+
listenersRegistered = false;
|
|
39
|
+
constructor(config, dependencies = {}){
|
|
40
|
+
this.config = config;
|
|
41
|
+
this.socket = dependencies.socketClient ?? new SocketModeClient({
|
|
42
|
+
appToken: config.appToken
|
|
43
|
+
});
|
|
44
|
+
this.web = dependencies.webClient ?? new WebClient(config.botToken);
|
|
45
|
+
this.delivery = new SlackDeliveryQueue({
|
|
46
|
+
postMessage: (input)=>this.web.chat.postMessage(input)
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async start() {
|
|
50
|
+
if (!this.listenersRegistered) {
|
|
51
|
+
this.socket.on('slack_event', (payload)=>{
|
|
52
|
+
void this.handleSlackEvent(payload).catch(()=>undefined);
|
|
53
|
+
});
|
|
54
|
+
this.socket.on('interactive', (payload)=>{
|
|
55
|
+
void this.handleInteraction(payload).catch(()=>undefined);
|
|
56
|
+
});
|
|
57
|
+
this.socket.on('connected', ()=>{
|
|
58
|
+
this.running = true;
|
|
59
|
+
});
|
|
60
|
+
this.socket.on('disconnected', ()=>{
|
|
61
|
+
this.running = false;
|
|
62
|
+
});
|
|
63
|
+
this.listenersRegistered = true;
|
|
64
|
+
}
|
|
65
|
+
await this.socket.start();
|
|
66
|
+
this.running = true;
|
|
67
|
+
}
|
|
68
|
+
async stop() {
|
|
69
|
+
this.running = false;
|
|
70
|
+
await this.socket.disconnect();
|
|
71
|
+
}
|
|
72
|
+
onMessage(handler) {
|
|
73
|
+
this.messageHandler = handler;
|
|
74
|
+
}
|
|
75
|
+
onInteraction(handler) {
|
|
76
|
+
this.interactionHandler = handler;
|
|
77
|
+
}
|
|
78
|
+
async sendMessage(chatId, text) {
|
|
79
|
+
return this.delivery.send(chatId, text);
|
|
80
|
+
}
|
|
81
|
+
async isHealthy() {
|
|
82
|
+
return this.running;
|
|
83
|
+
}
|
|
84
|
+
async sendQuestion(chatId, question) {
|
|
85
|
+
const fallback = escapeSlackText(`${question.header ? `${question.header}: ` : ''}${question.question}`);
|
|
86
|
+
const elements = question.options.map((option)=>({
|
|
87
|
+
type: 'button',
|
|
88
|
+
action_id: 'ai_devkit_question',
|
|
89
|
+
text: {
|
|
90
|
+
type: 'plain_text',
|
|
91
|
+
text: option.label.slice(0, 75)
|
|
92
|
+
},
|
|
93
|
+
value: `${question.id}:${option.value}`.slice(0, 2000)
|
|
94
|
+
}));
|
|
95
|
+
if (question.allowSkip) {
|
|
96
|
+
elements.push({
|
|
97
|
+
type: 'button',
|
|
98
|
+
action_id: 'ai_devkit_question',
|
|
99
|
+
text: {
|
|
100
|
+
type: 'plain_text',
|
|
101
|
+
text: 'Skip'
|
|
102
|
+
},
|
|
103
|
+
value: `${question.id}:skip`
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const response = await this.web.chat.postMessage({
|
|
107
|
+
channel: chatId,
|
|
108
|
+
text: fallback,
|
|
109
|
+
blocks: [
|
|
110
|
+
{
|
|
111
|
+
type: 'section',
|
|
112
|
+
text: {
|
|
113
|
+
type: 'mrkdwn',
|
|
114
|
+
text: `*${escapeSlackText(question.header ?? 'Question')}*\n${escapeSlackText(question.question)}`
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'actions',
|
|
119
|
+
elements
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
});
|
|
123
|
+
if (!response.ts) throw new Error('Slack did not return a question message timestamp');
|
|
124
|
+
return {
|
|
125
|
+
messageId: response.ts
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async finalizeInteraction(chatId, messageId) {
|
|
129
|
+
if (!this.web.chat.update) throw new Error('Slack Web API chat.update is unavailable');
|
|
130
|
+
await this.web.chat.update({
|
|
131
|
+
channel: chatId,
|
|
132
|
+
ts: messageId,
|
|
133
|
+
blocks: []
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async handleSlackEvent(envelope) {
|
|
137
|
+
if (envelope.type !== 'events_api') return;
|
|
138
|
+
await envelope.ack?.();
|
|
139
|
+
const body = envelope.body;
|
|
140
|
+
const event = body?.event;
|
|
141
|
+
const eventId = body?.event_id;
|
|
142
|
+
if (!body || !event || !eventId || !this.messageHandler) return;
|
|
143
|
+
if (!this.isValidDirectMessage(body, event)) return;
|
|
144
|
+
if (this.recentEventIds.has(eventId)) return;
|
|
145
|
+
this.rememberEvent(eventId);
|
|
146
|
+
try {
|
|
147
|
+
await this.messageHandler({
|
|
148
|
+
channelType: SLACK_CHANNEL_TYPE,
|
|
149
|
+
chatId: String(event.channel),
|
|
150
|
+
userId: String(event.user),
|
|
151
|
+
text: String(event.text),
|
|
152
|
+
timestamp: new Date(Number(event.event_ts ?? event.ts) * 1000),
|
|
153
|
+
messageId: eventId,
|
|
154
|
+
threadId: typeof event.thread_ts === 'string' ? event.thread_ts : undefined,
|
|
155
|
+
workspaceId: body.team_id,
|
|
156
|
+
metadata: {
|
|
157
|
+
slackTs: event.ts
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
// The channel consumer reports terminal errors; never reject a Socket Mode listener.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
isValidDirectMessage(body, event) {
|
|
165
|
+
return body.team_id === this.config.workspaceId && body.is_ext_shared_channel !== true && event.type === 'message' && event.channel_type === 'im' && event.user !== this.config.botUserId && typeof event.text === 'string' && typeof event.ts === 'string' && event.subtype === undefined && event.bot_id === undefined;
|
|
166
|
+
}
|
|
167
|
+
rememberEvent(eventId) {
|
|
168
|
+
this.recentEventIds.set(eventId, Date.now());
|
|
169
|
+
while(this.recentEventIds.size > 1000){
|
|
170
|
+
const oldest = this.recentEventIds.keys().next().value;
|
|
171
|
+
if (!oldest) break;
|
|
172
|
+
this.recentEventIds.delete(oldest);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async handleInteraction(envelope) {
|
|
176
|
+
await envelope.ack?.();
|
|
177
|
+
const body = envelope.body;
|
|
178
|
+
const action = body?.actions?.[0];
|
|
179
|
+
const interactionId = action?.action_ts;
|
|
180
|
+
const workspaceId = body?.team?.id;
|
|
181
|
+
const userId = body?.user?.id;
|
|
182
|
+
const chatId = body?.channel?.id;
|
|
183
|
+
const messageId = body?.container?.message_ts;
|
|
184
|
+
if (!body || !action || !interactionId || !this.interactionHandler) return;
|
|
185
|
+
if (body.type !== 'block_actions' || typeof workspaceId !== 'string' || typeof userId !== 'string' || typeof chatId !== 'string' || workspaceId !== this.config.workspaceId || typeof messageId !== 'string' || action.action_id !== 'ai_devkit_question' || typeof action.value !== 'string' || this.recentEventIds.has(`interaction:${interactionId}`)) return;
|
|
186
|
+
this.rememberEvent(`interaction:${interactionId}`);
|
|
187
|
+
try {
|
|
188
|
+
await this.interactionHandler({
|
|
189
|
+
channelType: SLACK_CHANNEL_TYPE,
|
|
190
|
+
chatId,
|
|
191
|
+
userId,
|
|
192
|
+
workspaceId,
|
|
193
|
+
interactionId,
|
|
194
|
+
messageId,
|
|
195
|
+
actionId: action.action_id,
|
|
196
|
+
value: action.value,
|
|
197
|
+
timestamp: new Date(Number(interactionId) * 1000)
|
|
198
|
+
});
|
|
199
|
+
} catch {
|
|
200
|
+
// Keep Socket Mode listener failures isolated from the SDK event loop.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=SlackAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/SlackAdapter.ts"],"sourcesContent":["import { SocketModeClient } from '@slack/socket-mode';\nimport { WebClient } from '@slack/web-api';\nimport type { InteractiveChannelAdapter } from './ChannelAdapter.js';\nimport type {\n ChannelQuestion,\n IncomingInteraction,\n IncomingMessage,\n SentMessage,\n SlackConfig,\n} from '../types.js';\nimport { SlackDeliveryQueue } from '../utils/SlackDeliveryQueue.js';\nimport { escapeSlackText } from '../utils/slackMarkdown.js';\n\nexport const SLACK_CHANNEL_TYPE = 'slack';\n\ninterface SlackAuthClient {\n authTest(): Promise<{ ok?: boolean; app_id?: string; user_id?: string; team_id?: string; team?: string }>;\n}\n\nexport interface SlackIdentity {\n appId?: string;\n botUserId: string;\n workspaceId: string;\n workspaceName?: string;\n}\n\ninterface SlackConnectionsClient {\n open(): Promise<{ ok?: boolean; url?: string }>;\n}\n\nexport async function validateSlackAppToken(appToken: string, client?: SlackConnectionsClient): Promise<void> {\n const connections = client ?? new WebClient(appToken).apps.connections;\n const result = await connections.open();\n if (!result.ok || !result.url) throw new Error('Slack app token cannot open Socket Mode');\n}\n\nexport async function validateSlackCredentials(botToken: string, client?: SlackAuthClient): Promise<SlackIdentity> {\n const authClient = client ?? {\n authTest: () => new WebClient(botToken).auth.test(),\n };\n const identity = await authClient.authTest();\n if (!identity.ok || !identity.user_id || !identity.team_id) {\n throw new Error('Slack bot token returned incomplete identity');\n }\n return {\n ...(identity.app_id ? { appId: identity.app_id } : {}),\n botUserId: identity.user_id,\n workspaceId: identity.team_id,\n workspaceName: identity.team,\n };\n}\n\ninterface SocketClientLike {\n on(event: string, listener: (payload?: unknown) => void): unknown;\n start(): Promise<unknown>;\n disconnect(): Promise<unknown>;\n}\n\ninterface WebClientLike {\n chat: {\n postMessage(input: Record<string, unknown>): Promise<{ ok?: boolean; ts?: string }>;\n update?(input: Record<string, unknown>): Promise<unknown>;\n };\n}\n\ninterface SlackAdapterDependencies {\n socketClient?: SocketClientLike;\n webClient?: WebClientLike;\n}\n\ninterface SlackEventEnvelope {\n type?: string;\n ack?: () => Promise<void>;\n body?: {\n team_id?: string;\n event_id?: string;\n is_ext_shared_channel?: boolean;\n event?: Record<string, unknown>;\n };\n}\n\nexport class SlackAdapter implements InteractiveChannelAdapter {\n readonly type = SLACK_CHANNEL_TYPE;\n private readonly socket: SocketClientLike;\n private readonly web: WebClientLike;\n private readonly delivery: SlackDeliveryQueue;\n private readonly recentEventIds = new Map<string, number>();\n private messageHandler: ((message: IncomingMessage) => Promise<void>) | null = null;\n private interactionHandler: ((interaction: IncomingInteraction) => Promise<void>) | null = null;\n private running = false;\n private listenersRegistered = false;\n\n constructor(private readonly config: SlackConfig, dependencies: SlackAdapterDependencies = {}) {\n this.socket = dependencies.socketClient ?? new SocketModeClient({ appToken: config.appToken });\n this.web = dependencies.webClient ?? new WebClient(config.botToken) as unknown as WebClientLike;\n this.delivery = new SlackDeliveryQueue({\n postMessage: (input) => this.web.chat.postMessage(input),\n });\n }\n\n async start(): Promise<void> {\n if (!this.listenersRegistered) {\n this.socket.on('slack_event', (payload) => {\n void this.handleSlackEvent(payload as SlackEventEnvelope).catch(() => undefined);\n });\n this.socket.on('interactive', (payload) => {\n void this.handleInteraction(payload as SlackInteractionEnvelope).catch(() => undefined);\n });\n this.socket.on('connected', () => { this.running = true; });\n this.socket.on('disconnected', () => { this.running = false; });\n this.listenersRegistered = true;\n }\n await this.socket.start();\n this.running = true;\n }\n\n async stop(): Promise<void> {\n this.running = false;\n await this.socket.disconnect();\n }\n\n onMessage(handler: (message: IncomingMessage) => Promise<void>): void {\n this.messageHandler = handler;\n }\n\n onInteraction(handler: (interaction: IncomingInteraction) => Promise<void>): void {\n this.interactionHandler = handler;\n }\n\n async sendMessage(chatId: string, text: string): Promise<SentMessage> {\n return this.delivery.send(chatId, text);\n }\n\n async isHealthy(): Promise<boolean> {\n return this.running;\n }\n\n async sendQuestion(chatId: string, question: ChannelQuestion): Promise<SentMessage> {\n const fallback = escapeSlackText(`${question.header ? `${question.header}: ` : ''}${question.question}`);\n const elements = question.options.map((option) => ({\n type: 'button',\n action_id: 'ai_devkit_question',\n text: { type: 'plain_text', text: option.label.slice(0, 75) },\n value: `${question.id}:${option.value}`.slice(0, 2000),\n }));\n if (question.allowSkip) {\n elements.push({\n type: 'button', action_id: 'ai_devkit_question',\n text: { type: 'plain_text', text: 'Skip' }, value: `${question.id}:skip`,\n });\n }\n const response = await this.web.chat.postMessage({\n channel: chatId,\n text: fallback,\n blocks: [\n {\n type: 'section',\n text: { type: 'mrkdwn', text: `*${escapeSlackText(question.header ?? 'Question')}*\\n${escapeSlackText(question.question)}` },\n },\n { type: 'actions', elements },\n ],\n });\n if (!response.ts) throw new Error('Slack did not return a question message timestamp');\n return { messageId: response.ts };\n }\n\n async finalizeInteraction(chatId: string, messageId: string): Promise<void> {\n if (!this.web.chat.update) throw new Error('Slack Web API chat.update is unavailable');\n await this.web.chat.update({ channel: chatId, ts: messageId, blocks: [] });\n }\n\n private async handleSlackEvent(envelope: SlackEventEnvelope): Promise<void> {\n if (envelope.type !== 'events_api') return;\n await envelope.ack?.();\n const body = envelope.body;\n const event = body?.event;\n const eventId = body?.event_id;\n if (!body || !event || !eventId || !this.messageHandler) return;\n if (!this.isValidDirectMessage(body, event)) return;\n if (this.recentEventIds.has(eventId)) return;\n this.rememberEvent(eventId);\n\n try {\n await this.messageHandler({\n channelType: SLACK_CHANNEL_TYPE,\n chatId: String(event.channel),\n userId: String(event.user),\n text: String(event.text),\n timestamp: new Date(Number(event.event_ts ?? event.ts) * 1000),\n messageId: eventId,\n threadId: typeof event.thread_ts === 'string' ? event.thread_ts : undefined,\n workspaceId: body.team_id,\n metadata: { slackTs: event.ts },\n });\n } catch {\n // The channel consumer reports terminal errors; never reject a Socket Mode listener.\n }\n }\n\n private isValidDirectMessage(body: NonNullable<SlackEventEnvelope['body']>, event: Record<string, unknown>): boolean {\n return body.team_id === this.config.workspaceId\n && body.is_ext_shared_channel !== true\n && event.type === 'message'\n && event.channel_type === 'im'\n && event.user !== this.config.botUserId\n && typeof event.text === 'string'\n && typeof event.ts === 'string'\n && event.subtype === undefined\n && event.bot_id === undefined;\n }\n\n private rememberEvent(eventId: string): void {\n this.recentEventIds.set(eventId, Date.now());\n while (this.recentEventIds.size > 1000) {\n const oldest = this.recentEventIds.keys().next().value as string | undefined;\n if (!oldest) break;\n this.recentEventIds.delete(oldest);\n }\n }\n\n private async handleInteraction(envelope: SlackInteractionEnvelope): Promise<void> {\n await envelope.ack?.();\n const body = envelope.body;\n const action = body?.actions?.[0];\n const interactionId = action?.action_ts;\n const workspaceId = body?.team?.id;\n const userId = body?.user?.id;\n const chatId = body?.channel?.id;\n const messageId = body?.container?.message_ts;\n if (!body || !action || !interactionId || !this.interactionHandler) return;\n if (body.type !== 'block_actions'\n || typeof workspaceId !== 'string'\n || typeof userId !== 'string'\n || typeof chatId !== 'string'\n || workspaceId !== this.config.workspaceId\n || typeof messageId !== 'string'\n || action.action_id !== 'ai_devkit_question'\n || typeof action.value !== 'string'\n || this.recentEventIds.has(`interaction:${interactionId}`)) return;\n this.rememberEvent(`interaction:${interactionId}`);\n try {\n await this.interactionHandler({\n channelType: SLACK_CHANNEL_TYPE,\n chatId,\n userId,\n workspaceId,\n interactionId,\n messageId,\n actionId: action.action_id,\n value: action.value,\n timestamp: new Date(Number(interactionId) * 1000),\n });\n } catch {\n // Keep Socket Mode listener failures isolated from the SDK event loop.\n }\n }\n}\n\ninterface SlackInteractionEnvelope {\n ack?: () => Promise<void>;\n body?: {\n type?: string;\n team?: { id?: string };\n user?: { id?: string };\n channel?: { id?: string };\n container?: { message_ts?: string };\n actions?: Array<{ action_id?: string; action_ts?: string; value?: string }>;\n };\n}\n"],"names":["SocketModeClient","WebClient","SlackDeliveryQueue","escapeSlackText","SLACK_CHANNEL_TYPE","validateSlackAppToken","appToken","client","connections","apps","result","open","ok","url","Error","validateSlackCredentials","botToken","authClient","authTest","auth","test","identity","user_id","team_id","app_id","appId","botUserId","workspaceId","workspaceName","team","SlackAdapter","type","socket","web","delivery","recentEventIds","Map","messageHandler","interactionHandler","running","listenersRegistered","config","dependencies","socketClient","webClient","postMessage","input","chat","start","on","payload","handleSlackEvent","catch","undefined","handleInteraction","stop","disconnect","onMessage","handler","onInteraction","sendMessage","chatId","text","send","isHealthy","sendQuestion","question","fallback","header","elements","options","map","option","action_id","label","slice","value","id","allowSkip","push","response","channel","blocks","ts","messageId","finalizeInteraction","update","envelope","ack","body","event","eventId","event_id","isValidDirectMessage","has","rememberEvent","channelType","String","userId","user","timestamp","Date","Number","event_ts","threadId","thread_ts","metadata","slackTs","is_ext_shared_channel","channel_type","subtype","bot_id","set","now","size","oldest","keys","next","delete","action","actions","interactionId","action_ts","container","message_ts","actionId"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChannelAdapter } from './ChannelAdapter.js';
|
|
2
|
-
import type { IncomingMessage } from '../types.js';
|
|
2
|
+
import type { IncomingMessage, InlineKeyboard, CallbackHandler } 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 {
|
|
@@ -12,6 +12,7 @@ export declare class TelegramAdapter implements ChannelAdapter {
|
|
|
12
12
|
readonly type = "telegram";
|
|
13
13
|
private bot;
|
|
14
14
|
private messageHandler;
|
|
15
|
+
private callbackHandler;
|
|
15
16
|
private running;
|
|
16
17
|
constructor(options: TelegramAdapterOptions);
|
|
17
18
|
start(): Promise<void>;
|
|
@@ -23,6 +24,23 @@ export declare class TelegramAdapter implements ChannelAdapter {
|
|
|
23
24
|
*/
|
|
24
25
|
sendMessage(chatId: string, text: string): Promise<void>;
|
|
25
26
|
onMessage(handler: (msg: IncomingMessage) => Promise<void>): void;
|
|
27
|
+
onCallback(handler: CallbackHandler): void;
|
|
28
|
+
/**
|
|
29
|
+
* Send a message with an inline keyboard. `html` is sent verbatim with
|
|
30
|
+
* parse_mode=HTML — callers must pre-escape any user-controlled fields.
|
|
31
|
+
* Returns the Telegram message_id of the sent message.
|
|
32
|
+
*/
|
|
33
|
+
sendInlineKeyboard(chatId: string, html: string, keyboard: InlineKeyboard): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Replace the inline keyboard on an existing message. Pass `null` to remove
|
|
36
|
+
* the keyboard entirely.
|
|
37
|
+
*/
|
|
38
|
+
editInlineKeyboard(chatId: string, messageId: number, keyboard: InlineKeyboard | null): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Acknowledge a callback_query. Without this Telegram leaves a spinner on
|
|
41
|
+
* the tapped button. Pass `text` to show a transient toast.
|
|
42
|
+
*/
|
|
43
|
+
answerCallback(callbackQueryId: string, text?: string): Promise<void>;
|
|
26
44
|
isHealthy(): Promise<boolean>;
|
|
27
45
|
}
|
|
28
46
|
//# sourceMappingURL=TelegramAdapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/TelegramAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"TelegramAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/TelegramAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAoB,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtG,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAChD,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAShD,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,eAAe,CAAgC;IACvD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,sBAAsB;IAIrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4DtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B;;;;OAIG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B9D,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIjE,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAI1C;;;;OAIG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAQjG;;;OAGG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3G;;;OAGG;IACG,cAAc,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrE,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGtC"}
|
|
@@ -11,6 +11,7 @@ const markdownLexer = new Marked();
|
|
|
11
11
|
type = TELEGRAM_CHANNEL_TYPE;
|
|
12
12
|
bot;
|
|
13
13
|
messageHandler = null;
|
|
14
|
+
callbackHandler = null;
|
|
14
15
|
running = false;
|
|
15
16
|
constructor(options){
|
|
16
17
|
this.bot = new Telegraf(options.botToken);
|
|
@@ -32,6 +33,38 @@ const markdownLexer = new Marked();
|
|
|
32
33
|
await ctx.reply(`Error processing message: ${errorMessage}`);
|
|
33
34
|
}
|
|
34
35
|
});
|
|
36
|
+
this.bot.on('callback_query', async (ctx)=>{
|
|
37
|
+
if (!this.callbackHandler) {
|
|
38
|
+
try {
|
|
39
|
+
await ctx.answerCbQuery();
|
|
40
|
+
} catch {}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const query = ctx.callbackQuery;
|
|
44
|
+
const data = typeof query.data === 'string' ? query.data : '';
|
|
45
|
+
if (!query.message) {
|
|
46
|
+
try {
|
|
47
|
+
await ctx.answerCbQuery();
|
|
48
|
+
} catch {}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const cb = {
|
|
52
|
+
channelType: TELEGRAM_CHANNEL_TYPE,
|
|
53
|
+
chatId: String(query.message.chat.id),
|
|
54
|
+
userId: String(query.from.id),
|
|
55
|
+
messageId: query.message.message_id,
|
|
56
|
+
callbackData: data,
|
|
57
|
+
callbackQueryId: query.id,
|
|
58
|
+
timestamp: new Date()
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
await this.callbackHandler(cb);
|
|
62
|
+
} catch {
|
|
63
|
+
try {
|
|
64
|
+
await ctx.answerCbQuery('Error');
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
35
68
|
await this.bot.launch();
|
|
36
69
|
this.running = true;
|
|
37
70
|
}
|
|
@@ -73,10 +106,46 @@ const markdownLexer = new Marked();
|
|
|
73
106
|
onMessage(handler) {
|
|
74
107
|
this.messageHandler = handler;
|
|
75
108
|
}
|
|
109
|
+
onCallback(handler) {
|
|
110
|
+
this.callbackHandler = handler;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Send a message with an inline keyboard. `html` is sent verbatim with
|
|
114
|
+
* parse_mode=HTML — callers must pre-escape any user-controlled fields.
|
|
115
|
+
* Returns the Telegram message_id of the sent message.
|
|
116
|
+
*/ async sendInlineKeyboard(chatId, html, keyboard) {
|
|
117
|
+
const result = await this.bot.telegram.sendMessage(chatId, html, {
|
|
118
|
+
parse_mode: TELEGRAM_PARSE_MODE,
|
|
119
|
+
reply_markup: {
|
|
120
|
+
inline_keyboard: toTelegrafKeyboard(keyboard)
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return result.message_id;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Replace the inline keyboard on an existing message. Pass `null` to remove
|
|
127
|
+
* the keyboard entirely.
|
|
128
|
+
*/ async editInlineKeyboard(chatId, messageId, keyboard) {
|
|
129
|
+
await this.bot.telegram.editMessageReplyMarkup(chatId, messageId, undefined, keyboard ? {
|
|
130
|
+
inline_keyboard: toTelegrafKeyboard(keyboard)
|
|
131
|
+
} : undefined);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Acknowledge a callback_query. Without this Telegram leaves a spinner on
|
|
135
|
+
* the tapped button. Pass `text` to show a transient toast.
|
|
136
|
+
*/ async answerCallback(callbackQueryId, text) {
|
|
137
|
+
await this.bot.telegram.answerCbQuery(callbackQueryId, text);
|
|
138
|
+
}
|
|
76
139
|
async isHealthy() {
|
|
77
140
|
return this.running;
|
|
78
141
|
}
|
|
79
142
|
}
|
|
143
|
+
function toTelegrafKeyboard(keyboard) {
|
|
144
|
+
return keyboard.map((row)=>row.map((btn)=>({
|
|
145
|
+
text: btn.text,
|
|
146
|
+
callback_data: btn.callbackData
|
|
147
|
+
})));
|
|
148
|
+
}
|
|
80
149
|
function isParseEntitiesError(error) {
|
|
81
150
|
if (!error || typeof error !== 'object') return false;
|
|
82
151
|
const description = error.description;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/TelegramAdapter.ts"],"sourcesContent":["import { Telegraf } from 'telegraf';\nimport { Marked, type Token, type Tokens } from 'marked';\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;\nconst markdownLexer = new Marked();\n\ntype TelegramMessageChunk = {\n text: string;\n html: boolean;\n};\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 as markdown source before rendering so each\n * Telegram HTML payload is independently valid.\n */\n async sendMessage(chatId: string, text: string): Promise<void> {\n let chunks: TelegramMessageChunk[];\n try {\n chunks = chunkMarkdownForTelegram(text, TELEGRAM_MAX_MESSAGE_LENGTH);\n } catch {\n for (const chunk of chunkMessage(text, TELEGRAM_MAX_MESSAGE_LENGTH)) {\n await this.bot.telegram.sendMessage(chatId, chunk);\n }\n return;\n }\n\n for (const chunk of chunks) {\n if (!chunk.html) {\n await this.bot.telegram.sendMessage(chatId, chunk.text);\n continue;\n }\n\n try {\n await this.bot.telegram.sendMessage(chatId, chunk.text, { 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.text));\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(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/&/g, '&');\n}\n\nfunction chunkMarkdownForTelegram(markdown: string, maxLen: number): TelegramMessageChunk[] {\n const markdownChunks = splitMarkdownSource(markdown, maxLen);\n const chunks: TelegramMessageChunk[] = [];\n\n for (const markdownChunk of markdownChunks) {\n const html = markdownToTelegramHtml(markdownChunk);\n if (html.length <= maxLen) {\n if (html.length > 0) chunks.push({ text: html, html: true });\n continue;\n }\n\n for (const plainChunk of chunkMessage(markdownChunk, maxLen)) {\n if (plainChunk.length > 0) chunks.push({ text: plainChunk, html: false });\n }\n }\n\n return chunks;\n}\n\nfunction splitMarkdownSource(markdown: string, maxLen: number, depth = 0): string[] {\n if (markdown.length === 0) return [];\n if (renderedLengthFits(markdown, maxLen)) return [markdown];\n if (depth > 6) return splitPlainMarkdownText(markdown, maxLen);\n\n const chunks: string[] = [];\n let current = '';\n const tokens = markdownLexer.lexer(markdown);\n\n for (const token of tokens) {\n const raw = token.raw ?? '';\n if (raw.length === 0) continue;\n\n const candidate = current + raw;\n if (candidate.length > 0 && renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(raw, maxLen)) {\n current = raw;\n } else {\n chunks.push(...splitOversizedToken(token, maxLen, depth + 1));\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks.flatMap((chunk) => renderedLengthFits(chunk, maxLen) ? [chunk] : splitPlainMarkdownText(chunk, maxLen));\n}\n\nfunction splitOversizedToken(token: Token, maxLen: number, depth: number): string[] {\n switch (token.type) {\n case 'code':\n return splitCodeToken(token as Tokens.Code, maxLen);\n case 'list':\n return splitListToken(token as Tokens.List, maxLen, depth);\n case 'paragraph':\n case 'text':\n return splitPlainMarkdownText(token.raw, maxLen);\n default:\n if ('tokens' in token && Array.isArray(token.tokens) && token.raw !== undefined) {\n return splitMarkdownSource(token.raw, maxLen, depth);\n }\n return splitPlainMarkdownText(token.raw ?? '', maxLen);\n }\n}\n\nfunction splitListToken(token: Tokens.List, maxLen: number, depth: number): string[] {\n const chunks: string[] = [];\n let current = '';\n\n for (const item of token.items) {\n const raw = item.raw;\n const candidate = current + raw;\n if (candidate.length > 0 && renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(raw, maxLen)) {\n current = raw;\n } else {\n chunks.push(...splitMarkdownSource(raw, maxLen, depth + 1));\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nfunction splitCodeToken(token: Tokens.Code, maxLen: number): string[] {\n const fence = token.raw.startsWith('~~~') ? '~~~' : '```';\n const lang = token.lang ? token.lang.split(/\\s/)[0] : '';\n const lines = token.text.split('\\n');\n const chunks: string[] = [];\n let currentLines: string[] = [];\n\n const renderFence = (codeLines: string[]): string =>\n `${fence}${lang}\\n${codeLines.join('\\n')}\\n${fence}\\n\\n`;\n\n for (const line of lines) {\n const candidateLines = [...currentLines, line];\n if (renderedLengthFits(renderFence(candidateLines), maxLen)) {\n currentLines = candidateLines;\n continue;\n }\n\n if (currentLines.length > 0) {\n chunks.push(renderFence(currentLines));\n currentLines = [];\n }\n\n if (renderedLengthFits(renderFence([line]), maxLen)) {\n currentLines = [line];\n } else {\n for (const segment of splitCodeLine(line, fence, lang, maxLen)) {\n chunks.push(renderFence([segment]));\n }\n }\n }\n\n if (currentLines.length > 0) chunks.push(renderFence(currentLines));\n return chunks;\n}\n\nfunction splitCodeLine(line: string, fence: string, lang: string, maxLen: number): string[] {\n const segments: string[] = [];\n let current = '';\n const renderFence = (value: string): string => `${fence}${lang}\\n${value}\\n${fence}\\n\\n`;\n\n for (const char of Array.from(line)) {\n const candidate = current + char;\n if (renderedLengthFits(renderFence(candidate), maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n segments.push(current);\n current = '';\n }\n\n if (renderedLengthFits(renderFence(char), maxLen)) {\n current = char;\n } else {\n segments.push(char);\n }\n }\n\n if (current.length > 0) segments.push(current);\n return segments;\n}\n\nfunction splitPlainMarkdownText(markdown: string, maxLen: number): string[] {\n if (markdown.length === 0) return [];\n if (renderedLengthFits(markdown, maxLen)) return [markdown];\n\n const newlineUnits = markdown.split(/(?<=\\n)/u);\n if (newlineUnits.length > 1) {\n return packMarkdownUnits(newlineUnits, maxLen, splitPlainMarkdownText);\n }\n\n const sentenceUnits = markdown.match(/[^.!?]+[.!?]+(?:\\s+|$)|[^.!?]+$/gu);\n if (sentenceUnits && sentenceUnits.length > 1) {\n return packMarkdownUnits(sentenceUnits, maxLen, splitPlainMarkdownText);\n }\n\n const wordUnits = markdown.match(/\\S+\\s*/gu);\n if (wordUnits && wordUnits.length > 1) {\n return packMarkdownUnits(wordUnits, maxLen, splitPlainMarkdownText);\n }\n\n return splitByCodePoint(markdown, maxLen);\n}\n\nfunction packMarkdownUnits(\n units: string[],\n maxLen: number,\n splitOversized: (unit: string, maxLen: number) => string[],\n): string[] {\n const chunks: string[] = [];\n let current = '';\n\n for (const unit of units) {\n const candidate = current + unit;\n if (renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(unit, maxLen)) {\n current = unit;\n } else {\n chunks.push(...splitOversized(unit, maxLen));\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nfunction splitByCodePoint(text: string, maxLen: number): string[] {\n const chunks: string[] = [];\n let current = '';\n\n for (const char of Array.from(text)) {\n const candidate = current + char;\n if (renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(char, maxLen)) {\n current = char;\n } else {\n chunks.push(char);\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nfunction renderedLengthFits(markdown: string, maxLen: number): boolean {\n return markdownToTelegramHtml(markdown).length <= maxLen;\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","Marked","markdownToTelegramHtml","TELEGRAM_CHANNEL_TYPE","TELEGRAM_MAX_MESSAGE_LENGTH","TELEGRAM_PARSE_MODE","markdownLexer","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","chunks","chunkMarkdownForTelegram","chunk","chunkMessage","telegram","html","parse_mode","isParseEntitiesError","htmlToPlainText","onMessage","handler","isHealthy","description","includes","replace","markdown","maxLen","markdownChunks","splitMarkdownSource","markdownChunk","length","push","plainChunk","depth","renderedLengthFits","splitPlainMarkdownText","current","tokens","lexer","token","raw","candidate","splitOversizedToken","flatMap","splitCodeToken","splitListToken","Array","isArray","undefined","item","items","fence","startsWith","lang","split","lines","currentLines","renderFence","codeLines","join","line","candidateLines","segment","splitCodeLine","segments","value","char","newlineUnits","packMarkdownUnits","sentenceUnits","match","wordUnits","splitByCodePoint","units","splitOversized","unit","remaining","lastParagraph","lastIndexOf","lastNewline","splitAt","slice"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/TelegramAdapter.ts"],"sourcesContent":["import { Telegraf } from 'telegraf';\nimport { Marked, type Token, type Tokens } from 'marked';\nimport type { ChannelAdapter } from './ChannelAdapter.js';\nimport { markdownToTelegramHtml } from '../utils/telegramHtml.js';\nimport type { IncomingMessage, InlineKeyboard, IncomingCallback, CallbackHandler } from '../types.js';\n\nexport const TELEGRAM_CHANNEL_TYPE = 'telegram';\nexport const TELEGRAM_MAX_MESSAGE_LENGTH = 4096;\nconst TELEGRAM_PARSE_MODE = 'HTML' as const;\nconst markdownLexer = new Marked();\n\ntype TelegramMessageChunk = {\n text: string;\n html: boolean;\n};\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 callbackHandler: CallbackHandler | 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 this.bot.on('callback_query', async (ctx) => {\n if (!this.callbackHandler) {\n try { await ctx.answerCbQuery(); } catch { /* ignore */ }\n return;\n }\n\n const query = ctx.callbackQuery as {\n id: string;\n data?: string;\n message?: { message_id: number; chat: { id: number | string } };\n from: { id: number | string };\n };\n\n const data = typeof query.data === 'string' ? query.data : '';\n if (!query.message) {\n try { await ctx.answerCbQuery(); } catch { /* ignore */ }\n return;\n }\n\n const cb: IncomingCallback = {\n channelType: TELEGRAM_CHANNEL_TYPE,\n chatId: String(query.message.chat.id),\n userId: String(query.from.id),\n messageId: query.message.message_id,\n callbackData: data,\n callbackQueryId: query.id,\n timestamp: new Date(),\n };\n\n try {\n await this.callbackHandler(cb);\n } catch {\n try { await ctx.answerCbQuery('Error'); } catch { /* ignore */ }\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 as markdown source before rendering so each\n * Telegram HTML payload is independently valid.\n */\n async sendMessage(chatId: string, text: string): Promise<void> {\n let chunks: TelegramMessageChunk[];\n try {\n chunks = chunkMarkdownForTelegram(text, TELEGRAM_MAX_MESSAGE_LENGTH);\n } catch {\n for (const chunk of chunkMessage(text, TELEGRAM_MAX_MESSAGE_LENGTH)) {\n await this.bot.telegram.sendMessage(chatId, chunk);\n }\n return;\n }\n\n for (const chunk of chunks) {\n if (!chunk.html) {\n await this.bot.telegram.sendMessage(chatId, chunk.text);\n continue;\n }\n\n try {\n await this.bot.telegram.sendMessage(chatId, chunk.text, { 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.text));\n }\n }\n }\n\n onMessage(handler: (msg: IncomingMessage) => Promise<void>): void {\n this.messageHandler = handler;\n }\n\n onCallback(handler: CallbackHandler): void {\n this.callbackHandler = handler;\n }\n\n /**\n * Send a message with an inline keyboard. `html` is sent verbatim with\n * parse_mode=HTML — callers must pre-escape any user-controlled fields.\n * Returns the Telegram message_id of the sent message.\n */\n async sendInlineKeyboard(chatId: string, html: string, keyboard: InlineKeyboard): Promise<number> {\n const result = await this.bot.telegram.sendMessage(chatId, html, {\n parse_mode: TELEGRAM_PARSE_MODE,\n reply_markup: { inline_keyboard: toTelegrafKeyboard(keyboard) },\n }) as { message_id: number };\n return result.message_id;\n }\n\n /**\n * Replace the inline keyboard on an existing message. Pass `null` to remove\n * the keyboard entirely.\n */\n async editInlineKeyboard(chatId: string, messageId: number, keyboard: InlineKeyboard | null): Promise<void> {\n await this.bot.telegram.editMessageReplyMarkup(chatId, messageId, undefined, keyboard\n ? { inline_keyboard: toTelegrafKeyboard(keyboard) }\n : undefined);\n }\n\n /**\n * Acknowledge a callback_query. Without this Telegram leaves a spinner on\n * the tapped button. Pass `text` to show a transient toast.\n */\n async answerCallback(callbackQueryId: string, text?: string): Promise<void> {\n await this.bot.telegram.answerCbQuery(callbackQueryId, text);\n }\n\n async isHealthy(): Promise<boolean> {\n return this.running;\n }\n}\n\nfunction toTelegrafKeyboard(keyboard: InlineKeyboard): { text: string; callback_data: string }[][] {\n return keyboard.map((row) => row.map((btn) => ({ text: btn.text, callback_data: btn.callbackData })));\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(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/&/g, '&');\n}\n\nfunction chunkMarkdownForTelegram(markdown: string, maxLen: number): TelegramMessageChunk[] {\n const markdownChunks = splitMarkdownSource(markdown, maxLen);\n const chunks: TelegramMessageChunk[] = [];\n\n for (const markdownChunk of markdownChunks) {\n const html = markdownToTelegramHtml(markdownChunk);\n if (html.length <= maxLen) {\n if (html.length > 0) chunks.push({ text: html, html: true });\n continue;\n }\n\n for (const plainChunk of chunkMessage(markdownChunk, maxLen)) {\n if (plainChunk.length > 0) chunks.push({ text: plainChunk, html: false });\n }\n }\n\n return chunks;\n}\n\nfunction splitMarkdownSource(markdown: string, maxLen: number, depth = 0): string[] {\n if (markdown.length === 0) return [];\n if (renderedLengthFits(markdown, maxLen)) return [markdown];\n if (depth > 6) return splitPlainMarkdownText(markdown, maxLen);\n\n const chunks: string[] = [];\n let current = '';\n const tokens = markdownLexer.lexer(markdown);\n\n for (const token of tokens) {\n const raw = token.raw ?? '';\n if (raw.length === 0) continue;\n\n const candidate = current + raw;\n if (candidate.length > 0 && renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(raw, maxLen)) {\n current = raw;\n } else {\n chunks.push(...splitOversizedToken(token, maxLen, depth + 1));\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks.flatMap((chunk) => renderedLengthFits(chunk, maxLen) ? [chunk] : splitPlainMarkdownText(chunk, maxLen));\n}\n\nfunction splitOversizedToken(token: Token, maxLen: number, depth: number): string[] {\n switch (token.type) {\n case 'code':\n return splitCodeToken(token as Tokens.Code, maxLen);\n case 'list':\n return splitListToken(token as Tokens.List, maxLen, depth);\n case 'paragraph':\n case 'text':\n return splitPlainMarkdownText(token.raw, maxLen);\n default:\n if ('tokens' in token && Array.isArray(token.tokens) && token.raw !== undefined) {\n return splitMarkdownSource(token.raw, maxLen, depth);\n }\n return splitPlainMarkdownText(token.raw ?? '', maxLen);\n }\n}\n\nfunction splitListToken(token: Tokens.List, maxLen: number, depth: number): string[] {\n const chunks: string[] = [];\n let current = '';\n\n for (const item of token.items) {\n const raw = item.raw;\n const candidate = current + raw;\n if (candidate.length > 0 && renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(raw, maxLen)) {\n current = raw;\n } else {\n chunks.push(...splitMarkdownSource(raw, maxLen, depth + 1));\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nfunction splitCodeToken(token: Tokens.Code, maxLen: number): string[] {\n const fence = token.raw.startsWith('~~~') ? '~~~' : '```';\n const lang = token.lang ? token.lang.split(/\\s/)[0] : '';\n const lines = token.text.split('\\n');\n const chunks: string[] = [];\n let currentLines: string[] = [];\n\n const renderFence = (codeLines: string[]): string =>\n `${fence}${lang}\\n${codeLines.join('\\n')}\\n${fence}\\n\\n`;\n\n for (const line of lines) {\n const candidateLines = [...currentLines, line];\n if (renderedLengthFits(renderFence(candidateLines), maxLen)) {\n currentLines = candidateLines;\n continue;\n }\n\n if (currentLines.length > 0) {\n chunks.push(renderFence(currentLines));\n currentLines = [];\n }\n\n if (renderedLengthFits(renderFence([line]), maxLen)) {\n currentLines = [line];\n } else {\n for (const segment of splitCodeLine(line, fence, lang, maxLen)) {\n chunks.push(renderFence([segment]));\n }\n }\n }\n\n if (currentLines.length > 0) chunks.push(renderFence(currentLines));\n return chunks;\n}\n\nfunction splitCodeLine(line: string, fence: string, lang: string, maxLen: number): string[] {\n const segments: string[] = [];\n let current = '';\n const renderFence = (value: string): string => `${fence}${lang}\\n${value}\\n${fence}\\n\\n`;\n\n for (const char of Array.from(line)) {\n const candidate = current + char;\n if (renderedLengthFits(renderFence(candidate), maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n segments.push(current);\n current = '';\n }\n\n if (renderedLengthFits(renderFence(char), maxLen)) {\n current = char;\n } else {\n segments.push(char);\n }\n }\n\n if (current.length > 0) segments.push(current);\n return segments;\n}\n\nfunction splitPlainMarkdownText(markdown: string, maxLen: number): string[] {\n if (markdown.length === 0) return [];\n if (renderedLengthFits(markdown, maxLen)) return [markdown];\n\n const newlineUnits = markdown.split(/(?<=\\n)/u);\n if (newlineUnits.length > 1) {\n return packMarkdownUnits(newlineUnits, maxLen, splitPlainMarkdownText);\n }\n\n const sentenceUnits = markdown.match(/[^.!?]+[.!?]+(?:\\s+|$)|[^.!?]+$/gu);\n if (sentenceUnits && sentenceUnits.length > 1) {\n return packMarkdownUnits(sentenceUnits, maxLen, splitPlainMarkdownText);\n }\n\n const wordUnits = markdown.match(/\\S+\\s*/gu);\n if (wordUnits && wordUnits.length > 1) {\n return packMarkdownUnits(wordUnits, maxLen, splitPlainMarkdownText);\n }\n\n return splitByCodePoint(markdown, maxLen);\n}\n\nfunction packMarkdownUnits(\n units: string[],\n maxLen: number,\n splitOversized: (unit: string, maxLen: number) => string[],\n): string[] {\n const chunks: string[] = [];\n let current = '';\n\n for (const unit of units) {\n const candidate = current + unit;\n if (renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(unit, maxLen)) {\n current = unit;\n } else {\n chunks.push(...splitOversized(unit, maxLen));\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nfunction splitByCodePoint(text: string, maxLen: number): string[] {\n const chunks: string[] = [];\n let current = '';\n\n for (const char of Array.from(text)) {\n const candidate = current + char;\n if (renderedLengthFits(candidate, maxLen)) {\n current = candidate;\n continue;\n }\n\n if (current.length > 0) {\n chunks.push(current);\n current = '';\n }\n\n if (renderedLengthFits(char, maxLen)) {\n current = char;\n } else {\n chunks.push(char);\n }\n }\n\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nfunction renderedLengthFits(markdown: string, maxLen: number): boolean {\n return markdownToTelegramHtml(markdown).length <= maxLen;\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","Marked","markdownToTelegramHtml","TELEGRAM_CHANNEL_TYPE","TELEGRAM_MAX_MESSAGE_LENGTH","TELEGRAM_PARSE_MODE","markdownLexer","TelegramAdapter","type","bot","messageHandler","callbackHandler","running","options","botToken","start","on","ctx","msg","channelType","chatId","String","message","chat","id","userId","from","text","timestamp","Date","date","error","errorMessage","Error","reply","answerCbQuery","query","callbackQuery","data","cb","messageId","message_id","callbackData","callbackQueryId","launch","stop","sendMessage","chunks","chunkMarkdownForTelegram","chunk","chunkMessage","telegram","html","parse_mode","isParseEntitiesError","htmlToPlainText","onMessage","handler","onCallback","sendInlineKeyboard","keyboard","result","reply_markup","inline_keyboard","toTelegrafKeyboard","editInlineKeyboard","editMessageReplyMarkup","undefined","answerCallback","isHealthy","map","row","btn","callback_data","description","includes","replace","markdown","maxLen","markdownChunks","splitMarkdownSource","markdownChunk","length","push","plainChunk","depth","renderedLengthFits","splitPlainMarkdownText","current","tokens","lexer","token","raw","candidate","splitOversizedToken","flatMap","splitCodeToken","splitListToken","Array","isArray","item","items","fence","startsWith","lang","split","lines","currentLines","renderFence","codeLines","join","line","candidateLines","segment","splitCodeLine","segments","value","char","newlineUnits","packMarkdownUnits","sentenceUnits","match","wordUnits","splitByCodePoint","units","splitOversized","unit","remaining","lastParagraph","lastIndexOf","lastNewline","splitAt","slice"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export { ChannelManager } from './ChannelManager.js';
|
|
2
2
|
export { ConfigStore } from './ConfigStore.js';
|
|
3
3
|
export { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';
|
|
4
|
+
export { SlackAdapter, SLACK_CHANNEL_TYPE, validateSlackAppToken, validateSlackCredentials, } from './adapters/SlackAdapter.js';
|
|
5
|
+
export type { SlackIdentity } from './adapters/SlackAdapter.js';
|
|
6
|
+
export { SLACK_MAX_MESSAGE_LENGTH, chunkMarkdownForSlack, escapeSlackText, markdownToSlackMrkdwn, } from './utils/slackMarkdown.js';
|
|
7
|
+
export { SlackDeliveryQueue } from './utils/SlackDeliveryQueue.js';
|
|
4
8
|
export type { TelegramAdapterOptions } from './adapters/TelegramAdapter.js';
|
|
5
|
-
export
|
|
6
|
-
export type {
|
|
9
|
+
export { isInteractiveChannelAdapter } from './adapters/ChannelAdapter.js';
|
|
10
|
+
export type { ChannelAdapter, InteractiveChannelAdapter } from './adapters/ChannelAdapter.js';
|
|
11
|
+
export type { IncomingMessage, MessageHandler, ChannelConfig, ChannelEntry, ChannelType, TelegramConfig, SlackConfig, SlackChannelEntry, SendMessageOptions, SentMessage, ChannelQuestion, ChannelQuestionOption, IncomingInteraction, InteractionHandler, InlineKeyboardButton, InlineKeyboard, IncomingCallback, CallbackHandler, } from './types.js';
|
|
7
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACpH,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACpH,OAAO,EACH,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EACH,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,YAAY,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAE9F,YAAY,EACR,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,WAAW,EACX,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,eAAe,GAClB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { ChannelManager } from './ChannelManager.js';
|
|
2
2
|
export { ConfigStore } from './ConfigStore.js';
|
|
3
3
|
export { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';
|
|
4
|
+
export { SlackAdapter, SLACK_CHANNEL_TYPE, validateSlackAppToken, validateSlackCredentials } from './adapters/SlackAdapter.js';
|
|
5
|
+
export { SLACK_MAX_MESSAGE_LENGTH, chunkMarkdownForSlack, escapeSlackText, markdownToSlackMrkdwn } from './utils/slackMarkdown.js';
|
|
6
|
+
export { SlackDeliveryQueue } from './utils/SlackDeliveryQueue.js';
|
|
7
|
+
export { isInteractiveChannelAdapter } from './adapters/ChannelAdapter.js';
|
|
4
8
|
|
|
5
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { ChannelManager } from './ChannelManager.js';\nexport { ConfigStore } from './ConfigStore.js';\nexport { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';\nexport type { TelegramAdapterOptions } from './adapters/TelegramAdapter.js';\n\nexport type { ChannelAdapter } from './adapters/ChannelAdapter.js';\n\nexport type {\n IncomingMessage,\n MessageHandler,\n ChannelConfig,\n ChannelEntry,\n ChannelType,\n TelegramConfig,\n} from './types.js';\n"],"names":["ChannelManager","ConfigStore","TelegramAdapter","TELEGRAM_CHANNEL_TYPE","TELEGRAM_MAX_MESSAGE_LENGTH"],"mappings":"AAAA,SAASA,cAAc,QAAQ,sBAAsB;AACrD,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,eAAe,EAAEC,qBAAqB,EAAEC,2BAA2B,QAAQ,gCAAgC"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { ChannelManager } from './ChannelManager.js';\nexport { ConfigStore } from './ConfigStore.js';\nexport { TelegramAdapter, TELEGRAM_CHANNEL_TYPE, TELEGRAM_MAX_MESSAGE_LENGTH } from './adapters/TelegramAdapter.js';\nexport {\n SlackAdapter,\n SLACK_CHANNEL_TYPE,\n validateSlackAppToken,\n validateSlackCredentials,\n} from './adapters/SlackAdapter.js';\nexport type { SlackIdentity } from './adapters/SlackAdapter.js';\nexport {\n SLACK_MAX_MESSAGE_LENGTH,\n chunkMarkdownForSlack,\n escapeSlackText,\n markdownToSlackMrkdwn,\n} from './utils/slackMarkdown.js';\nexport { SlackDeliveryQueue } from './utils/SlackDeliveryQueue.js';\nexport type { TelegramAdapterOptions } from './adapters/TelegramAdapter.js';\n\nexport { isInteractiveChannelAdapter } from './adapters/ChannelAdapter.js';\nexport type { ChannelAdapter, InteractiveChannelAdapter } from './adapters/ChannelAdapter.js';\n\nexport type {\n IncomingMessage,\n MessageHandler,\n ChannelConfig,\n ChannelEntry,\n ChannelType,\n TelegramConfig,\n SlackConfig,\n SlackChannelEntry,\n SendMessageOptions,\n SentMessage,\n ChannelQuestion,\n ChannelQuestionOption,\n IncomingInteraction,\n InteractionHandler,\n InlineKeyboardButton,\n InlineKeyboard,\n IncomingCallback,\n CallbackHandler,\n} from './types.js';\n"],"names":["ChannelManager","ConfigStore","TelegramAdapter","TELEGRAM_CHANNEL_TYPE","TELEGRAM_MAX_MESSAGE_LENGTH","SlackAdapter","SLACK_CHANNEL_TYPE","validateSlackAppToken","validateSlackCredentials","SLACK_MAX_MESSAGE_LENGTH","chunkMarkdownForSlack","escapeSlackText","markdownToSlackMrkdwn","SlackDeliveryQueue","isInteractiveChannelAdapter"],"mappings":"AAAA,SAASA,cAAc,QAAQ,sBAAsB;AACrD,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,eAAe,EAAEC,qBAAqB,EAAEC,2BAA2B,QAAQ,gCAAgC;AACpH,SACIC,YAAY,EACZC,kBAAkB,EAClBC,qBAAqB,EACrBC,wBAAwB,QACrB,6BAA6B;AAEpC,SACIC,wBAAwB,EACxBC,qBAAqB,EACrBC,eAAe,EACfC,qBAAqB,QAClB,2BAA2B;AAClC,SAASC,kBAAkB,QAAQ,gCAAgC;AAGnE,SAASC,2BAA2B,QAAQ,+BAA+B"}
|