@rhizogrip/spore-plugin-telegram 0.5.0 → 0.6.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/README.md +12 -0
- package/package.json +3 -3
- package/src/adapter.js +29 -10
- package/src/api.js +6 -0
package/README.md
CHANGED
|
@@ -111,6 +111,18 @@ It is then shown `reading data/report.md`, `running git log --oneline -5`,
|
|
|
111
111
|
`source forgejo --path …` — the first line, cut short. Choose it for a chat you would let
|
|
112
112
|
read the commands; the others keep the kinds.
|
|
113
113
|
|
|
114
|
+
A chat can be shown a turn more quietly, which is what a group gets unless its entry says
|
|
115
|
+
otherwise: `"show": "react"` puts a 🫡 on your message when the spore takes it and shows Telegram's
|
|
116
|
+
`typing…` while the turn runs, then takes the 🫡 off when the answer arrives — or puts a 💔 there
|
|
117
|
+
when the turn ended without one. `"show": "working"` is the `working…` message, what a private
|
|
118
|
+
chat gets; `"show": "both"` is both. The bot's two emoji are its own to choose, from the ones
|
|
119
|
+
Telegram lets a bot react with (✅ and ❌ are not among them; the log lists them when one is refused):
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{ "token": "${OPS_BOT_TOKEN}", "react": { "taken": "👀", "failed": "🤷" },
|
|
123
|
+
"chats": { "-1001234567": { "spore": "triage", "show": "react" } } }
|
|
124
|
+
```
|
|
125
|
+
|
|
114
126
|
Two chats may point at one spore and then share its conversation. A reply goes to the
|
|
115
127
|
chat that asked; a turn nobody in a chat started goes to the spore's default.
|
|
116
128
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhizogrip/spore-plugin-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Telegram for spores: a bot of yours in chats, each chat reaching its spore, run as a service",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Christophe Le Bars",
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"marked": "^18.0.11"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@rhizogrip/spore": "^1.
|
|
34
|
+
"@rhizogrip/spore": "^1.18.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"test": "vitest run"
|
|
38
38
|
},
|
|
39
39
|
"spore": {
|
|
40
|
-
"works": "^1.
|
|
40
|
+
"works": "^1.18.0",
|
|
41
41
|
"services": {
|
|
42
42
|
"telegram": {
|
|
43
43
|
"what": "a bot of yours in Telegram chats, each chat reaching its spore",
|
package/src/adapter.js
CHANGED
|
@@ -13,14 +13,27 @@ const NOT_MODIFIED = /message is not modified/
|
|
|
13
13
|
const CANNOT_PARSE = /can't parse entities/i
|
|
14
14
|
const SHAPE = '{ "token": "${TRIAGE_BOT_TOKEN}", "chats": { "<chat id>": "<spore>" } }' // eslint-disable-line no-template-curly-in-string
|
|
15
15
|
|
|
16
|
+
/** What a bot may react with: `ReactionTypeEmoji` in the Bot API. */
|
|
17
|
+
const REACTIONS = [
|
|
18
|
+
'❤', '👍', '👎', '🔥', '🥰', '👏', '😁', '🤔', '🤯', '😱',
|
|
19
|
+
'🤬', '😢', '🎉', '🤩', '🤮', '💩', '🙏', '👌', '🕊', '🤡',
|
|
20
|
+
'🥱', '🥴', '😍', '🐳', '❤🔥', '🌚', '🌭', '💯', '🤣', '⚡',
|
|
21
|
+
'🍌', '🏆', '💔', '🤨', '😐', '🍓', '🍾', '💋', '🖕', '😈',
|
|
22
|
+
'😴', '😭', '🤓', '👻', '👨💻', '👀', '🎃', '🙈', '😇', '😨',
|
|
23
|
+
'🤝', '✍', '🤗', '🫡', '🎅', '🎄', '☃', '💅', '🤪', '🗿',
|
|
24
|
+
'🆒', '💘', '🙉', '🦄', '😘', '💊', '🙊', '😎', '👾', '🤷♂',
|
|
25
|
+
'🤷', '🤷♀', '😡'
|
|
26
|
+
]
|
|
27
|
+
|
|
16
28
|
const said = (err) => [err.message, err.detail].filter(Boolean).join(' — ')
|
|
17
29
|
|
|
18
30
|
const invalid = (path, problem, detail) => new SporeError(`${path} ${problem}`, { type: 'TELEGRAM_BOT_INVALID', detail })
|
|
19
31
|
|
|
20
32
|
/**
|
|
21
|
-
* `{ "<chat id>": "<spore>" }`, or `{ "<chat id>": { "spore": "<spore>", "details": true
|
|
22
|
-
* chat shown what each call does — its command or path — rather than
|
|
23
|
-
* is
|
|
33
|
+
* `{ "<chat id>": "<spore>" }`, or `{ "<chat id>": { "spore": "<spore>", "details": true, "show":
|
|
34
|
+
* "react" } }`: `details` for a chat shown what each call does — its command or path — rather than
|
|
35
|
+
* only what kind of call it is, since a Telegram message leaves the machine; `show` for how a turn
|
|
36
|
+
* shows itself there.
|
|
24
37
|
*/
|
|
25
38
|
function routes (raw, path) {
|
|
26
39
|
if (typeof raw.chats !== 'object' || raw.chats === null || Array.isArray(raw.chats)) {
|
|
@@ -29,11 +42,11 @@ function routes (raw, path) {
|
|
|
29
42
|
const table = new Map()
|
|
30
43
|
for (const [chat, entry] of Object.entries(raw.chats)) {
|
|
31
44
|
const spore = typeof entry === 'string' ? entry : entry?.spore
|
|
32
|
-
const extra = typeof entry === 'object' && entry !== null ? Object.keys(entry).filter(key => !['spore', 'details'].includes(key)) : []
|
|
45
|
+
const extra = typeof entry === 'object' && entry !== null ? Object.keys(entry).filter(key => !['spore', 'details', 'show'].includes(key)) : []
|
|
33
46
|
if (typeof spore !== 'string' || !spore || extra.length || (typeof entry === 'object' && entry.details !== undefined && typeof entry.details !== 'boolean')) {
|
|
34
|
-
throw invalid(path, `maps ${chat} to something that is not a spore`, 'each chat is a chat id against the name of a spore it may reach, or { "spore": "<name>", "details": true }')
|
|
47
|
+
throw invalid(path, `maps ${chat} to something that is not a spore`, 'each chat is a chat id against the name of a spore it may reach, or { "spore": "<name>", "details": true, "show": "react" }')
|
|
35
48
|
}
|
|
36
|
-
table.set(chat, { spores: [spore], details: typeof entry === 'object' && entry.details === true })
|
|
49
|
+
table.set(chat, { spores: [spore], details: typeof entry === 'object' && entry.details === true, show: typeof entry === 'object' ? entry.show ?? null : null })
|
|
37
50
|
}
|
|
38
51
|
return table
|
|
39
52
|
}
|
|
@@ -44,7 +57,7 @@ export const TELEGRAM = {
|
|
|
44
57
|
kind: 'telegram',
|
|
45
58
|
label: 'Telegram',
|
|
46
59
|
noun: 'chat',
|
|
47
|
-
fields: ['token', 'chats'],
|
|
60
|
+
fields: ['token', 'chats', 'react'],
|
|
48
61
|
credentials: ['token'],
|
|
49
62
|
stateFields: [],
|
|
50
63
|
sourceVariables: [],
|
|
@@ -52,8 +65,9 @@ export const TELEGRAM = {
|
|
|
52
65
|
example: SHAPE,
|
|
53
66
|
routes,
|
|
54
67
|
usage: { help: HELP, extend: '/extend wall 30m, /extend cost 1.50 or /extend rounds 20' },
|
|
55
|
-
limits: { message: 4096, file: 50 * 1024 * 1024, editEveryMs: 2500 },
|
|
56
|
-
can: { upload: true, remove: true },
|
|
68
|
+
limits: { message: 4096, file: 50 * 1024 * 1024, editEveryMs: 2500, typingEveryMs: 4000 },
|
|
69
|
+
can: { upload: true, remove: true, react: true, typing: true },
|
|
70
|
+
reactions: REACTIONS,
|
|
57
71
|
hint (err, { file, bot }) {
|
|
58
72
|
if (err.type === 'TELEGRAM_NO_BOT') {
|
|
59
73
|
const variable = `${bot.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_BOT_TOKEN`
|
|
@@ -81,6 +95,7 @@ export function normalize (update, bot) {
|
|
|
81
95
|
message: message.message_id,
|
|
82
96
|
person: { id: String(message.from.id), name: `${[first, last].filter(Boolean).join(' ')}${username ? ` (@${username})` : ''}`, username },
|
|
83
97
|
where: message.chat.type === 'private' ? 'a private chat on Telegram' : `the Telegram group "${message.chat.title}"`,
|
|
98
|
+
shared: message.chat.type !== 'private',
|
|
84
99
|
text,
|
|
85
100
|
command: command ? { verb: command[1].toLowerCase(), args: command[3] ?? '' } : null,
|
|
86
101
|
addresses: null,
|
|
@@ -146,6 +161,10 @@ export function createAdapter ({ telegram, bot }) {
|
|
|
146
161
|
|
|
147
162
|
remove: (conversation, note) => telegram.remove(Number(conversation), note),
|
|
148
163
|
|
|
149
|
-
upload: (conversation, file, caption) => telegram.document(Number(conversation), file, caption)
|
|
164
|
+
upload: (conversation, file, caption) => telegram.document(Number(conversation), file, caption),
|
|
165
|
+
|
|
166
|
+
react: (conversation, message, emoji) => telegram.react(Number(conversation), message, emoji),
|
|
167
|
+
|
|
168
|
+
typing: (conversation) => telegram.typing(Number(conversation))
|
|
150
169
|
}
|
|
151
170
|
}
|
package/src/api.js
CHANGED
|
@@ -64,6 +64,12 @@ export function createTelegram (token, { fetchImpl = fetch, base = BASE } = {})
|
|
|
64
64
|
|
|
65
65
|
remove: (chat, message) => call('deleteMessage', { chat_id: chat, message_id: message }),
|
|
66
66
|
|
|
67
|
+
/** `emoji` on the message as the bot's one reaction, or none when it is null. */
|
|
68
|
+
react: (chat, message, emoji) =>
|
|
69
|
+
call('setMessageReaction', { chat_id: chat, message_id: message, reaction: emoji === null ? [] : [{ type: 'emoji', emoji }] }),
|
|
70
|
+
|
|
71
|
+
typing: (chat) => call('sendChatAction', { chat_id: chat, action: 'typing' }),
|
|
72
|
+
|
|
67
73
|
document: (chat, { path, mime }, caption) => {
|
|
68
74
|
const form = new FormData()
|
|
69
75
|
form.append('chat_id', String(chat))
|