@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/dist/index.js ADDED
@@ -0,0 +1,318 @@
1
+ // src/providers/element.ts
2
+ import { marked } from "marked";
3
+ var ElementSender = class {
4
+ name = "Element";
5
+ homeserverUrl;
6
+ accessToken;
7
+ roomId;
8
+ constructor(config) {
9
+ this.homeserverUrl = config.homeserverUrl;
10
+ this.accessToken = config.accessToken;
11
+ this.roomId = config.roomId;
12
+ }
13
+ async send(message) {
14
+ const lines = [];
15
+ if (message.title) {
16
+ lines.push(message.markdown ? `**${message.title}**` : message.title, "");
17
+ }
18
+ lines.push(message.body);
19
+ const body = lines.join("\n");
20
+ const formatted = message.markdown ? {
21
+ format: "org.matrix.custom.html",
22
+ formatted_body: await marked.parse(body, { async: true })
23
+ } : {};
24
+ const txnId = `send-${Date.now()}`;
25
+ const encodedRoomId = encodeURIComponent(this.roomId);
26
+ const url = `${this.homeserverUrl}/_matrix/client/v3/rooms/${encodedRoomId}/send/m.room.message/${txnId}`;
27
+ let response;
28
+ try {
29
+ response = await fetch(url, {
30
+ method: "PUT",
31
+ headers: {
32
+ "Content-Type": "application/json",
33
+ Authorization: `Bearer ${this.accessToken}`
34
+ },
35
+ body: JSON.stringify({
36
+ msgtype: "m.text",
37
+ body,
38
+ ...formatted
39
+ })
40
+ });
41
+ } catch (error) {
42
+ const cause = error instanceof Error ? error.cause ?? error.message : error;
43
+ throw new Error(`Element fetch failed: ${JSON.stringify(cause)}`);
44
+ }
45
+ if (!response.ok) {
46
+ const responseBody = await response.text();
47
+ throw new Error(
48
+ `Element API returned status ${response.status}: ${responseBody}`
49
+ );
50
+ }
51
+ }
52
+ };
53
+
54
+ // src/providers/mattermost.ts
55
+ var MattermostSender = class {
56
+ name = "Mattermost";
57
+ webhookUrl;
58
+ constructor(config) {
59
+ this.webhookUrl = config.webhookUrl;
60
+ }
61
+ /** Mattermost always renders markdown; escape special chars for plain bodies. */
62
+ escapeMarkdown(text) {
63
+ return text.replace(/([\\`*_{}[\]()#+\-.!|>~])/g, "\\$1");
64
+ }
65
+ async send(message) {
66
+ const summary = message.title ?? message.body;
67
+ const body = message.markdown ? message.body : this.escapeMarkdown(message.body);
68
+ const payload = {
69
+ text: message.title ? `#### ${message.title}` : "",
70
+ attachments: [
71
+ {
72
+ fallback: summary,
73
+ color: "#2eb886",
74
+ text: body
75
+ }
76
+ ]
77
+ };
78
+ const response = await fetch(this.webhookUrl, {
79
+ method: "POST",
80
+ headers: { "Content-Type": "application/json" },
81
+ body: JSON.stringify(payload)
82
+ });
83
+ if (!response.ok) {
84
+ const body2 = await response.text();
85
+ throw new Error(
86
+ `Mattermost webhook returned status ${response.status}: ${body2}`
87
+ );
88
+ }
89
+ }
90
+ };
91
+
92
+ // src/providers/slack.ts
93
+ var SlackSender = class {
94
+ name = "Slack";
95
+ webhookUrl;
96
+ constructor(config) {
97
+ this.webhookUrl = config.webhookUrl;
98
+ }
99
+ async send(message) {
100
+ const summary = message.title ?? message.body;
101
+ const blocks = [];
102
+ if (message.title) {
103
+ blocks.push({
104
+ type: "header",
105
+ text: { type: "plain_text", text: message.title }
106
+ });
107
+ }
108
+ blocks.push({ type: "divider" });
109
+ blocks.push({
110
+ type: "section",
111
+ text: {
112
+ type: message.markdown ? "mrkdwn" : "plain_text",
113
+ text: message.body
114
+ }
115
+ });
116
+ const payload = { text: summary, blocks };
117
+ const response = await fetch(this.webhookUrl, {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify(payload)
121
+ });
122
+ if (!response.ok) {
123
+ const body = await response.text();
124
+ throw new Error(
125
+ `Slack webhook returned status ${response.status}: ${body}`
126
+ );
127
+ }
128
+ }
129
+ };
130
+
131
+ // src/providers/teams.ts
132
+ var TeamsSender = class {
133
+ name = "Microsoft Teams";
134
+ webhookUrl;
135
+ constructor(config) {
136
+ this.webhookUrl = config.webhookUrl;
137
+ }
138
+ async send(message) {
139
+ const summary = message.title ?? message.body;
140
+ const messageCard = {
141
+ "@type": "MessageCard",
142
+ "@context": "https://schema.org/extensions",
143
+ themeColor: "0078D7",
144
+ summary,
145
+ sections: [
146
+ {
147
+ activityTitle: message.title,
148
+ text: message.body,
149
+ markdown: message.markdown ?? false
150
+ }
151
+ ]
152
+ };
153
+ const response = await fetch(this.webhookUrl, {
154
+ method: "POST",
155
+ headers: { "Content-Type": "application/json" },
156
+ body: JSON.stringify(messageCard)
157
+ });
158
+ if (!response.ok) {
159
+ throw new Error(
160
+ `Teams webhook returned status ${response.status}: ${response.statusText}`
161
+ );
162
+ }
163
+ }
164
+ };
165
+
166
+ // src/providers/telegram.ts
167
+ var TelegramSender = class {
168
+ name = "Telegram";
169
+ botToken;
170
+ chatId;
171
+ constructor(config) {
172
+ this.botToken = config.botToken;
173
+ this.chatId = config.chatId;
174
+ }
175
+ escapeMarkdown(text) {
176
+ return text.replace(/([*_`[])/g, "\\$1");
177
+ }
178
+ async send(message) {
179
+ const lines = [];
180
+ if (message.markdown) {
181
+ if (message.title) {
182
+ lines.push(`*${this.escapeMarkdown(message.title)}*`, "");
183
+ }
184
+ lines.push(message.body);
185
+ } else {
186
+ if (message.title) lines.push(message.title, "");
187
+ lines.push(message.body);
188
+ }
189
+ const text = lines.join("\n");
190
+ const url = `https://api.telegram.org/bot${this.botToken}/sendMessage`;
191
+ const response = await fetch(url, {
192
+ method: "POST",
193
+ headers: { "Content-Type": "application/json" },
194
+ body: JSON.stringify({
195
+ chat_id: this.chatId,
196
+ text,
197
+ ...message.markdown ? { parse_mode: "Markdown" } : {}
198
+ })
199
+ });
200
+ if (!response.ok) {
201
+ const body = await response.text();
202
+ throw new Error(
203
+ `Telegram API returned status ${response.status}: ${body}`
204
+ );
205
+ }
206
+ }
207
+ };
208
+
209
+ // src/providers/index.ts
210
+ var PROVIDERS = [
211
+ "slack",
212
+ "teams",
213
+ "mattermost",
214
+ "telegram",
215
+ "element"
216
+ ];
217
+ function createSender(config) {
218
+ switch (config.provider) {
219
+ case "slack":
220
+ return new SlackSender(config);
221
+ case "teams":
222
+ return new TeamsSender(config);
223
+ case "mattermost":
224
+ return new MattermostSender(config);
225
+ case "telegram":
226
+ return new TelegramSender(config);
227
+ case "element":
228
+ return new ElementSender(config);
229
+ }
230
+ }
231
+
232
+ // src/schema/element.ts
233
+ import { z } from "zod";
234
+ var elementConfigSchema = z.object({
235
+ provider: z.literal("element"),
236
+ homeserverUrl: z.url(),
237
+ accessToken: z.string(),
238
+ roomId: z.string()
239
+ });
240
+
241
+ // src/schema/mattermost.ts
242
+ import { z as z2 } from "zod";
243
+ var mattermostConfigSchema = z2.object({
244
+ provider: z2.literal("mattermost"),
245
+ webhookUrl: z2.url()
246
+ });
247
+
248
+ // src/schema/sender.ts
249
+ import { z as z6 } from "zod";
250
+
251
+ // src/schema/slack.ts
252
+ import { z as z3 } from "zod";
253
+ var slackConfigSchema = z3.object({
254
+ provider: z3.literal("slack"),
255
+ webhookUrl: z3.url()
256
+ });
257
+
258
+ // src/schema/teams.ts
259
+ import { z as z4 } from "zod";
260
+ var teamsConfigSchema = z4.object({
261
+ provider: z4.literal("teams"),
262
+ webhookUrl: z4.url()
263
+ });
264
+
265
+ // src/schema/telegram.ts
266
+ import { z as z5 } from "zod";
267
+ var telegramConfigSchema = z5.object({
268
+ provider: z5.literal("telegram"),
269
+ botToken: z5.string(),
270
+ chatId: z5.string()
271
+ });
272
+
273
+ // src/schema/sender.ts
274
+ var messageSchema = z6.object({
275
+ title: z6.string().optional(),
276
+ body: z6.string(),
277
+ /** Render the body as markdown in the provider's native format. */
278
+ markdown: z6.boolean().optional()
279
+ });
280
+ var senderConfigSchema = z6.discriminatedUnion("provider", [
281
+ slackConfigSchema,
282
+ teamsConfigSchema,
283
+ mattermostConfigSchema,
284
+ telegramConfigSchema,
285
+ elementConfigSchema
286
+ ]);
287
+ var sendInputSchema = z6.intersection(
288
+ senderConfigSchema,
289
+ messageSchema
290
+ );
291
+ var providerConfigSchemas = {
292
+ slack: slackConfigSchema,
293
+ teams: teamsConfigSchema,
294
+ mattermost: mattermostConfigSchema,
295
+ telegram: telegramConfigSchema,
296
+ element: elementConfigSchema
297
+ };
298
+
299
+ // src/index.ts
300
+ async function send(input) {
301
+ const { title, body, markdown, ...config } = input;
302
+ const message = { title, body, markdown };
303
+ await createSender(config).send(message);
304
+ }
305
+ export {
306
+ PROVIDERS,
307
+ createSender,
308
+ elementConfigSchema,
309
+ mattermostConfigSchema,
310
+ messageSchema,
311
+ providerConfigSchemas,
312
+ send,
313
+ sendInputSchema,
314
+ senderConfigSchema,
315
+ slackConfigSchema,
316
+ teamsConfigSchema,
317
+ telegramConfigSchema
318
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@amirhosseinnouri/send",
3
+ "version": "1.0.0-2",
4
+ "description": "Send a structured message to chat providers (Slack, Teams, Telegram, Element, Mattermost) from code or the CLI.",
5
+ "type": "module",
6
+ "bin": {
7
+ "send": "dist/cli.cjs"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "NODE_ENV=production tsup",
21
+ "test": "bun test",
22
+ "check": "biome check .",
23
+ "check:fix": "biome check --write ."
24
+ },
25
+ "keywords": [
26
+ "slack",
27
+ "teams",
28
+ "telegram",
29
+ "element",
30
+ "matrix",
31
+ "mattermost",
32
+ "webhook",
33
+ "notification",
34
+ "cli"
35
+ ],
36
+ "license": "MIT",
37
+ "dependencies": {
38
+ "command-line-args": "^6.0.2",
39
+ "marked": "^18.0.2",
40
+ "zod": "^4.1.8"
41
+ },
42
+ "devDependencies": {
43
+ "@biomejs/biome": "2.3.8",
44
+ "@types/bun": "latest",
45
+ "@types/command-line-args": "^5.2.3",
46
+ "@types/node": "^25.6.0",
47
+ "tsup": "^8.5.1",
48
+ "typescript": "^5"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }