@amirhosseinnouri/send 1.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/README.md +181 -0
- package/dist/cli.cjs +408 -0
- package/dist/index.cjs +356 -0
- package/dist/index.d.cts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +318 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# @amirhosseinnouri/send
|
|
2
|
+
|
|
3
|
+
Send a **structured message** to a chat provider — from code or the command line.
|
|
4
|
+
|
|
5
|
+
Supported providers: **Slack**, **Microsoft Teams**, **Telegram**, **Element/Matrix**, **Mattermost**.
|
|
6
|
+
|
|
7
|
+
A message is provider-agnostic:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
interface Message {
|
|
11
|
+
title?: string;
|
|
12
|
+
body: string;
|
|
13
|
+
markdown?: boolean; // render the body as markdown in the provider's native format
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Each provider renders it into its own native format (Slack blocks, Teams MessageCard,
|
|
18
|
+
Telegram/Element markdown, Mattermost attachment). With `markdown: true` the body is
|
|
19
|
+
rendered as rich markdown; otherwise it is sent as plain text.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm i @amirhosseinnouri/send
|
|
25
|
+
# or run directly, no install:
|
|
26
|
+
npx @amirhosseinnouri/send --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Programmatic API
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { send, createSender } from "@amirhosseinnouri/send";
|
|
33
|
+
|
|
34
|
+
// one-shot — flat config + message
|
|
35
|
+
await send({
|
|
36
|
+
provider: "slack",
|
|
37
|
+
webhookUrl: "https://hooks.slack.com/services/...",
|
|
38
|
+
title: "Release 1.2.0",
|
|
39
|
+
body: "## Changes\n- Fixed a bug",
|
|
40
|
+
markdown: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// reusable sender bound to one provider
|
|
44
|
+
const sender = createSender({ provider: "telegram", botToken: "T", chatId: "42" });
|
|
45
|
+
await sender.send({ body: "hello" });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Exports also include the zod schemas (`senderConfigSchema`, `messageSchema`,
|
|
49
|
+
`providerConfigSchemas`, ...) and the `PROVIDERS` list for consumers that want to
|
|
50
|
+
validate input or reuse the per-provider config shapes.
|
|
51
|
+
|
|
52
|
+
### Provider config
|
|
53
|
+
|
|
54
|
+
| provider | config keys |
|
|
55
|
+
| ------------ | ---------------------------------------------- |
|
|
56
|
+
| `slack` | `webhookUrl` |
|
|
57
|
+
| `teams` | `webhookUrl` |
|
|
58
|
+
| `mattermost` | `webhookUrl` |
|
|
59
|
+
| `telegram` | `botToken`, `chatId` |
|
|
60
|
+
| `element` | `homeserverUrl`, `accessToken`, `roomId` |
|
|
61
|
+
|
|
62
|
+
## CLI
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
send --provider slack --webhook-url <url> \
|
|
66
|
+
--title "Release 1.2" --body "Bug **fixes**" --markdown
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- Provider config flags: `--webhook-url` (slack/teams/mattermost);
|
|
70
|
+
`--bot-token --chat-id` (telegram);
|
|
71
|
+
`--homeserver-url --access-token --room-id` (element).
|
|
72
|
+
- Body: `--body <text>` (required).
|
|
73
|
+
- `--title` optional. `--markdown` renders the body as markdown (off by default).
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
send --provider telegram --bot-token T --chat-id 42 --title Hi --body "the body"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Examples by provider
|
|
80
|
+
|
|
81
|
+
Each block shows the CLI invocation and the equivalent programmatic call.
|
|
82
|
+
|
|
83
|
+
### Slack
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
send --provider slack \
|
|
87
|
+
--webhook-url https://hooks.slack.com/services/T000/B000/XXXX \
|
|
88
|
+
--title "Release 1.2.0" --body "Bug **fixes** and improvements" --markdown
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
await send({
|
|
93
|
+
provider: "slack",
|
|
94
|
+
webhookUrl: "https://hooks.slack.com/services/T000/B000/XXXX",
|
|
95
|
+
title: "Release 1.2.0",
|
|
96
|
+
body: "Bug **fixes** and improvements",
|
|
97
|
+
markdown: true,
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Microsoft Teams
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
send --provider teams \
|
|
105
|
+
--webhook-url https://outlook.office.com/webhook/XXXX/IncomingWebhook/YYYY \
|
|
106
|
+
--title "Deploy complete" --body "Shipped to **production**" --markdown
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
await send({
|
|
111
|
+
provider: "teams",
|
|
112
|
+
webhookUrl: "https://outlook.office.com/webhook/XXXX/IncomingWebhook/YYYY",
|
|
113
|
+
title: "Deploy complete",
|
|
114
|
+
body: "Shipped to **production**",
|
|
115
|
+
markdown: true,
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Telegram
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
send --provider telegram \
|
|
123
|
+
--bot-token 123456:ABC-DEF1234ghIkl \
|
|
124
|
+
--chat-id -1001234567890 \
|
|
125
|
+
--title "Alert" --body "Disk usage at *90%*" --markdown
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
await send({
|
|
130
|
+
provider: "telegram",
|
|
131
|
+
botToken: "123456:ABC-DEF1234ghIkl",
|
|
132
|
+
chatId: "-1001234567890",
|
|
133
|
+
title: "Alert",
|
|
134
|
+
body: "Disk usage at *90%*",
|
|
135
|
+
markdown: true,
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Element / Matrix
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
send --provider element \
|
|
143
|
+
--homeserver-url https://matrix.org \
|
|
144
|
+
--access-token syt_XXXX \
|
|
145
|
+
--room-id '!abc123:matrix.org' \
|
|
146
|
+
--title "CI" --body "Build **passed**" --markdown
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
await send({
|
|
151
|
+
provider: "element",
|
|
152
|
+
homeserverUrl: "https://matrix.org",
|
|
153
|
+
accessToken: "syt_XXXX",
|
|
154
|
+
roomId: "!abc123:matrix.org",
|
|
155
|
+
title: "CI",
|
|
156
|
+
body: "Build **passed**",
|
|
157
|
+
markdown: true,
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Mattermost
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
send --provider mattermost \
|
|
165
|
+
--webhook-url https://chat.example.com/hooks/xxxxxxxxxxxxxxxxx \
|
|
166
|
+
--title "Backup" --body "Nightly backup `OK`" --markdown
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
await send({
|
|
171
|
+
provider: "mattermost",
|
|
172
|
+
webhookUrl: "https://chat.example.com/hooks/xxxxxxxxxxxxxxxxx",
|
|
173
|
+
title: "Backup",
|
|
174
|
+
body: "Nightly backup `OK`",
|
|
175
|
+
markdown: true,
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_command_line_args = __toESM(require("command-line-args"), 1);
|
|
28
|
+
var import_zod7 = require("zod");
|
|
29
|
+
|
|
30
|
+
// src/schema/sender.ts
|
|
31
|
+
var import_zod6 = require("zod");
|
|
32
|
+
|
|
33
|
+
// src/schema/element.ts
|
|
34
|
+
var import_zod = require("zod");
|
|
35
|
+
var elementConfigSchema = import_zod.z.object({
|
|
36
|
+
provider: import_zod.z.literal("element"),
|
|
37
|
+
homeserverUrl: import_zod.z.url(),
|
|
38
|
+
accessToken: import_zod.z.string(),
|
|
39
|
+
roomId: import_zod.z.string()
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// src/schema/mattermost.ts
|
|
43
|
+
var import_zod2 = require("zod");
|
|
44
|
+
var mattermostConfigSchema = import_zod2.z.object({
|
|
45
|
+
provider: import_zod2.z.literal("mattermost"),
|
|
46
|
+
webhookUrl: import_zod2.z.url()
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// src/schema/slack.ts
|
|
50
|
+
var import_zod3 = require("zod");
|
|
51
|
+
var slackConfigSchema = import_zod3.z.object({
|
|
52
|
+
provider: import_zod3.z.literal("slack"),
|
|
53
|
+
webhookUrl: import_zod3.z.url()
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// src/schema/teams.ts
|
|
57
|
+
var import_zod4 = require("zod");
|
|
58
|
+
var teamsConfigSchema = import_zod4.z.object({
|
|
59
|
+
provider: import_zod4.z.literal("teams"),
|
|
60
|
+
webhookUrl: import_zod4.z.url()
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// src/schema/telegram.ts
|
|
64
|
+
var import_zod5 = require("zod");
|
|
65
|
+
var telegramConfigSchema = import_zod5.z.object({
|
|
66
|
+
provider: import_zod5.z.literal("telegram"),
|
|
67
|
+
botToken: import_zod5.z.string(),
|
|
68
|
+
chatId: import_zod5.z.string()
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// src/schema/sender.ts
|
|
72
|
+
var messageSchema = import_zod6.z.object({
|
|
73
|
+
title: import_zod6.z.string().optional(),
|
|
74
|
+
body: import_zod6.z.string(),
|
|
75
|
+
/** Render the body as markdown in the provider's native format. */
|
|
76
|
+
markdown: import_zod6.z.boolean().optional()
|
|
77
|
+
});
|
|
78
|
+
var senderConfigSchema = import_zod6.z.discriminatedUnion("provider", [
|
|
79
|
+
slackConfigSchema,
|
|
80
|
+
teamsConfigSchema,
|
|
81
|
+
mattermostConfigSchema,
|
|
82
|
+
telegramConfigSchema,
|
|
83
|
+
elementConfigSchema
|
|
84
|
+
]);
|
|
85
|
+
var sendInputSchema = import_zod6.z.intersection(
|
|
86
|
+
senderConfigSchema,
|
|
87
|
+
messageSchema
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// src/lib/flags.ts
|
|
91
|
+
var toKebab = (key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
92
|
+
var flagFor = (key) => `--${toKebab(key)}`;
|
|
93
|
+
var configKeys = [
|
|
94
|
+
...new Set(
|
|
95
|
+
senderConfigSchema.options.flatMap((option) => Object.keys(option.shape))
|
|
96
|
+
)
|
|
97
|
+
];
|
|
98
|
+
var configOptionDefinitions = configKeys.map((key) => ({
|
|
99
|
+
name: toKebab(key),
|
|
100
|
+
type: String
|
|
101
|
+
}));
|
|
102
|
+
var optionDefinitions = [
|
|
103
|
+
...configOptionDefinitions,
|
|
104
|
+
{ name: "title", type: String },
|
|
105
|
+
{ name: "body", type: String },
|
|
106
|
+
{ name: "markdown", type: Boolean },
|
|
107
|
+
{ name: "help", alias: "h", type: Boolean }
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
// src/lib/config.ts
|
|
111
|
+
function formatConfigError(error) {
|
|
112
|
+
const lines = error.issues.map((issue) => {
|
|
113
|
+
const key = issue.path[0];
|
|
114
|
+
const where = typeof key === "string" ? flagFor(key) : "config";
|
|
115
|
+
return ` \u2192 ${where}: ${issue.message}`;
|
|
116
|
+
});
|
|
117
|
+
return `Invalid provider config:
|
|
118
|
+
${lines.join("\n")}`;
|
|
119
|
+
}
|
|
120
|
+
function parseConfig(values) {
|
|
121
|
+
const result = senderConfigSchema.safeParse(values);
|
|
122
|
+
if (!result.success) {
|
|
123
|
+
throw new Error(formatConfigError(result.error));
|
|
124
|
+
}
|
|
125
|
+
return result.data;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/providers/element.ts
|
|
129
|
+
var import_marked = require("marked");
|
|
130
|
+
var ElementSender = class {
|
|
131
|
+
name = "Element";
|
|
132
|
+
homeserverUrl;
|
|
133
|
+
accessToken;
|
|
134
|
+
roomId;
|
|
135
|
+
constructor(config) {
|
|
136
|
+
this.homeserverUrl = config.homeserverUrl;
|
|
137
|
+
this.accessToken = config.accessToken;
|
|
138
|
+
this.roomId = config.roomId;
|
|
139
|
+
}
|
|
140
|
+
async send(message) {
|
|
141
|
+
const lines = [];
|
|
142
|
+
if (message.title) {
|
|
143
|
+
lines.push(message.markdown ? `**${message.title}**` : message.title, "");
|
|
144
|
+
}
|
|
145
|
+
lines.push(message.body);
|
|
146
|
+
const body = lines.join("\n");
|
|
147
|
+
const formatted = message.markdown ? {
|
|
148
|
+
format: "org.matrix.custom.html",
|
|
149
|
+
formatted_body: await import_marked.marked.parse(body, { async: true })
|
|
150
|
+
} : {};
|
|
151
|
+
const txnId = `send-${Date.now()}`;
|
|
152
|
+
const encodedRoomId = encodeURIComponent(this.roomId);
|
|
153
|
+
const url = `${this.homeserverUrl}/_matrix/client/v3/rooms/${encodedRoomId}/send/m.room.message/${txnId}`;
|
|
154
|
+
let response;
|
|
155
|
+
try {
|
|
156
|
+
response = await fetch(url, {
|
|
157
|
+
method: "PUT",
|
|
158
|
+
headers: {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify({
|
|
163
|
+
msgtype: "m.text",
|
|
164
|
+
body,
|
|
165
|
+
...formatted
|
|
166
|
+
})
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const cause = error instanceof Error ? error.cause ?? error.message : error;
|
|
170
|
+
throw new Error(`Element fetch failed: ${JSON.stringify(cause)}`);
|
|
171
|
+
}
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
const responseBody = await response.text();
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Element API returned status ${response.status}: ${responseBody}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// src/providers/mattermost.ts
|
|
182
|
+
var MattermostSender = class {
|
|
183
|
+
name = "Mattermost";
|
|
184
|
+
webhookUrl;
|
|
185
|
+
constructor(config) {
|
|
186
|
+
this.webhookUrl = config.webhookUrl;
|
|
187
|
+
}
|
|
188
|
+
/** Mattermost always renders markdown; escape special chars for plain bodies. */
|
|
189
|
+
escapeMarkdown(text) {
|
|
190
|
+
return text.replace(/([\\`*_{}[\]()#+\-.!|>~])/g, "\\$1");
|
|
191
|
+
}
|
|
192
|
+
async send(message) {
|
|
193
|
+
const summary = message.title ?? message.body;
|
|
194
|
+
const body = message.markdown ? message.body : this.escapeMarkdown(message.body);
|
|
195
|
+
const payload = {
|
|
196
|
+
text: message.title ? `#### ${message.title}` : "",
|
|
197
|
+
attachments: [
|
|
198
|
+
{
|
|
199
|
+
fallback: summary,
|
|
200
|
+
color: "#2eb886",
|
|
201
|
+
text: body
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
};
|
|
205
|
+
const response = await fetch(this.webhookUrl, {
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: { "Content-Type": "application/json" },
|
|
208
|
+
body: JSON.stringify(payload)
|
|
209
|
+
});
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
const body2 = await response.text();
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Mattermost webhook returned status ${response.status}: ${body2}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/providers/slack.ts
|
|
220
|
+
var SlackSender = class {
|
|
221
|
+
name = "Slack";
|
|
222
|
+
webhookUrl;
|
|
223
|
+
constructor(config) {
|
|
224
|
+
this.webhookUrl = config.webhookUrl;
|
|
225
|
+
}
|
|
226
|
+
async send(message) {
|
|
227
|
+
const summary = message.title ?? message.body;
|
|
228
|
+
const blocks = [];
|
|
229
|
+
if (message.title) {
|
|
230
|
+
blocks.push({
|
|
231
|
+
type: "header",
|
|
232
|
+
text: { type: "plain_text", text: message.title }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
blocks.push({ type: "divider" });
|
|
236
|
+
blocks.push({
|
|
237
|
+
type: "section",
|
|
238
|
+
text: {
|
|
239
|
+
type: message.markdown ? "mrkdwn" : "plain_text",
|
|
240
|
+
text: message.body
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
const payload = { text: summary, blocks };
|
|
244
|
+
const response = await fetch(this.webhookUrl, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: { "Content-Type": "application/json" },
|
|
247
|
+
body: JSON.stringify(payload)
|
|
248
|
+
});
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
const body = await response.text();
|
|
251
|
+
throw new Error(
|
|
252
|
+
`Slack webhook returned status ${response.status}: ${body}`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/providers/teams.ts
|
|
259
|
+
var TeamsSender = class {
|
|
260
|
+
name = "Microsoft Teams";
|
|
261
|
+
webhookUrl;
|
|
262
|
+
constructor(config) {
|
|
263
|
+
this.webhookUrl = config.webhookUrl;
|
|
264
|
+
}
|
|
265
|
+
async send(message) {
|
|
266
|
+
const summary = message.title ?? message.body;
|
|
267
|
+
const messageCard = {
|
|
268
|
+
"@type": "MessageCard",
|
|
269
|
+
"@context": "https://schema.org/extensions",
|
|
270
|
+
themeColor: "0078D7",
|
|
271
|
+
summary,
|
|
272
|
+
sections: [
|
|
273
|
+
{
|
|
274
|
+
activityTitle: message.title,
|
|
275
|
+
text: message.body,
|
|
276
|
+
markdown: message.markdown ?? false
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
};
|
|
280
|
+
const response = await fetch(this.webhookUrl, {
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: { "Content-Type": "application/json" },
|
|
283
|
+
body: JSON.stringify(messageCard)
|
|
284
|
+
});
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Teams webhook returned status ${response.status}: ${response.statusText}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/providers/telegram.ts
|
|
294
|
+
var TelegramSender = class {
|
|
295
|
+
name = "Telegram";
|
|
296
|
+
botToken;
|
|
297
|
+
chatId;
|
|
298
|
+
constructor(config) {
|
|
299
|
+
this.botToken = config.botToken;
|
|
300
|
+
this.chatId = config.chatId;
|
|
301
|
+
}
|
|
302
|
+
escapeMarkdown(text) {
|
|
303
|
+
return text.replace(/([*_`[])/g, "\\$1");
|
|
304
|
+
}
|
|
305
|
+
async send(message) {
|
|
306
|
+
const lines = [];
|
|
307
|
+
if (message.markdown) {
|
|
308
|
+
if (message.title) {
|
|
309
|
+
lines.push(`*${this.escapeMarkdown(message.title)}*`, "");
|
|
310
|
+
}
|
|
311
|
+
lines.push(message.body);
|
|
312
|
+
} else {
|
|
313
|
+
if (message.title) lines.push(message.title, "");
|
|
314
|
+
lines.push(message.body);
|
|
315
|
+
}
|
|
316
|
+
const text = lines.join("\n");
|
|
317
|
+
const url = `https://api.telegram.org/bot${this.botToken}/sendMessage`;
|
|
318
|
+
const response = await fetch(url, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: { "Content-Type": "application/json" },
|
|
321
|
+
body: JSON.stringify({
|
|
322
|
+
chat_id: this.chatId,
|
|
323
|
+
text,
|
|
324
|
+
...message.markdown ? { parse_mode: "Markdown" } : {}
|
|
325
|
+
})
|
|
326
|
+
});
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
const body = await response.text();
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Telegram API returned status ${response.status}: ${body}`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/providers/index.ts
|
|
337
|
+
function createSender(config) {
|
|
338
|
+
switch (config.provider) {
|
|
339
|
+
case "slack":
|
|
340
|
+
return new SlackSender(config);
|
|
341
|
+
case "teams":
|
|
342
|
+
return new TeamsSender(config);
|
|
343
|
+
case "mattermost":
|
|
344
|
+
return new MattermostSender(config);
|
|
345
|
+
case "telegram":
|
|
346
|
+
return new TelegramSender(config);
|
|
347
|
+
case "element":
|
|
348
|
+
return new ElementSender(config);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/cli.ts
|
|
353
|
+
var USAGE = `send \u2014 post a structured message to a chat provider
|
|
354
|
+
|
|
355
|
+
Usage:
|
|
356
|
+
send --provider <slack|teams|telegram|element|mattermost> [config] --body <text> [options]
|
|
357
|
+
|
|
358
|
+
Provider config:
|
|
359
|
+
slack | teams | mattermost --webhook-url <url>
|
|
360
|
+
telegram --bot-token <token> --chat-id <id>
|
|
361
|
+
element --homeserver-url <url> --access-token <token> --room-id <id>
|
|
362
|
+
|
|
363
|
+
Message:
|
|
364
|
+
--title <text> Optional heading
|
|
365
|
+
--body <text> Message body
|
|
366
|
+
--markdown Render the body as markdown
|
|
367
|
+
|
|
368
|
+
Examples:
|
|
369
|
+
send --provider slack --webhook-url https://hooks.slack.com/... \\
|
|
370
|
+
--title "Release 1.2" --body "Bug **fixes**" --markdown
|
|
371
|
+
|
|
372
|
+
send --provider telegram --bot-token T --chat-id 42 --title Hi --body "the body"
|
|
373
|
+
`;
|
|
374
|
+
function fail(message) {
|
|
375
|
+
process.stderr.write(`${message}
|
|
376
|
+
`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
async function main() {
|
|
380
|
+
const values = (0, import_command_line_args.default)(optionDefinitions, {
|
|
381
|
+
camelCase: true
|
|
382
|
+
});
|
|
383
|
+
if (values.help || !values.provider) {
|
|
384
|
+
process.stdout.write(USAGE);
|
|
385
|
+
process.exit(values.help ? 0 : 1);
|
|
386
|
+
}
|
|
387
|
+
const config = parseConfig(values);
|
|
388
|
+
const messageResult = messageSchema.safeParse({
|
|
389
|
+
title: values.title,
|
|
390
|
+
body: values.body,
|
|
391
|
+
markdown: values.markdown
|
|
392
|
+
});
|
|
393
|
+
if (!messageResult.success) {
|
|
394
|
+
fail(
|
|
395
|
+
`Invalid message (a body is required):
|
|
396
|
+
${import_zod7.z.prettifyError(messageResult.error)}`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
const sender = createSender(config);
|
|
400
|
+
try {
|
|
401
|
+
await sender.send(messageResult.data);
|
|
402
|
+
process.stdout.write(`Sent to ${sender.name}.
|
|
403
|
+
`);
|
|
404
|
+
} catch (error) {
|
|
405
|
+
fail(`Failed to send to ${sender.name}: ${error.message}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
main().catch((error) => fail(error.message));
|