@assemora/notifications 0.1.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/LICENSE +202 -0
- package/dist/channel.d.ts +44 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +49 -0
- package/dist/channel.js.map +1 -0
- package/dist/commands.d.ts +42 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +135 -0
- package/dist/commands.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs.d.ts +7 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +59 -0
- package/dist/jobs.js.map +1 -0
- package/dist/models.d.ts +91 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +61 -0
- package/dist/models.js.map +1 -0
- package/dist/module.d.ts +31 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +63 -0
- package/dist/module.js.map +1 -0
- package/dist/notification.d.ts +69 -0
- package/dist/notification.d.ts.map +1 -0
- package/dist/notification.js +36 -0
- package/dist/notification.js.map +1 -0
- package/dist/resources.d.ts +49 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +79 -0
- package/dist/resources.js.map +1 -0
- package/dist/telegram.d.ts +13 -0
- package/dist/telegram.d.ts.map +1 -0
- package/dist/telegram.js +94 -0
- package/dist/telegram.js.map +1 -0
- package/package.json +42 -0
package/dist/telegram.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Telegram driver (SPEC.md §81).
|
|
3
|
+
*
|
|
4
|
+
* A bot, a chat id and one method of the Bot API. It is the only file in this
|
|
5
|
+
* repository that knows Telegram exists, and it holds no state beyond its token — the
|
|
6
|
+
* same bargain `s3-storage.ts` makes with a bucket.
|
|
7
|
+
*
|
|
8
|
+
* There is deliberately no `parse_mode`. Telegram then reads the text literally, so a
|
|
9
|
+
* customer called `*Ада*` arrives as those characters and a comment holding an HTML
|
|
10
|
+
* tag cannot close one the message never opened. Formatting would have to be escaped
|
|
11
|
+
* value by value, and a missed escape is not a cosmetic bug: it is a stranger writing
|
|
12
|
+
* markup into a staff channel.
|
|
13
|
+
*/
|
|
14
|
+
import { ConfigurationError } from '@assemora/core';
|
|
15
|
+
import { rejected, unreachable } from './channel.js';
|
|
16
|
+
/**
|
|
17
|
+
* Telegram's own ceiling, in UTF-16 code units, and the message is cut to fit.
|
|
18
|
+
*
|
|
19
|
+
* Refusing instead would be defensible for a message a person wrote; this is a
|
|
20
|
+
* rendered notification, and half a kitchen order is worth more than none. The cut is
|
|
21
|
+
* visible in the message so nobody reads a truncated address as a complete one.
|
|
22
|
+
*/
|
|
23
|
+
const LIMIT = 4096;
|
|
24
|
+
const CUT = '…';
|
|
25
|
+
/**
|
|
26
|
+
* Whether Telegram's refusal is about this address or about this moment.
|
|
27
|
+
*
|
|
28
|
+
* 429 is the rate limit and 5xx is Telegram itself, and both mean "later". Everything
|
|
29
|
+
* else — a chat that does not exist, a bot nobody started, a revoked token — is the
|
|
30
|
+
* same answer however many times it is asked.
|
|
31
|
+
*/
|
|
32
|
+
const isMomentary = (status) => status === 429 || status >= 500;
|
|
33
|
+
const bodyOf = async (response) => {
|
|
34
|
+
try {
|
|
35
|
+
return (await response.json());
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
export const telegram = (options) => {
|
|
42
|
+
if (options.token.trim() === '') {
|
|
43
|
+
throw new ConfigurationError('The Telegram channel was given an empty bot token. Set TELEGRAM_BOT_TOKEN, or leave the channel out until there is one.');
|
|
44
|
+
}
|
|
45
|
+
const api = (options.api ?? 'https://api.telegram.org').replace(/\/+$/, '');
|
|
46
|
+
const send = options.fetch ?? globalThis.fetch;
|
|
47
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
48
|
+
return {
|
|
49
|
+
name: 'telegram',
|
|
50
|
+
async send(address, message) {
|
|
51
|
+
const text = message.text.length > LIMIT
|
|
52
|
+
? `${message.text.slice(0, LIMIT - CUT.length)}${CUT}`
|
|
53
|
+
: message.text;
|
|
54
|
+
let response;
|
|
55
|
+
try {
|
|
56
|
+
response = await send(`${api}/bot${options.token}/sendMessage`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'content-type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
chat_id: address,
|
|
61
|
+
text,
|
|
62
|
+
// A staff channel wants the order, not a preview card of whatever URL the
|
|
63
|
+
// message happens to carry.
|
|
64
|
+
disable_web_page_preview: true,
|
|
65
|
+
}),
|
|
66
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (cause) {
|
|
70
|
+
// A refused connection, a DNS failure, the timeout above. None of them say
|
|
71
|
+
// anything about the address.
|
|
72
|
+
throw unreachable('Telegram could not be reached', {
|
|
73
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (response.ok)
|
|
77
|
+
return;
|
|
78
|
+
const answer = await bodyOf(response);
|
|
79
|
+
// Telegram's own text, which names the reason: "chat not found", "bot was
|
|
80
|
+
// blocked by the user", "Unauthorized". It carries no token and no message body.
|
|
81
|
+
const description = answer.description ?? `HTTP ${response.status}`;
|
|
82
|
+
if (isMomentary(response.status)) {
|
|
83
|
+
throw unreachable(`Telegram refused for now: ${description}`, {
|
|
84
|
+
status: response.status,
|
|
85
|
+
...(answer.parameters?.retry_after === undefined
|
|
86
|
+
? {}
|
|
87
|
+
: { retryAfter: answer.parameters.retry_after }),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
throw rejected(`Telegram refused this chat: ${description}`, { status: response.status });
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=telegram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram.js","sourceRoot":"","sources":["../src/telegram.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@assemora/notifications",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Outbound notifications: what an application announces, to whom, and over which channel (SPEC.md §81)",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/assemora/assemora.git",
|
|
12
|
+
"directory": "packages/notifications"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"!dist/.tsbuildinfo"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@assemora/schema": "0.1.0",
|
|
29
|
+
"@assemora/data": "0.1.0",
|
|
30
|
+
"@assemora/core": "0.1.0",
|
|
31
|
+
"@assemora/resources": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "24.13.3",
|
|
35
|
+
"@assemora/database": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -b tsconfig.build.json",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
40
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
41
|
+
}
|
|
42
|
+
}
|