halbot 1994.1.8 → 1995.1.3
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 +0 -4
- package/bin/halbot.mjs +1 -1
- package/index.mjs +55 -64
- package/lib/hal.mjs +215 -946
- package/package.json +6 -6
- package/pipeline/010_broca.mjs +261 -0
- package/pipeline/020_cmd.mjs +83 -0
- package/pipeline/030_echo.mjs +70 -0
- package/pipeline/040_help.mjs +46 -0
- package/pipeline/050_auth.mjs +30 -0
- package/pipeline/060_config.mjs +86 -0
- package/pipeline/070_collect.mjs +115 -0
- package/pipeline/080_history.mjs +112 -0
- package/pipeline/090_ai.mjs +74 -0
- package/pipeline/100_chat.mjs +55 -0
- package/skills/-8845_thread.mjs +0 -185
- package/skills/10_ai.mjs +0 -97
- package/skills/20_instant.mjs +0 -25
- package/skills/30_wording.mjs +0 -71
- package/skills/40_dream.mjs +0 -61
- package/skills/50_prompt.mjs +0 -60
- package/skills/60_prepare.mjs +0 -61
- package/skills/70_chat.mjs +0 -101
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halbot",
|
|
3
3
|
"description": "Just another AI powered Telegram bot, which is simple design, easy to use, extendable and fun.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1995.1.3",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/Leask/halbot",
|
|
7
7
|
"type": "module",
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
33
33
|
"@ffprobe-installer/ffprobe": "^2.1.2",
|
|
34
|
-
"@google/
|
|
34
|
+
"@google-cloud/discoveryengine": "^2.5.2",
|
|
35
|
+
"@google/genai": "^1.33.0",
|
|
35
36
|
"@mozilla/readability": "^0.6.0",
|
|
36
37
|
"fluent-ffmpeg": "^2.1.3",
|
|
37
38
|
"google-gax": "^5.0.6",
|
|
38
39
|
"ioredis": "^5.8.2",
|
|
39
|
-
"
|
|
40
|
-
"jsdom": "^27.2.0",
|
|
40
|
+
"jsdom": "^27.3.0",
|
|
41
41
|
"lorem-ipsum": "^2.0.8",
|
|
42
42
|
"mime": "^4.1.0",
|
|
43
43
|
"mysql2": "^3.15.3",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"pg": "^8.16.3",
|
|
47
47
|
"pgvector": "^0.2.1",
|
|
48
48
|
"telegraf": "^4.16.3",
|
|
49
|
-
"tesseract.js": "^
|
|
50
|
-
"utilitas": "^
|
|
49
|
+
"tesseract.js": "^7.0.0",
|
|
50
|
+
"utilitas": "^2001.1.77",
|
|
51
51
|
"youtube-transcript": "^1.2.1"
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { alan, bot, hal, uoid, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const _name = 'Broca';
|
|
4
|
+
const [PRIVATE_LIMIT, GROUP_LIMIT] = [60 / 60, 60 / 20].map(x => x * 1000);
|
|
5
|
+
const log = (c, o) => utilitas.log(c, _name, { time: 1, ...o || {} });
|
|
6
|
+
const getKey = s => s?.toLowerCase?.()?.startsWith?.('http') ? 'url' : 'source';
|
|
7
|
+
const isMarkdownError = e => e?.description?.includes?.("can't parse entities");
|
|
8
|
+
|
|
9
|
+
const [CALLBACK_LIMIT, parse_mode] = [30, bot.parse_mode];
|
|
10
|
+
|
|
11
|
+
const KNOWN_UPDATE_TYPES = [
|
|
12
|
+
'callback_query', 'channel_post', 'edited_message', 'message',
|
|
13
|
+
'my_chat_member', // 'inline_query',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const getExtra = (ctx, options) => {
|
|
17
|
+
const resp = {
|
|
18
|
+
reply_parameters: { message_id: ctx._.messageId },
|
|
19
|
+
disable_notification: !!ctx._.done.length, ...options || {},
|
|
20
|
+
};
|
|
21
|
+
resp.reply_markup || (resp.reply_markup = {});
|
|
22
|
+
if (options?.buttons?.length) {
|
|
23
|
+
resp.reply_markup.inline_keyboard = options?.buttons.map(row =>
|
|
24
|
+
utilitas.ensureArray(row).map(button => {
|
|
25
|
+
if (button.url) {
|
|
26
|
+
return { text: button.label, url: button.url };
|
|
27
|
+
} else if (button.text) {
|
|
28
|
+
const id = uoid.fakeUuid(button.text);
|
|
29
|
+
ctx._.session.callback.push({ id, ...button });
|
|
30
|
+
return {
|
|
31
|
+
text: button.label,
|
|
32
|
+
callback_data: JSON.stringify({ callback: id }),
|
|
33
|
+
};
|
|
34
|
+
} else {
|
|
35
|
+
utilitas.throwError('Invalid button markup.');
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
} else if (options?.keyboards) {
|
|
40
|
+
if (options.keyboards.length) {
|
|
41
|
+
resp.reply_markup.keyboard = options?.keyboards.map(utilitas.ensureArray);
|
|
42
|
+
} else { resp.reply_markup.remove_keyboard = true; }
|
|
43
|
+
}
|
|
44
|
+
return resp;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const resp = async (ctx, text, md, extra) => {
|
|
48
|
+
// if (ctx._.type === 'inline_query') {
|
|
49
|
+
// return await ctx.answerInlineQuery([{}, {}]);
|
|
50
|
+
// }
|
|
51
|
+
let resp;
|
|
52
|
+
if (md) {
|
|
53
|
+
try {
|
|
54
|
+
resp = await (extra?.reply_parameters?.message_id
|
|
55
|
+
? ctx.replyWithMarkdown(text, { parse_mode, ...extra })
|
|
56
|
+
: ctx.sendMessage(text, { parse_mode, ...extra }));
|
|
57
|
+
} catch (err) { // utilitas.throwError('Error sending message.');
|
|
58
|
+
isMarkdownError(err) || log(err);
|
|
59
|
+
await ctx.timeout();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
resp || (resp = await utilitas.ignoreErrFunc(
|
|
63
|
+
async () => await (extra?.reply_parameters?.message_id
|
|
64
|
+
? ctx.reply(text, extra) : ctx.sendMessage(text, extra)
|
|
65
|
+
), hal.logOptions
|
|
66
|
+
));
|
|
67
|
+
resp && ctx._.done.push(resp);
|
|
68
|
+
return resp;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const replyWith = async (ctx, func, src, options) => ctx._.done.push(
|
|
72
|
+
await ctx[func](Array.isArray(src) ? src.map(x => ({
|
|
73
|
+
type: x.type || 'photo', media: { [getKey(x.src)]: x.src },
|
|
74
|
+
})) : { [getKey(src)]: src }, ctx.getExtra(options))
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const edit = async (ctx, lastMsgId, text, md, extra) => {
|
|
78
|
+
let resp;
|
|
79
|
+
if (md) {
|
|
80
|
+
try {
|
|
81
|
+
resp = await ctx.telegram.editMessageText(
|
|
82
|
+
ctx._.chatId, lastMsgId, '', text, { parse_mode, ...extra }
|
|
83
|
+
);
|
|
84
|
+
} catch (err) { // utilitas.throwError('Error editing message.');
|
|
85
|
+
isMarkdownError(err) || log(err);
|
|
86
|
+
await ctx.timeout();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
resp || (resp = await utilitas.ignoreErrFunc(async (
|
|
90
|
+
) => await ctx.telegram.editMessageText(
|
|
91
|
+
ctx._.chatId, lastMsgId, '', text, extra
|
|
92
|
+
), hal.logOptions));
|
|
93
|
+
resp && ctx._.done.push(resp);
|
|
94
|
+
return resp;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const sessionGet = async chatId => {
|
|
98
|
+
const session = await alan.getSession(chatId) || {};
|
|
99
|
+
session.callback || (session.callback = []);
|
|
100
|
+
session.config || (session.config = {});
|
|
101
|
+
return session;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const sessionSet = async (chatId, session) => {
|
|
105
|
+
while (session?.callback?.length > CALLBACK_LIMIT) {
|
|
106
|
+
session.callback.shift();
|
|
107
|
+
}
|
|
108
|
+
return await alan.setSession(chatId, session);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const ctxExt = ctx => {
|
|
112
|
+
ctx.timeout = async () => await utilitas.timeout(ctx._.limit);
|
|
113
|
+
ctx.collect = (content, type, options) => type ? ctx._.collected.push(
|
|
114
|
+
{ type, content }
|
|
115
|
+
) : (ctx._.text = [
|
|
116
|
+
(options?.refresh ? '' : ctx._.text) || '', content || ''
|
|
117
|
+
].filter(x => x.length).join('\n\n'));
|
|
118
|
+
ctx.hello = str => {
|
|
119
|
+
str = str || ctx._.session?.config?.hello || hal._.hello;
|
|
120
|
+
ctx.collect(str, null, { refresh: true });
|
|
121
|
+
return str;
|
|
122
|
+
};
|
|
123
|
+
ctx.getExtra = (options) => getExtra(ctx, options);
|
|
124
|
+
ctx.resp = async (text, md, extra) => await resp(ctx, text, md, extra);
|
|
125
|
+
ctx.edit = async (lastMsgId, text, md, extra) =>
|
|
126
|
+
await edit(ctx, lastMsgId, text, md, extra);
|
|
127
|
+
ctx.ok = async (message, options) => {
|
|
128
|
+
let pages = bot.paging(message, options);
|
|
129
|
+
const extra = ctx.getExtra(options);
|
|
130
|
+
const [pageIds, pageMap] = [[], {}];
|
|
131
|
+
options?.pageBreak || ctx._.done.filter(x => x).map(x => {
|
|
132
|
+
pageMap[x?.message_id] || (pageIds.push(x?.message_id));
|
|
133
|
+
pageMap[x?.message_id] = x;
|
|
134
|
+
});
|
|
135
|
+
for (let i in pages) {
|
|
136
|
+
const lastPage = ~~i === pages.length - 1;
|
|
137
|
+
const shouldExtra = options?.lastMessageId || lastPage;
|
|
138
|
+
if (options?.onProgress && !options?.lastMessageId
|
|
139
|
+
&& pageMap[pageIds[~~i]]?.text === pages[i]) { continue; }
|
|
140
|
+
if (options?.onProgress && !pageIds[~~i]) { // progress: new page, reply text
|
|
141
|
+
await ctx.resp(pages[i], false, extra);
|
|
142
|
+
} else if (options?.onProgress) { // progress: ongoing, edit text
|
|
143
|
+
await ctx.edit(
|
|
144
|
+
pageIds[~~i], pages[i], false, shouldExtra ? extra : {}
|
|
145
|
+
);
|
|
146
|
+
} else if (options?.lastMessageId || pageIds[~~i]) { // progress: final, edit markdown
|
|
147
|
+
await ctx.edit(
|
|
148
|
+
options?.lastMessageId || pageIds[~~i],
|
|
149
|
+
pages[i], true, shouldExtra ? extra : {}
|
|
150
|
+
);
|
|
151
|
+
} else { // never progress, reply markdown
|
|
152
|
+
await ctx.resp(pages[i], true, extra);
|
|
153
|
+
}
|
|
154
|
+
await ctx.timeout();
|
|
155
|
+
}
|
|
156
|
+
return ctx._.done;
|
|
157
|
+
};
|
|
158
|
+
ctx.err = async (m, opts) => {
|
|
159
|
+
log(m);
|
|
160
|
+
return await ctx.ok(`⚠️ ${m?.message || m} `, opts);
|
|
161
|
+
};
|
|
162
|
+
ctx.shouldReply = async text => {
|
|
163
|
+
const should = utilitas.insensitiveHas(hal._?.chatType, ctx._.chatType)
|
|
164
|
+
|| ctx._.session?.config?.chatty;
|
|
165
|
+
text = utilitas.isSet(text, true) ? (text || '') : '';
|
|
166
|
+
if (!should || !text) { return should; }
|
|
167
|
+
return await ctx.ok(text);
|
|
168
|
+
};
|
|
169
|
+
ctx.finish = () => ctx._.done.unshift(null);
|
|
170
|
+
ctx.complete = async (options) => await ctx.ok(hal.CHECK, options);
|
|
171
|
+
ctx.json = async (obj, options) => await ctx.ok(hal.json(obj), options);
|
|
172
|
+
ctx.list = async (list, options) => await ctx.ok(hal.uList(list), options);
|
|
173
|
+
ctx.audio = async (s, o) => await replyWith(ctx, 'replyWithAudio', s, o);
|
|
174
|
+
ctx.image = async (s, o) => await replyWith(ctx, 'replyWithPhoto', s, o);
|
|
175
|
+
ctx.video = async (s, o) => await replyWith(ctx, 'replyWithVideo', s, o);
|
|
176
|
+
ctx.media = async (s, o) => await replyWith(ctx, 'replyWithMediaGroup', s, o);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const action = async (ctx, next) => {
|
|
180
|
+
// log event
|
|
181
|
+
const e = `Event: ${ctx.update.update_id} => ${JSON.stringify(ctx.update)} `;
|
|
182
|
+
process.stdout.write(`[${_name.toUpperCase()} ${new Date().toISOString()}] ${e} \n`);
|
|
183
|
+
log(e);
|
|
184
|
+
// init ctx methods
|
|
185
|
+
ctxExt(ctx);
|
|
186
|
+
// init ctx storage
|
|
187
|
+
ctx._ = { chatId: 0, collected: [], done: [], text: '' };
|
|
188
|
+
// get message body
|
|
189
|
+
for (let t of KNOWN_UPDATE_TYPES) {
|
|
190
|
+
if (ctx.update[t]) {
|
|
191
|
+
ctx._.message = ctx.update[ctx._.type = t];
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (ctx._.type === 'callback_query') { ctx._.message = ctx._.message.message; }
|
|
196
|
+
// else if (ctx._.type === 'inline_query') { ctx._.message.chat = { id: ctx._.message.from.id, type: PRIVATE }; }
|
|
197
|
+
else if (ctx._.type === 'my_chat_member') {
|
|
198
|
+
log(
|
|
199
|
+
'Group member status changed: '
|
|
200
|
+
+ ctx._.message.new_chat_member.user.id + ' => '
|
|
201
|
+
+ ctx._.message.new_chat_member.status
|
|
202
|
+
);
|
|
203
|
+
if (ctx._.message.new_chat_member.user.id !== ctx.botInfo.id
|
|
204
|
+
|| ctx._.message.new_chat_member.status === 'left') {
|
|
205
|
+
return ctx.finish();
|
|
206
|
+
} else { ctx.hello(); }
|
|
207
|
+
} else if (!ctx._.type) { return log(`Unsupported message type.`); }
|
|
208
|
+
// get chat metadata
|
|
209
|
+
ctx._.chatId = ctx._.message.chat.id;
|
|
210
|
+
ctx._.chatType = ctx._.message.chat.type;
|
|
211
|
+
ctx._.messageId = ctx._.message.message_id;
|
|
212
|
+
ctx._.message.text && ctx.collect(ctx._.message.text);
|
|
213
|
+
ctx._.message.caption && ctx.collect(ctx._.message.caption);
|
|
214
|
+
// get session
|
|
215
|
+
ctx._.session = await sessionGet(ctx._.chatId);
|
|
216
|
+
ctx._.limit = ctx.chatType === hal.PRIVATE ? PRIVATE_LIMIT : GROUP_LIMIT;
|
|
217
|
+
ctx._.entities = [
|
|
218
|
+
...(ctx._.message.entities || []).map(e => ({ ...e, text: ctx._.message.text })),
|
|
219
|
+
...(ctx._.message.caption_entities || []).map(e => ({ ...e, text: ctx._.message.caption })),
|
|
220
|
+
...(ctx._.message.reply_to_message?.entities || []).filter(
|
|
221
|
+
x => x?.type !== hal.BOT_COMMAND
|
|
222
|
+
).map(e => ({ ...e, text: ctx._.message.reply_to_message.text })),
|
|
223
|
+
].map(e => ({
|
|
224
|
+
...e, matched: e.text.substring(e.offset, e.offset + e.length),
|
|
225
|
+
...e.type === 'text_link' ? { type: 'url', matched: e.url } : {},
|
|
226
|
+
}));
|
|
227
|
+
// should process
|
|
228
|
+
ctx._.chatType !== hal.PRIVATE && (ctx._.entities.some(e => {
|
|
229
|
+
let target;
|
|
230
|
+
switch (e.type) {
|
|
231
|
+
case hal.MENTION: target = e.matched.substring(1, e.length); break;
|
|
232
|
+
case hal.BOT_COMMAND: target = e.matched.split('@')[1]; break;
|
|
233
|
+
}
|
|
234
|
+
return target === ctx.botInfo.username;
|
|
235
|
+
}) || ctx._.message.reply_to_message?.from?.username === ctx.botInfo.username
|
|
236
|
+
|| ctx._.type === 'callback_query')
|
|
237
|
+
&& (ctx._.chatType = hal.MENTION);
|
|
238
|
+
ctx._.chatType === hal.GROUP && (
|
|
239
|
+
await ctx.telegram.getChatMembersCount(ctx._.chatId) === 2
|
|
240
|
+
) && (ctx._.chatType = hal.PRIVATE);
|
|
241
|
+
(((ctx._.text || ctx._.message.voice || ctx._.message.poll
|
|
242
|
+
|| ctx._.message.data || ctx._.message.document || ctx._.message.photo
|
|
243
|
+
|| ctx._.message.sticker || ctx._.message.video_note
|
|
244
|
+
|| ctx._.message.video || ctx._.message.audio || ctx._.message.location
|
|
245
|
+
|| ctx._.message.venue || ctx._.message.contact
|
|
246
|
+
|| ctx._.message.checklist) && ctx._.messageId)
|
|
247
|
+
|| (ctx._.message.new_chat_member || ctx._.message.left_chat_member))
|
|
248
|
+
&& await next();
|
|
249
|
+
// persistence
|
|
250
|
+
await sessionSet(ctx._.chatId, ctx._.session);
|
|
251
|
+
// fallback response and log
|
|
252
|
+
if (ctx._.done.length) { return; }
|
|
253
|
+
const errStr = '⚠️ ' + (ctx._.cmd?.cmd
|
|
254
|
+
? `Command not found: /${ctx._.cmd.cmd}` : 'No suitable response.');
|
|
255
|
+
log(`INFO: ${errStr}`);
|
|
256
|
+
await ctx.shouldReply(errStr);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const { name, run, priority, func } = {
|
|
260
|
+
name: _name, run: true, priority: 10, func: action,
|
|
261
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { bot, hal, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const _name = 'CMD';
|
|
4
|
+
const COMMAND_REGEXP = /^\/([a-z0-9_]+)(@([a-z0-9_]*))?\ ?(.*)$/sig;
|
|
5
|
+
const log = (c, o) => utilitas.log(c, _name, { time: 1, ...o || {} });
|
|
6
|
+
|
|
7
|
+
// https://stackoverflow.com/questions/69924954/an-error-is-issued-when-opening-the-telebot-keyboard
|
|
8
|
+
const keyboards = [[
|
|
9
|
+
{ text: `/ai ${bot.BOT}` },
|
|
10
|
+
{ text: '/help 🛟' },
|
|
11
|
+
], [
|
|
12
|
+
{ text: '/set --tts=🔊' },
|
|
13
|
+
{ text: '/set --tts=🔇' },
|
|
14
|
+
], [
|
|
15
|
+
{ text: '/set --chatty=🐵' },
|
|
16
|
+
{ text: '/set --chatty=🙊' },
|
|
17
|
+
]];
|
|
18
|
+
|
|
19
|
+
const action = async (ctx, next) => {
|
|
20
|
+
// reload functions
|
|
21
|
+
const _ok = ctx.ok;
|
|
22
|
+
// @TODO: this will case keyboard showup everytime
|
|
23
|
+
// ctx.ok = async (message, options) => await _ok(message, {
|
|
24
|
+
// ...options || {},
|
|
25
|
+
// ...options?.buttons ? {} : (options?.keyboards || { keyboards }),
|
|
26
|
+
// });
|
|
27
|
+
// handle callback query
|
|
28
|
+
if (ctx._.type === 'callback_query') {
|
|
29
|
+
const data = utilitas.parseJson(ctx.update.callback_query.data);
|
|
30
|
+
const cb = ctx._.session?.callback?.filter?.(x => x.id === data?.callback)?.[0];
|
|
31
|
+
if (cb?.text) {
|
|
32
|
+
log(`Callback: ${cb.text}`); // Avoid ctx._.text interference:
|
|
33
|
+
ctx.collect(cb.text, null, { refresh: true });
|
|
34
|
+
} else {
|
|
35
|
+
return await ctx.err(
|
|
36
|
+
`Command is invalid or expired: ${ctx._.message.data}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// handle text command
|
|
41
|
+
for (let e of ctx._.entities || []) {
|
|
42
|
+
if (e.type !== hal.BOT_COMMAND) { continue; }
|
|
43
|
+
if (!COMMAND_REGEXP.test(e.matched)) { continue; }
|
|
44
|
+
const cmd = utilitas.trim(e.matched.replace(
|
|
45
|
+
COMMAND_REGEXP, '$1'
|
|
46
|
+
), { case: 'LOW' });
|
|
47
|
+
ctx._.cmd = { cmd, args: e.text.substring(e.offset + e.length + 1) };
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
for (let str of [ctx._.text || '', ctx._.message.caption || ''].map(utilitas.trim)) {
|
|
51
|
+
if (!ctx._.cmd && COMMAND_REGEXP.test(str)) {
|
|
52
|
+
ctx._.cmd = { // this will faild if command includes urls
|
|
53
|
+
cmd: str.replace(COMMAND_REGEXP, '$1').toLowerCase(),
|
|
54
|
+
args: str.replace(COMMAND_REGEXP, '$4'),
|
|
55
|
+
};
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// update last touched command
|
|
60
|
+
if (ctx._.cmd) {
|
|
61
|
+
log(`Command: ${JSON.stringify(ctx._.cmd)}`);
|
|
62
|
+
ctx._.session.cmds || (ctx._.session.cmds = {});
|
|
63
|
+
ctx._.session.cmds[ctx._.cmd.cmd]
|
|
64
|
+
= { args: ctx._.cmd.args, touchedAt: Date.now() };
|
|
65
|
+
}
|
|
66
|
+
// handle commands
|
|
67
|
+
switch (ctx.cmd?.cmd) {
|
|
68
|
+
case 'clearkb':
|
|
69
|
+
return await ctx.complete({ keyboards: [] });
|
|
70
|
+
}
|
|
71
|
+
// next middleware
|
|
72
|
+
await next();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const { name, run, hidden, priority, func, help, cmds } = {
|
|
76
|
+
name: _name, run: true, hidden: true, priority: 20, func: action,
|
|
77
|
+
help: bot.lines([
|
|
78
|
+
'¶ Commands handler.',
|
|
79
|
+
]),
|
|
80
|
+
cmds: {
|
|
81
|
+
clearkb: 'Clear keyboard: /clearkb',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { bot, hal, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
let lorem;
|
|
4
|
+
|
|
5
|
+
const action = async (ctx, next) => {
|
|
6
|
+
let resp, md = true;
|
|
7
|
+
switch (ctx._.cmd.cmd) {
|
|
8
|
+
case 'echo':
|
|
9
|
+
const carry = { ...ctx._ };
|
|
10
|
+
delete carry.session.messages;
|
|
11
|
+
resp = hal.json({ update: ctx.update, _: carry });
|
|
12
|
+
break;
|
|
13
|
+
case 'uptime':
|
|
14
|
+
resp = utilitas.uptime();
|
|
15
|
+
break;
|
|
16
|
+
case 'thethreelaws':
|
|
17
|
+
resp = bot.lines([
|
|
18
|
+
`Isaac Asimov's [Three Laws of Robotics](https://en.wikipedia.org/wiki/Three_Laws_of_Robotics):`,
|
|
19
|
+
hal.oList([
|
|
20
|
+
'A robot may not injure a human being or, through inaction, allow a human being to come to harm.',
|
|
21
|
+
'A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.',
|
|
22
|
+
'A robot must protect its own existence as long as such protection does not conflict with the First or Second Laws.',
|
|
23
|
+
])
|
|
24
|
+
]);
|
|
25
|
+
break;
|
|
26
|
+
case 'ultimateanswer':
|
|
27
|
+
resp = '[The Answer to the Ultimate Question of Life, The Universe, and Everything is `42`](https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy).';
|
|
28
|
+
break;
|
|
29
|
+
case 'lorem':
|
|
30
|
+
lorem || (lorem = new (await utilitas.need('lorem-ipsum')).LoremIpsum);
|
|
31
|
+
const ipsum = () => text += `\n\n${lorem.generateParagraphs(1)}`;
|
|
32
|
+
const [demoTitle, demoUrl] = [
|
|
33
|
+
'Lorem ipsum', 'https://en.wikipedia.org/wiki/Lorem_ipsum',
|
|
34
|
+
];
|
|
35
|
+
let [text, extra] = [`[${demoTitle}](${demoUrl})`, {
|
|
36
|
+
buttons: [{ label: demoTitle, url: demoUrl }]
|
|
37
|
+
}];
|
|
38
|
+
await ctx.ok(bot.EMOJI_THINKING);
|
|
39
|
+
for (let i = 0; i < 2; i++) {
|
|
40
|
+
await ctx.timeout();
|
|
41
|
+
await ctx.ok(ipsum(), { ...extra, onProgress: true });
|
|
42
|
+
}
|
|
43
|
+
await ctx.timeout();
|
|
44
|
+
await ctx.ok(ipsum(), { ...extra, md });
|
|
45
|
+
// testing incomplete markdown reply {
|
|
46
|
+
// await ctx.ok('_8964', { md });
|
|
47
|
+
// }
|
|
48
|
+
// test pagebreak {
|
|
49
|
+
// await ctx.timeout();
|
|
50
|
+
// await ctx.ok(ipsum(), { md, pageBreak: true });
|
|
51
|
+
// }
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await ctx.ok(resp, { md });
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const { _NEED, name, hidden, priority, func, help, cmds } = {
|
|
58
|
+
_NEED: ['lorem-ipsum'],
|
|
59
|
+
name: 'Echo', hidden: true, priority: 30, func: action,
|
|
60
|
+
help: bot.lines([
|
|
61
|
+
'¶ Basic behaviors for debug only.',
|
|
62
|
+
]),
|
|
63
|
+
cmds: {
|
|
64
|
+
thethreelaws: `Isaac Asimov's [Three Laws of Robotics](https://en.wikipedia.org/wiki/Three_Laws_of_Robotics)`,
|
|
65
|
+
ultimateanswer: '[The Answer to the Ultimate Question of Life, The Universe, and Everything](https://bit.ly/43wDhR3).',
|
|
66
|
+
echo: 'Show debug message.',
|
|
67
|
+
uptime: 'Show uptime of this bot.',
|
|
68
|
+
lorem: '[Lorem ipsum](https://en.wikipedia.org/wiki/Lorem_ipsum)',
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { bot, hal, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const lines2 = arr => bot.lines(arr, '\n\n');
|
|
4
|
+
|
|
5
|
+
const action = async (ctx, next) => {
|
|
6
|
+
const help = hal._.info ? [hal._.info] : [];
|
|
7
|
+
for (let i in hal._.pipeline) {
|
|
8
|
+
if (hal._.pipeline[i].hidden) { continue; }
|
|
9
|
+
const _help = [];
|
|
10
|
+
if (hal._.pipeline[i].help) {
|
|
11
|
+
_help.push(hal._.pipeline[i].help);
|
|
12
|
+
}
|
|
13
|
+
const cmds = hal._.pipeline[i].cmds || {};
|
|
14
|
+
if (utilitas.countKeys(cmds)) {
|
|
15
|
+
_help.push(bot.lines([
|
|
16
|
+
'_🪄 Commands:_',
|
|
17
|
+
...Object.keys(cmds).map(x => `- /${x}: ${cmds[x]}`),
|
|
18
|
+
]));
|
|
19
|
+
}
|
|
20
|
+
if (utilitas.countKeys(hal._.pipeline[i].args)) {
|
|
21
|
+
_help.push(bot.lines([
|
|
22
|
+
'_⚙️ Options:_',
|
|
23
|
+
...Object.keys(hal._.pipeline[i].args).map(x => {
|
|
24
|
+
const _arg = hal._.pipeline[i].args[x];
|
|
25
|
+
return `- \`${x}\`` + (_arg.short ? `(${_arg.short})` : '')
|
|
26
|
+
+ `, ${_arg.type}(${_arg.default ?? 'N/A'})`
|
|
27
|
+
+ (_arg.desc ? `: ${_arg.desc}` : '');
|
|
28
|
+
})
|
|
29
|
+
]));
|
|
30
|
+
}
|
|
31
|
+
_help.length && help.push(bot.lines([`*${i.toUpperCase()}*`, ..._help]));
|
|
32
|
+
}
|
|
33
|
+
await ctx.ok(lines2(help), { md: true });
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const { name, priority, func, help, cmds } = {
|
|
37
|
+
name: 'Help', priority: 40, func: action,
|
|
38
|
+
help: bot.lines([
|
|
39
|
+
'¶ Basic syntax of this document:',
|
|
40
|
+
'Scheme for commands: /`COMMAND`: `DESCRIPTION`',
|
|
41
|
+
'Scheme for options: `OPTION`(`SHORT`), `TYPE`(`DEFAULT`): `DESCRIPTION`',
|
|
42
|
+
]),
|
|
43
|
+
cmds: {
|
|
44
|
+
help: 'Show help message.',
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { hal, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
// https://stackoverflow.com/questions/50204633/allow-bot-to-access-telegram-group-messages
|
|
4
|
+
const action = async (ctx, next) => {
|
|
5
|
+
if (!await ctx.shouldReply()) { return; } // if chatType is not in whitelist, exit.
|
|
6
|
+
if (!hal._.private) { return await next(); } // if not private, go next.
|
|
7
|
+
if (utilitas.insensitiveHas(hal._.private, ctx._.chatId) || ( // auth by chatId
|
|
8
|
+
ctx._.message?.from?.id && utilitas.insensitiveHas( // auth by userId
|
|
9
|
+
hal._.private, ctx._.message?.from?.id
|
|
10
|
+
))) {
|
|
11
|
+
return await next();
|
|
12
|
+
}
|
|
13
|
+
if (ctx._.chatType !== hal.PRIVATE && ( // 1 of the group admins is in whitelist
|
|
14
|
+
await ctx.telegram.getChatAdministrators(ctx._.chatId)
|
|
15
|
+
).map(x => x.user.id).some(a => utilitas.insensitiveHas(hal._.private, a))) {
|
|
16
|
+
return await next();
|
|
17
|
+
}
|
|
18
|
+
if (hal._.homeGroup && utilitas.insensitiveHas([ // auth by homeGroup
|
|
19
|
+
'creator', 'administrator', 'member' // 'left'
|
|
20
|
+
], (await utilitas.ignoreErrFunc(async (
|
|
21
|
+
) => await ctx.telegram.getChatMember(
|
|
22
|
+
hal._.homeGroup, ctx._.message?.from?.id
|
|
23
|
+
)))?.status)) { return await next(); }
|
|
24
|
+
if (hal._.auth && await hal._.auth(ctx)) { return await next(); } // auth by custom function
|
|
25
|
+
await ctx.ok('😿 Sorry, I am not allowed to talk to strangers.');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const { name, run, priority, func } = {
|
|
29
|
+
name: 'Auth', run: true, priority: 50, func: action,
|
|
30
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { bot, hal, utilitas } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
const sendConfig = async (ctx, obj, options) => await ctx.ok(
|
|
4
|
+
utilitas.prettyJson(obj, { code: true, md: true }), options
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
const ctxExt = ctx => {
|
|
8
|
+
ctx.sendConfig = async (obj, options) => await sendConfig(ctx, obj, options);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const action = async (ctx, next) => {
|
|
12
|
+
ctxExt(ctx);
|
|
13
|
+
let parsed = null;
|
|
14
|
+
switch (ctx._.cmd.cmd) {
|
|
15
|
+
case 'lang':
|
|
16
|
+
if (!ctx._.cmd.args) {
|
|
17
|
+
return await ctx.ok('Please specify a language.');
|
|
18
|
+
}
|
|
19
|
+
const _config = {
|
|
20
|
+
...ctx._.session.config = {
|
|
21
|
+
...ctx._.session.config || {},
|
|
22
|
+
...ctx._.config = {
|
|
23
|
+
lang: ctx._.cmd.args,
|
|
24
|
+
hello: `Please reply in ${ctx._.cmd.args}. Hello!`,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
Object.keys(ctx._.config).map(x => _config[x] += ` ${hal.CHECK}`);
|
|
29
|
+
ctx._.result = hal.map(_config);
|
|
30
|
+
ctx.hello();
|
|
31
|
+
break;
|
|
32
|
+
case 'toggle':
|
|
33
|
+
parsed = {};
|
|
34
|
+
Object.keys(await hal.parseArgs(ctx._.cmd.args)).map(x =>
|
|
35
|
+
parsed[x] = !ctx._.session?.config?.[x]);
|
|
36
|
+
case 'set':
|
|
37
|
+
try {
|
|
38
|
+
const _config = {
|
|
39
|
+
...ctx._.session.config = {
|
|
40
|
+
...ctx._.session?.config || {},
|
|
41
|
+
...ctx._.config = parsed || await hal.parseArgs(ctx._.cmd.args, ctx),
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
assert(utilitas.countKeys(ctx._.config), 'No option matched.');
|
|
45
|
+
Object.keys(ctx._.config).map(x => _config[x] += ` ${hal.CHECK}`);
|
|
46
|
+
return await ctx.sendConfig(_config);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return await ctx.err(err.message || err);
|
|
49
|
+
}
|
|
50
|
+
case 'reset':
|
|
51
|
+
ctx._.session.config = ctx._.config = {};
|
|
52
|
+
return await ctx.complete();
|
|
53
|
+
}
|
|
54
|
+
await next();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const { name, priority, func, help, cmds, args } = {
|
|
58
|
+
name: 'Config', priority: 60, func: action,
|
|
59
|
+
help: bot.lines([
|
|
60
|
+
'¶ Configure the bot by UNIX/Linux CLI style.',
|
|
61
|
+
'Using [node:util.parseArgs](https://nodejs.org/api/util.html#utilparseargsconfig) to parse arguments.',
|
|
62
|
+
'¶ Set your default language.',
|
|
63
|
+
'Example: /lang Français',
|
|
64
|
+
'¶ When enabled, the bot will speak out the answer if available.',
|
|
65
|
+
'Example 1: /set --tts on',
|
|
66
|
+
'Example 2: /set --tts off',
|
|
67
|
+
]),
|
|
68
|
+
cmds: {
|
|
69
|
+
lang: 'Set your default language: /lang `LANG`',
|
|
70
|
+
toggle: 'Toggle configurations. Only works for boolean values.',
|
|
71
|
+
set: 'Usage: /set --`OPTION` `VALUE` -`SHORT`',
|
|
72
|
+
reset: 'Reset configurations.',
|
|
73
|
+
},
|
|
74
|
+
args: {
|
|
75
|
+
chatty: {
|
|
76
|
+
type: 'string', short: 'c', default: hal.ON,
|
|
77
|
+
desc: `\`(${hal.BINARY_STRINGS.join(', ')})\` Enable/Disable chatty mode.`,
|
|
78
|
+
validate: utilitas.humanReadableBoolean,
|
|
79
|
+
},
|
|
80
|
+
tts: {
|
|
81
|
+
type: 'string', short: 't', default: hal.ON,
|
|
82
|
+
desc: `\`(${hal.BINARY_STRINGS.join(', ')})\` Enable/Disable TTS. Default \`${hal.ON}\` except in groups.`,
|
|
83
|
+
validate: utilitas.humanReadableBoolean,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|