@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/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
|
+
}
|