@rhizogrip/spore-plugin-telegram 0.8.0 → 0.9.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 +21 -11
- package/package.json +3 -3
- package/src/adapter.js +22 -17
package/README.md
CHANGED
|
@@ -121,6 +121,15 @@ It is then shown `reading data/report.md`, `running git log --oneline -5`,
|
|
|
121
121
|
`source forgejo --path …` — the first line, cut short. Choose it for a chat you would let
|
|
122
122
|
read the commands; the others keep the kinds. A group takes `"details": true` the same way.
|
|
123
123
|
|
|
124
|
+
A bot that serves people who should see their answers and not the machinery — your customers, say —
|
|
125
|
+
takes `"brief": true` beside `people` and `groups`: each chat is then told its spore's state and its
|
|
126
|
+
answers, and nothing of how it runs. `/status` says `working`, `idle`, `paused` or how many messages
|
|
127
|
+
wait — never the model, the spend, the turns or the limits; an answer comes without how the turn
|
|
128
|
+
ended, a turn with none says `no answer this time` and the log says why; a limit reached shows as
|
|
129
|
+
`wrapping up`; and `/extend` is not offered, since what a turn may spend is yours. A person or a
|
|
130
|
+
group takes `"brief"` as well, `false` to be told everything in a brief bot; `details` is refused
|
|
131
|
+
beside it.
|
|
132
|
+
|
|
124
133
|
A chat can be shown a turn more quietly, which is what a group gets unless its entry says
|
|
125
134
|
otherwise: `"show": "react"` puts a 👨💻 on your message when the spore takes it and shows Telegram's
|
|
126
135
|
`typing…` while the turn runs, then takes the 👨💻 off when the answer arrives — or puts a 💔 there
|
|
@@ -143,8 +152,8 @@ refused chat 148392011 (@alice) — to allow it, add "148392011": "<a spore>" to
|
|
|
143
152
|
```
|
|
144
153
|
|
|
145
154
|
which is also how you learn your own user id: start with `"people": {}` and message the bot. A
|
|
146
|
-
group the file does not name is told nothing either, when someone there
|
|
147
|
-
says the `groups` entry to add, its id negative. Where a spore's `replyTo` default is one of this
|
|
155
|
+
group the file does not name is told nothing either, when someone there writes `/status@<the bot>`;
|
|
156
|
+
the log says the `groups` entry to add, its id negative. Where a spore's `replyTo` default is one of this
|
|
148
157
|
bot's chats, that chat is told the same line, once per chat the file does not name, so you hear of
|
|
149
158
|
someone trying the bot without reading its log.
|
|
150
159
|
|
|
@@ -152,11 +161,14 @@ someone trying the bot without reading its log.
|
|
|
152
161
|
|
|
153
162
|
A group has one spore — in a group the bot speaks as itself, so a second spore there is a second
|
|
154
163
|
bot — and `people`, who may address it: user ids, or `"*"` for everyone in the group, written out.
|
|
155
|
-
A message reaches the spore when it
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
A message reaches the spore when it replies to one of the bot's messages, is a command for it —
|
|
165
|
+
`/status`, or `/status@triage_bot` where several bots share the group; a command naming another bot
|
|
166
|
+
is not seen — or mentions the bot (`@triage_bot what failed?`). Telegram hands a bot a mention only
|
|
167
|
+
with its privacy mode off — `/setprivacy` in @BotFather, then the bot added to the group again — or
|
|
168
|
+
as an admin of the group; with neither, the service says so for each of its groups when it starts.
|
|
169
|
+
Anything else said there is not for the spore, and the bot's mention is not part of what the spore
|
|
170
|
+
is told. Someone the entry does not list who addresses the bot gets silence, and the log gets the
|
|
171
|
+
line that would let them in.
|
|
160
172
|
|
|
161
173
|
`/status` is for anyone listed; `/stop`, `/wrap` and `/extend` are for the people the entry names
|
|
162
174
|
by id — not everyone `"*"` lets talk — and anyone else is told so.
|
|
@@ -170,10 +182,8 @@ A group can let its spore hear what is said there that is not to it:
|
|
|
170
182
|
What the people it lists say in the group then reaches the spore as overheard: no turn starts for
|
|
171
183
|
it and no answer is owed, and the next turn the spore runs reads it as what was said around it, the
|
|
172
184
|
latest 100 messages at a time. It is everything they say there, kept in the spore's records and
|
|
173
|
-
read by its next turn, so choose it for a group whose talk the spore should follow.
|
|
174
|
-
bot
|
|
175
|
-
bot added to the group again — or as an admin of the group; with neither, the service says so for
|
|
176
|
-
each group that overhears when it starts.
|
|
185
|
+
read by its next turn, so choose it for a group whose talk the spore should follow. It needs the
|
|
186
|
+
bot's privacy mode off, or the bot an admin of the group, as a mention does.
|
|
177
187
|
|
|
178
188
|
## What it keeps
|
|
179
189
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhizogrip/spore-plugin-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.23.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"test": "vitest run"
|
|
38
38
|
},
|
|
39
39
|
"spore": {
|
|
40
|
-
"works": "^1.
|
|
40
|
+
"works": "^1.23.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
|
@@ -27,8 +27,8 @@ const REACTIONS = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
const FIELDS = ['token', 'people', 'groups', 'react']
|
|
30
|
-
const PERSON_FIELDS = ['spore', 'details', 'show']
|
|
31
|
-
const GROUP_FIELDS = ['spores', 'people', 'details', 'show', 'overhear']
|
|
30
|
+
const PERSON_FIELDS = ['spore', 'details', 'show', 'brief']
|
|
31
|
+
const GROUP_FIELDS = ['spores', 'people', 'details', 'show', 'overhear', 'brief']
|
|
32
32
|
const PERSON = /^\d+$/
|
|
33
33
|
const GROUP = /^-\d+$/
|
|
34
34
|
|
|
@@ -43,6 +43,8 @@ const strays = (path, kind, id, entry, takes) => {
|
|
|
43
43
|
throw invalid(path, `gives ${kind} ${id} ${extra.join(', ')}, which a ${kind} does not take`, `a ${kind} takes ${takes.join(', ')}${bots.length ? `; ${bots.join(', ')} is the bot's, beside people and groups` : ''}`)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const briefOf = (entry) => typeof entry.brief === 'boolean' ? entry.brief : null
|
|
47
|
+
|
|
46
48
|
const flags = (path, kind, id, entry, names) => {
|
|
47
49
|
for (const name of names) {
|
|
48
50
|
if (entry[name] !== undefined && typeof entry[name] !== 'boolean') throw invalid(path, `has a ${kind} ${id} whose ${name} is not true or false`)
|
|
@@ -50,13 +52,14 @@ const flags = (path, kind, id, entry, names) => {
|
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
|
-
* The private chats, `people: { "<person id>": "<spore>" }` or `{ "spore", "details", "show"
|
|
54
|
-
* and the groups, `groups: { "<group id>": { "spores": ["<spore>"], "people": [...],
|
|
55
|
-
* "show", "overhear" } }`. `details` shows a chat what each call does — its
|
|
56
|
-
* than only what kind of call it is, since a Telegram message leaves the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* `overhear` gives the spore what they say
|
|
55
|
+
* The private chats, `people: { "<person id>": "<spore>" }` or `{ "spore", "details", "show",
|
|
56
|
+
* "brief" }`, and the groups, `groups: { "<group id>": { "spores": ["<spore>"], "people": [...],
|
|
57
|
+
* "details", "show", "overhear", "brief" } }`. `details` shows a chat what each call does — its
|
|
58
|
+
* command or path — rather than only what kind of call it is, since a Telegram message leaves the
|
|
59
|
+
* machine; `show` how a turn shows itself there; `brief` the bot's own, for this chat. A group names
|
|
60
|
+
* who may address its spore — user ids, or `*` for everyone in it — and has one spore: in a group a
|
|
61
|
+
* bot speaks as itself, so a second spore is a second bot. `overhear` gives the spore what they say
|
|
62
|
+
* there that is not to it.
|
|
60
63
|
*/
|
|
61
64
|
function routes (raw, path) {
|
|
62
65
|
if (raw.people === undefined && raw.groups === undefined) {
|
|
@@ -73,28 +76,29 @@ function routes (raw, path) {
|
|
|
73
76
|
const shaped = typeof entry === 'object' && entry !== null && !Array.isArray(entry)
|
|
74
77
|
if (shaped) {
|
|
75
78
|
strays(path, 'person', person, entry, PERSON_FIELDS)
|
|
76
|
-
flags(path, 'person', person, entry, ['details'])
|
|
79
|
+
flags(path, 'person', person, entry, ['details', 'brief'])
|
|
77
80
|
}
|
|
78
81
|
const spore = shaped ? entry.spore : entry
|
|
79
82
|
if (typeof spore !== 'string' || !spore) throw invalid(path, `maps person ${person} to something that is not a spore`, 'each person is their id against the name of the spore they reach, or { "spore": "<name>", "details": true, "show": "react" }')
|
|
80
|
-
table.set(person, { spores: [spore], details: shaped && entry.details === true, show: shaped ? entry.show ?? null : null, people: null, overhear: false })
|
|
83
|
+
table.set(person, { spores: [spore], details: shaped && entry.details === true, show: shaped ? entry.show ?? null : null, brief: shaped ? briefOf(entry) : null, people: null, overhear: false })
|
|
81
84
|
}
|
|
82
85
|
for (const [group, entry] of entries('groups')) {
|
|
83
86
|
if (!GROUP.test(group)) throw invalid(path, `names "${group}" among its groups, which is not a group's id`, 'a group\'s id is negative (-100… for a supergroup); a person\'s goes under people')
|
|
84
87
|
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) throw invalid(path, `has a group ${group} that is not { "spores": [...], "people": [...] }`)
|
|
85
88
|
strays(path, 'group', group, entry, GROUP_FIELDS)
|
|
86
|
-
flags(path, 'group', group, entry, ['details', 'overhear'])
|
|
89
|
+
flags(path, 'group', group, entry, ['details', 'overhear', 'brief'])
|
|
87
90
|
if (!Array.isArray(entry.spores) || !entry.spores.length || !entry.spores.every(spore => typeof spore === 'string' && spore)) throw invalid(path, `has a group ${group} with no spores`)
|
|
88
91
|
if (entry.spores.length > 1) throw invalid(path, `gives group ${group} ${entry.spores.length} spores`, 'a bot speaks in a group as itself, so a group has one spore — a second spore there is a second bot')
|
|
89
92
|
if (!Array.isArray(entry.people) || !entry.people.every(who => who === '*' || (typeof who === 'string' && PERSON.test(who)))) {
|
|
90
93
|
throw invalid(path, `has a group ${group} whose people are not user ids or *`, 'people is who may address its spore — ["<user id>", …], or ["*"] for everyone in the group, written out')
|
|
91
94
|
}
|
|
92
|
-
table.set(group, { spores: entry.spores, details: entry.details === true, show: entry.show ?? null, people: entry.people, overhear: entry.overhear === true })
|
|
95
|
+
table.set(group, { spores: entry.spores, details: entry.details === true, show: entry.show ?? null, brief: briefOf(entry), people: entry.people, overhear: entry.overhear === true })
|
|
93
96
|
}
|
|
94
97
|
return table
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
const HELP = 'Anything you write to me in private goes to the spore; in a group, mention me or reply to me. /status says what it is doing; /stop ends the turn it is running and sets aside what you sent that it had not read; /wrap [10m] asks it to wrap up
|
|
100
|
+
const HELP = 'Anything you write to me in private goes to the spore; in a group, mention me or reply to me. /status says what it is doing; /stop ends the turn it is running and sets aside what you sent that it had not read; /wrap [10m] asks it to wrap up'
|
|
101
|
+
const EXTEND = '/extend wall 30m gives it more, as far as its spore.cmk allows'
|
|
98
102
|
|
|
99
103
|
export const TELEGRAM = {
|
|
100
104
|
kind: 'telegram',
|
|
@@ -107,7 +111,7 @@ export const TELEGRAM = {
|
|
|
107
111
|
refusals: ['TELEGRAM_CONFLICT'],
|
|
108
112
|
example: SHAPE,
|
|
109
113
|
routes,
|
|
110
|
-
usage: { help: HELP
|
|
114
|
+
usage: { help: `${HELP}; ${EXTEND}.`, briefHelp: `${HELP}.`, extend: '/extend wall 30m, /extend cost 1.50 or /extend rounds 20' },
|
|
111
115
|
limits: { message: 4096, file: 50 * 1024 * 1024, editEveryMs: 2500, typingEveryMs: 4000 },
|
|
112
116
|
can: { upload: true, remove: true, react: true, typing: true, reply: true },
|
|
113
117
|
showShared: 'react',
|
|
@@ -173,11 +177,12 @@ export function createAdapter ({ telegram, bot, me }) {
|
|
|
173
177
|
return {
|
|
174
178
|
...TELEGRAM,
|
|
175
179
|
|
|
176
|
-
/** Each group
|
|
180
|
+
/** Each group, said when the bot's privacy mode keeps mentions — and what it would overhear — from it. */
|
|
177
181
|
prepare: async (table, log) => {
|
|
178
182
|
if (me.can_read_all_group_messages) return
|
|
179
183
|
for (const [group, { overhear }] of table) {
|
|
180
|
-
if (
|
|
184
|
+
if (Number(group) >= 0) continue
|
|
185
|
+
log(`group ${group}: @${me.username} gets only commands for it and replies to its messages there, no mention${overhear ? ' and nothing to overhear' : ''} — turn its privacy mode off with /setprivacy in @BotFather and add it to the group again, or make it an admin of the group`)
|
|
181
186
|
}
|
|
182
187
|
},
|
|
183
188
|
|