@checkstack/notification-telegram-backend 0.0.2
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/CHANGELOG.md +85 -0
- package/package.json +26 -0
- package/src/index.ts +216 -0
- package/src/plugin-metadata.ts +5 -0
- package/tsconfig.json +3 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @checkstack/notification-telegram-backend
|
|
2
|
+
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
|
|
8
|
+
- Updated dependencies [d20d274]
|
|
9
|
+
- @checkstack/backend-api@0.0.2
|
|
10
|
+
- @checkstack/common@0.0.2
|
|
11
|
+
- @checkstack/notification-backend@0.0.2
|
|
12
|
+
|
|
13
|
+
## 0.1.2
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- a65e002: Add compile-time type safety for Lucide icon names
|
|
18
|
+
|
|
19
|
+
- Add `LucideIconName` type and `lucideIconSchema` Zod schema to `@checkstack/common`
|
|
20
|
+
- Update backend interfaces (`AuthStrategy`, `NotificationStrategy`, `IntegrationProvider`, `CommandDefinition`) to use `LucideIconName`
|
|
21
|
+
- Update RPC contracts to use `lucideIconSchema` for proper type inference across RPC boundaries
|
|
22
|
+
- Simplify `SocialProviderButton` to use `DynamicIcon` directly (removes 30+ lines of pascalCase conversion)
|
|
23
|
+
- Replace static `iconMap` in `SearchDialog` with `DynamicIcon` for dynamic icon rendering
|
|
24
|
+
- Add fallback handling in `DynamicIcon` when icon name isn't found
|
|
25
|
+
- Fix legacy kebab-case icon names to PascalCase: `mail`→`Mail`, `send`→`Send`, `github`→`Github`, `key-round`→`KeyRound`, `network`→`Network`, `AlertCircle`→`CircleAlert`
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [b4eb432]
|
|
28
|
+
- Updated dependencies [a65e002]
|
|
29
|
+
- @checkstack/backend-api@1.1.0
|
|
30
|
+
- @checkstack/notification-backend@0.1.2
|
|
31
|
+
- @checkstack/common@0.2.0
|
|
32
|
+
|
|
33
|
+
## 0.1.1
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- @checkstack/notification-backend@0.1.1
|
|
38
|
+
|
|
39
|
+
## 0.1.0
|
|
40
|
+
|
|
41
|
+
### Minor Changes
|
|
42
|
+
|
|
43
|
+
- b354ab3: # Strategy Instructions Support & Telegram Notification Plugin
|
|
44
|
+
|
|
45
|
+
## Strategy Instructions Interface
|
|
46
|
+
|
|
47
|
+
Added `adminInstructions` and `userInstructions` optional fields to the `NotificationStrategy` interface. These allow strategies to export markdown-formatted setup guides that are displayed in the configuration UI:
|
|
48
|
+
|
|
49
|
+
- **`adminInstructions`**: Shown when admins configure platform-wide strategy settings (e.g., how to create API keys)
|
|
50
|
+
- **`userInstructions`**: Shown when users configure their personal settings (e.g., how to link their account)
|
|
51
|
+
|
|
52
|
+
### Updated Components
|
|
53
|
+
|
|
54
|
+
- `StrategyConfigCard` now accepts an `instructions` prop and renders it before config sections
|
|
55
|
+
- `StrategyCard` passes `adminInstructions` to `StrategyConfigCard`
|
|
56
|
+
- `UserChannelCard` renders `userInstructions` when users need to connect
|
|
57
|
+
|
|
58
|
+
## New Telegram Notification Plugin
|
|
59
|
+
|
|
60
|
+
Added `@checkstack/notification-telegram-backend` plugin for sending notifications via Telegram:
|
|
61
|
+
|
|
62
|
+
- Uses [grammY](https://grammy.dev/) framework for Telegram Bot API integration
|
|
63
|
+
- Sends messages with MarkdownV2 formatting and inline keyboard buttons for actions
|
|
64
|
+
- Includes comprehensive admin instructions for bot setup via @BotFather
|
|
65
|
+
- Includes user instructions for account linking
|
|
66
|
+
|
|
67
|
+
### Configuration
|
|
68
|
+
|
|
69
|
+
Admins need to configure a Telegram Bot Token obtained from @BotFather.
|
|
70
|
+
|
|
71
|
+
### User Linking
|
|
72
|
+
|
|
73
|
+
The strategy uses `contactResolution: { type: "custom" }` for Telegram Login Widget integration. Full frontend integration for the Login Widget is pending future work.
|
|
74
|
+
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- Updated dependencies [ffc28f6]
|
|
78
|
+
- Updated dependencies [71275dd]
|
|
79
|
+
- Updated dependencies [ae19ff6]
|
|
80
|
+
- Updated dependencies [b55fae6]
|
|
81
|
+
- Updated dependencies [b354ab3]
|
|
82
|
+
- Updated dependencies [81f3f85]
|
|
83
|
+
- @checkstack/common@0.1.0
|
|
84
|
+
- @checkstack/backend-api@1.0.0
|
|
85
|
+
- @checkstack/notification-backend@0.1.0
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/notification-telegram-backend",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./plugin-metadata": "./src/plugin-metadata.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"lint": "eslint src --ext .ts"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@checkstack/backend-api": "workspace:*",
|
|
16
|
+
"@checkstack/common": "workspace:*",
|
|
17
|
+
"@checkstack/notification-backend": "workspace:*",
|
|
18
|
+
"grammy": "^1.35.0",
|
|
19
|
+
"telegramify-markdown": "^1.3.2",
|
|
20
|
+
"zod": "^4.2.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@checkstack/tsconfig": "workspace:*",
|
|
24
|
+
"typescript": "^5.7.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Bot } from "grammy";
|
|
3
|
+
import {
|
|
4
|
+
createBackendPlugin,
|
|
5
|
+
configString,
|
|
6
|
+
Versioned,
|
|
7
|
+
type NotificationStrategy,
|
|
8
|
+
type NotificationSendContext,
|
|
9
|
+
type NotificationDeliveryResult,
|
|
10
|
+
} from "@checkstack/backend-api";
|
|
11
|
+
import { notificationStrategyExtensionPoint } from "@checkstack/notification-backend";
|
|
12
|
+
import { pluginMetadata } from "./plugin-metadata";
|
|
13
|
+
|
|
14
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
15
|
+
// Configuration Schema
|
|
16
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Admin configuration for Telegram strategy.
|
|
20
|
+
*/
|
|
21
|
+
const telegramConfigSchemaV1 = z.object({
|
|
22
|
+
botToken: configString({ "x-secret": true }).describe(
|
|
23
|
+
"Telegram Bot API Token from @BotFather"
|
|
24
|
+
),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
type TelegramConfig = z.infer<typeof telegramConfigSchemaV1>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* User configuration for Telegram - users provide their own chat ID.
|
|
31
|
+
*/
|
|
32
|
+
const telegramUserConfigSchema = z.object({
|
|
33
|
+
chatId: z.string().describe("Your Telegram Chat ID"),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
type TelegramUserConfig = z.infer<typeof telegramUserConfigSchema>;
|
|
37
|
+
|
|
38
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
39
|
+
// Instructions
|
|
40
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
41
|
+
|
|
42
|
+
const adminInstructions = `
|
|
43
|
+
## Setup a Telegram Bot
|
|
44
|
+
|
|
45
|
+
1. Open [@BotFather](https://t.me/BotFather) in Telegram
|
|
46
|
+
2. Send \`/newbot\` and follow the prompts to create your bot
|
|
47
|
+
3. Copy the **Bot Token** (format: \`123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11\`)
|
|
48
|
+
4. Send \`/setdomain\` to BotFather and set your domain (e.g., \`yourdomain.com\`)
|
|
49
|
+
|
|
50
|
+
> **Note**: The domain must match where Checkstack is hosted for the Login Widget to work.
|
|
51
|
+
`.trim();
|
|
52
|
+
|
|
53
|
+
const userInstructions = `
|
|
54
|
+
## Get Your Telegram Chat ID
|
|
55
|
+
|
|
56
|
+
1. Start a chat with your organization's notification bot
|
|
57
|
+
2. Send any message to the bot
|
|
58
|
+
3. Open [@userinfobot](https://t.me/userinfobot) and send \`/start\` to get your Chat ID
|
|
59
|
+
4. Enter your Chat ID in the field above and save
|
|
60
|
+
|
|
61
|
+
> **Note**: Make sure you've messaged the notification bot before sending a notification, or the bot won't be able to reach you.
|
|
62
|
+
`.trim();
|
|
63
|
+
|
|
64
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
65
|
+
// Telegram Strategy Implementation
|
|
66
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
67
|
+
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
69
|
+
const telegramifyMarkdown = require("telegramify-markdown") as (
|
|
70
|
+
markdown: string,
|
|
71
|
+
unsupportedTagsStrategy?: "escape" | "remove" | "keep"
|
|
72
|
+
) => string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Telegram notification strategy using grammY.
|
|
76
|
+
*/
|
|
77
|
+
const telegramStrategy: NotificationStrategy<
|
|
78
|
+
TelegramConfig,
|
|
79
|
+
TelegramUserConfig
|
|
80
|
+
> = {
|
|
81
|
+
id: "telegram",
|
|
82
|
+
displayName: "Telegram",
|
|
83
|
+
description: "Send notifications via Telegram bot messages",
|
|
84
|
+
icon: "Send",
|
|
85
|
+
|
|
86
|
+
config: new Versioned({
|
|
87
|
+
version: 1,
|
|
88
|
+
schema: telegramConfigSchemaV1,
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
// User-config resolution - users enter their chat ID manually
|
|
92
|
+
contactResolution: { type: "user-config", field: "chatId" },
|
|
93
|
+
|
|
94
|
+
userConfig: new Versioned({
|
|
95
|
+
version: 1,
|
|
96
|
+
schema: telegramUserConfigSchema,
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
adminInstructions,
|
|
100
|
+
userInstructions,
|
|
101
|
+
|
|
102
|
+
async send(
|
|
103
|
+
context: NotificationSendContext<TelegramConfig, TelegramUserConfig>
|
|
104
|
+
): Promise<NotificationDeliveryResult> {
|
|
105
|
+
const { userConfig, notification, strategyConfig } = context;
|
|
106
|
+
|
|
107
|
+
if (!strategyConfig.botToken) {
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: "Telegram bot token not configured",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!userConfig?.chatId) {
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
error: "User has not configured their Telegram chat ID",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// Create bot instance
|
|
123
|
+
const bot = new Bot(strategyConfig.botToken);
|
|
124
|
+
|
|
125
|
+
// Build message body using telegramify-markdown for proper escaping and conversion
|
|
126
|
+
let messageBody = "";
|
|
127
|
+
if (notification.body) {
|
|
128
|
+
messageBody = telegramifyMarkdown(notification.body, "escape");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Build title (bold) with proper escaping
|
|
132
|
+
const messageTitle = telegramifyMarkdown(
|
|
133
|
+
`**${notification.title}**`,
|
|
134
|
+
"escape"
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Add importance indicator
|
|
138
|
+
const importanceEmoji = {
|
|
139
|
+
info: "ℹ️",
|
|
140
|
+
warning: "⚠️",
|
|
141
|
+
critical: "🚨",
|
|
142
|
+
};
|
|
143
|
+
let messageText = `${
|
|
144
|
+
importanceEmoji[notification.importance]
|
|
145
|
+
} ${messageTitle}`;
|
|
146
|
+
if (messageBody) {
|
|
147
|
+
messageText += `\n\n${messageBody}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Build inline keyboard for action button
|
|
151
|
+
const actionUrl = notification.action?.url;
|
|
152
|
+
|
|
153
|
+
// Don't show action button for localhost URLs (Telegram rejects them)
|
|
154
|
+
// Instead, add as inline link in the message
|
|
155
|
+
const isLocalhost =
|
|
156
|
+
actionUrl?.includes("localhost") || actionUrl?.includes("127.0.0.1");
|
|
157
|
+
|
|
158
|
+
if (notification.action && actionUrl && isLocalhost) {
|
|
159
|
+
// Add action as plain text (Telegram won't make localhost links clickable anyway)
|
|
160
|
+
// This makes it easier to copy the URL for debugging
|
|
161
|
+
const plainTextAction = `📎 ${notification.action.label}:\n${actionUrl}\n\n_Note: Telegram blocks localhost URLs, so no inline button is shown._`;
|
|
162
|
+
messageText += `\n\n${telegramifyMarkdown(plainTextAction, "escape")}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const inlineKeyboard =
|
|
166
|
+
notification.action && actionUrl && !isLocalhost
|
|
167
|
+
? {
|
|
168
|
+
inline_keyboard: [
|
|
169
|
+
[
|
|
170
|
+
{
|
|
171
|
+
text: notification.action.label,
|
|
172
|
+
url: actionUrl,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
],
|
|
176
|
+
}
|
|
177
|
+
: undefined;
|
|
178
|
+
|
|
179
|
+
// Send the message
|
|
180
|
+
const result = await bot.api.sendMessage(userConfig.chatId, messageText, {
|
|
181
|
+
parse_mode: "MarkdownV2",
|
|
182
|
+
reply_markup: inlineKeyboard,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
externalId: String(result.message_id),
|
|
188
|
+
};
|
|
189
|
+
} catch (error) {
|
|
190
|
+
const message =
|
|
191
|
+
error instanceof Error ? error.message : "Unknown Telegram API error";
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: `Failed to send Telegram message: ${message}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
201
|
+
// Plugin Definition
|
|
202
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
203
|
+
|
|
204
|
+
export default createBackendPlugin({
|
|
205
|
+
metadata: pluginMetadata,
|
|
206
|
+
|
|
207
|
+
register(env) {
|
|
208
|
+
// Get the notification strategy extension point
|
|
209
|
+
const extensionPoint = env.getExtensionPoint(
|
|
210
|
+
notificationStrategyExtensionPoint
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Register the Telegram strategy with our plugin metadata
|
|
214
|
+
extensionPoint.addStrategy(telegramStrategy, pluginMetadata);
|
|
215
|
+
},
|
|
216
|
+
});
|
package/tsconfig.json
ADDED