halbot 1994.1.8 → 1995.1.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 +0 -4
- package/bin/halbot.mjs +1 -1
- package/index.mjs +55 -64
- package/lib/hal.mjs +210 -938
- package/package.json +6 -6
- package/pipeline/010_broca.mjs +261 -0
- package/pipeline/020_cmd.mjs +77 -0
- package/pipeline/030_echo.mjs +70 -0
- package/pipeline/040_help.mjs +49 -0
- package/pipeline/050_auth.mjs +30 -0
- package/pipeline/060_config.mjs +84 -0
- package/pipeline/070_collect.mjs +115 -0
- package/pipeline/080_history.mjs +111 -0
- package/pipeline/090_ai.mjs +77 -0
- package/pipeline/100_chat.mjs +65 -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/lib/hal.mjs
CHANGED
|
@@ -1,46 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
} from 'utilitas';
|
|
4
|
-
|
|
5
|
-
import { basename, join } from 'path';
|
|
1
|
+
import { alan, bot, callosum, dbio, storage, utilitas, } from 'utilitas';
|
|
2
|
+
import { join } from 'path';
|
|
6
3
|
import { parseArgs as _parseArgs } from 'node:util';
|
|
7
4
|
import { readdirSync } from 'fs';
|
|
8
5
|
|
|
9
|
-
const _NEED = ['lorem-ipsum', 'mime'];
|
|
10
6
|
// 👇 https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this
|
|
11
|
-
const table = 'utilitas_hal_events';
|
|
12
|
-
const [PRIVATE_LIMIT, GROUP_LIMIT] = [60 / 60, 60 / 20].map(x => x * 1000);
|
|
13
|
-
const log = (c, o) => utilitas.log(c, import.meta.url, { time: 1, ...o || {} });
|
|
14
|
-
const [end, parse_mode] = [bot.end, bot.parse_mode];
|
|
15
|
-
const normalizeKey = chatId => `${HALBOT}_SESSION_${chatId}`;
|
|
16
|
-
const lines2 = arr => bot.lines(arr, '\n\n');
|
|
17
|
-
const uList = arr => bot.lines(arr.map(x => `- ${x}`));
|
|
18
|
-
const oList = arr => bot.lines(arr.map((v, k) => `${k + 1}. ${v}`));
|
|
19
|
-
const isMarkdownError = e => e?.description?.includes?.("can't parse entities");
|
|
20
|
-
const getFile = async (id, op) => (await web.get(await getFileUrl(id), op)).content;
|
|
21
|
-
const compact = (str, op) => utilitas.ensureString(str, { ...op || {}, compact: true });
|
|
22
|
-
const compactLimit = (str, op) => compact(str, { ...op || {}, limit: 140 });
|
|
23
|
-
const getKey = s => s?.toLowerCase?.()?.startsWith?.('http') ? 'url' : 'source';
|
|
24
|
-
const SEARCH_LIMIT = 10;
|
|
25
|
-
|
|
26
7
|
const [ // https://limits.tginfo.me/en
|
|
27
|
-
HELLO, GROUP, PRIVATE, CHANNEL, MENTION,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
EMOJI_LOOK, EMOJI_BOT, logOptions, ON, OFF,
|
|
8
|
+
HELLO, GROUP, PRIVATE, CHANNEL, MENTION, jsonOptions, COMMAND_LIMIT, ON,
|
|
9
|
+
OFF, BOT_COMMAND, CHECK, logOptions, COMMAND_LENGTH,
|
|
10
|
+
COMMAND_DESCRIPTION_LENGTH, RELEVANCE, SEARCH_LIMIT, SUB_LIMIT,
|
|
31
11
|
] = [
|
|
32
|
-
'Hello!', 'group', 'private', 'channel', 'mention',
|
|
33
|
-
|
|
34
|
-
'
|
|
35
|
-
'bot_command', '👂', '👀', '🤖', { log: true }, 'on', 'off',
|
|
12
|
+
'Hello!', 'group', 'private', 'channel', 'mention',
|
|
13
|
+
{ code: true, extraCodeBlock: 1 }, 100, 'on', 'off', 'bot_command',
|
|
14
|
+
'☑️', { log: true }, 32, 256, 0.2, 10, 200, // Google Rerank limit
|
|
36
15
|
];
|
|
37
16
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
];
|
|
17
|
+
const table = 'utilitas_hal_events';
|
|
18
|
+
const log = (c, o) => utilitas.log(c, import.meta.url, { time: 1, ...o || {} });
|
|
19
|
+
const [end] = [bot.end];
|
|
20
|
+
const uList = arr => bot.lines(arr.map(x => `- ${x}`));
|
|
21
|
+
const oList = arr => bot.lines(arr.map((v, k) => `${k + 1}. ${v}`));
|
|
22
|
+
const [BINARY_STRINGS] = [[OFF, ON]];
|
|
44
23
|
|
|
45
24
|
const initSql = {
|
|
46
25
|
[dbio.MYSQL]: [[
|
|
@@ -120,982 +99,275 @@ const initSql = {
|
|
|
120
99
|
]],
|
|
121
100
|
};
|
|
122
101
|
|
|
123
|
-
let
|
|
124
|
-
|
|
125
|
-
const getExtra = (ctx, options) => {
|
|
126
|
-
const resp = {
|
|
127
|
-
reply_parameters: {
|
|
128
|
-
message_id: ctx.chatType === PRIVATE ? undefined : ctx.messageId,
|
|
129
|
-
}, disable_notification: !!ctx.done.length, ...options || {},
|
|
130
|
-
};
|
|
131
|
-
resp.reply_markup || (resp.reply_markup = {});
|
|
132
|
-
if (options?.buttons?.length) {
|
|
133
|
-
resp.reply_markup.inline_keyboard = options?.buttons.map(row =>
|
|
134
|
-
utilitas.ensureArray(row).map(button => {
|
|
135
|
-
if (button.url) {
|
|
136
|
-
return { text: button.label, url: button.url };
|
|
137
|
-
} else if (button.text) {
|
|
138
|
-
const id = uoid.fakeUuid(button.text);
|
|
139
|
-
ctx.session.callback.push({ id, ...button });
|
|
140
|
-
return {
|
|
141
|
-
text: button.label,
|
|
142
|
-
callback_data: JSON.stringify({ callback: id }),
|
|
143
|
-
};
|
|
144
|
-
} else {
|
|
145
|
-
utilitas.throwError('Invalid button markup.');
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
);
|
|
149
|
-
} else if (options?.keyboards) {
|
|
150
|
-
if (options.keyboards.length) {
|
|
151
|
-
resp.reply_markup.keyboard = options?.keyboards.map(utilitas.ensureArray);
|
|
152
|
-
} else { resp.reply_markup.remove_keyboard = true; }
|
|
153
|
-
}
|
|
154
|
-
return resp;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const getFileUrl = async (file_id) => {
|
|
158
|
-
assert(file_id, 'File ID is required.', 400);
|
|
159
|
-
const file = await (await init()).telegram.getFile(file_id);
|
|
160
|
-
assert(file.file_path, 'Error getting file info.', 500);
|
|
161
|
-
return `${API_ROOT}file/bot${hal.token}/${file.file_path}`;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const officeParser = async file => await utilitas.ignoreErrFunc(
|
|
165
|
-
async () => await vision.parseOfficeFile(file, { input: storage.BUFFER }), { log: true }
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
const json = obj => bot.lines([
|
|
169
|
-
'```json', utilitas.prettyJson(obj, jsonOptions).replaceAll('```', ''), '```'
|
|
170
|
-
]);
|
|
171
|
-
|
|
172
|
-
const sessionGet = async chatId => {
|
|
173
|
-
const key = normalizeKey(chatId);
|
|
174
|
-
sessions[chatId] || (sessions[chatId] = (
|
|
175
|
-
hal._.session.get && await hal._.session.get(key) || {}
|
|
176
|
-
));
|
|
177
|
-
sessions[chatId].callback || (sessions[chatId].callback = []);
|
|
178
|
-
sessions[chatId].config || (sessions[chatId].config = {});
|
|
179
|
-
return sessions[chatId];
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const sessionSet = async chatId => {
|
|
183
|
-
const key = normalizeKey(chatId);
|
|
184
|
-
while (sessions[chatId]?.callback?.length > CALLBACK_LIMIT) {
|
|
185
|
-
sessions[chatId].callback.shift();
|
|
186
|
-
}
|
|
187
|
-
const toSet = {};
|
|
188
|
-
Object.keys(sessions[chatId]).filter(x => /^[^_]+$/g.test(x)).map(
|
|
189
|
-
x => toSet[x] = sessions[chatId][x]
|
|
190
|
-
);
|
|
191
|
-
return hal._.session.set && await hal._.session.set(key, toSet);
|
|
192
|
-
};
|
|
102
|
+
let _;
|
|
193
103
|
|
|
194
104
|
const newCommand = (command, description) => ({
|
|
195
105
|
command: utilitas.ensureString(command, { case: 'SNAKE' }).slice(0, COMMAND_LENGTH),
|
|
196
106
|
description: utilitas.trim(description).slice(0, COMMAND_DESCRIPTION_LENGTH),
|
|
197
107
|
});
|
|
198
108
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let seconds = process.uptime();
|
|
203
|
-
const ss = Object.keys(sessions);
|
|
204
|
-
const days = Math.floor(seconds / (3600 * 24));
|
|
205
|
-
seconds -= days * 3600 * 24;
|
|
206
|
-
let hours = Math.floor(seconds / 3600);
|
|
207
|
-
seconds -= hours * 3600;
|
|
208
|
-
hours = hours.toString().padStart(2, '0');
|
|
209
|
-
let minutes = Math.floor(seconds / 60);
|
|
210
|
-
minutes = minutes.toString().padStart(2, '0');
|
|
211
|
-
seconds = Math.floor(seconds % 60).toString().padStart(2, '0');
|
|
212
|
-
days > 0 && (resp += ` ${days} day${days > 1 ? 's' : ''},`);
|
|
213
|
-
return `${resp} ${hours}:${minutes}:${seconds}, `
|
|
214
|
-
+ `${ss.length} session${ss.length > 1 ? 's' : ''}`;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const reply = async (ctx, md, text, extra) => {
|
|
218
|
-
// if (ctx.type === 'inline_query') {
|
|
219
|
-
// return await ctx.answerInlineQuery([{}, {}]);
|
|
220
|
-
// }
|
|
221
|
-
if (md) {
|
|
222
|
-
try {
|
|
223
|
-
return await (extra?.reply_parameters?.message_id
|
|
224
|
-
? ctx.replyWithMarkdown(text, { parse_mode, ...extra })
|
|
225
|
-
: ctx.sendMessage(text, { parse_mode, ...extra }));
|
|
226
|
-
} catch (err) { // utilitas.throwError('Error sending message.');
|
|
227
|
-
isMarkdownError(err) || log(err);
|
|
228
|
-
await ctx.timeout();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return await utilitas.ignoreErrFunc(
|
|
232
|
-
async () => await (extra?.reply_parameters?.message_id
|
|
233
|
-
? ctx.reply(text, extra) : ctx.sendMessage(text, extra)
|
|
234
|
-
), logOptions
|
|
235
|
-
);
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
const editMessageText = async (ctx, md, lastMsgId, text, extra) => {
|
|
239
|
-
if (md) {
|
|
240
|
-
try {
|
|
241
|
-
return await ctx.telegram.editMessageText(
|
|
242
|
-
ctx.chatId, lastMsgId, '', text, { parse_mode, ...extra }
|
|
243
|
-
);
|
|
244
|
-
} catch (err) { // utilitas.throwError('Error editing message.');
|
|
245
|
-
isMarkdownError(err) || log(err);
|
|
246
|
-
await ctx.timeout();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return await utilitas.ignoreErrFunc(async () => await ctx.telegram.editMessageText(
|
|
250
|
-
ctx.chatId, lastMsgId, '', text, extra
|
|
251
|
-
), logOptions);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const memorize = async (ctx) => {
|
|
255
|
-
if (ctx._skipMemorize) { return; }
|
|
256
|
-
const received = ctx.update;
|
|
257
|
-
const received_text = ctx.txt || ''; // ATTACHMENTS
|
|
258
|
-
const id = received.update_id;
|
|
259
|
-
const response = utilitas.lastItem(ctx.done.filter(x => x?.text)) || {};
|
|
260
|
-
const response_text = response?.text || '';
|
|
261
|
-
const collected = ctx.collected.filter(x => String.isString(x.content));
|
|
262
|
-
const distilled = compact(bot.lines([
|
|
263
|
-
received_text, response_text, ...collected.map(x => x.content)
|
|
264
|
-
]));
|
|
265
|
-
if (!ctx.messageId || !distilled) { return; }
|
|
266
|
-
const event = {
|
|
267
|
-
id, bot_id: ctx.botInfo.id, chat_id: ctx.chatId,
|
|
268
|
-
chat_type: ctx.chatType, message_id: ctx.messageId,
|
|
269
|
-
received: JSON.stringify(received), received_text,
|
|
270
|
-
response: JSON.stringify(response), response_text,
|
|
271
|
-
collected: JSON.stringify(collected), distilled,
|
|
272
|
-
};
|
|
273
|
-
return await utilitas.ignoreErrFunc(async () => {
|
|
274
|
-
event.distilled_vector = hal._.embedding
|
|
275
|
-
? await hal._.embedding(event.distilled) : [];
|
|
276
|
-
switch (hal._.database.provider) {
|
|
277
|
-
case dbio.MYSQL:
|
|
278
|
-
event.distilled_vector = JSON.stringify(event.distilled_vector);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
await hal._.database?.client?.upsert?.(table, event, { skipEcho: true });
|
|
282
|
-
return hal._.memorize && await hal._.memorize(event);
|
|
283
|
-
}, logOptions);
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
// https://stackoverflow.com/questions/50204633/allow-bot-to-access-telegram-group-messages
|
|
287
|
-
const subconscious = [{
|
|
288
|
-
run: true, priority: -8960, name: 'broca', func: async (ctx, next) => {
|
|
289
|
-
const e = `Event: ${ctx.update.update_id} => ${JSON.stringify(ctx.update)}`;
|
|
290
|
-
process.stdout.write(`[HAL] ${e}\n`);
|
|
291
|
-
log(e);
|
|
292
|
-
ctx.done = [];
|
|
293
|
-
ctx.collected = [];
|
|
294
|
-
ctx.timeout = async () => await utilitas.timeout(ctx.limit);
|
|
295
|
-
ctx.hello = str => {
|
|
296
|
-
str = str || ctx.session?.config?.hello || hal._.hello;
|
|
297
|
-
ctx.collect(str, null, { refresh: true });
|
|
298
|
-
return str;
|
|
299
|
-
};
|
|
300
|
-
ctx.shouldReply = async text => {
|
|
301
|
-
const should = utilitas.insensitiveHas(ctx._?.chatType, ctx.chatType)
|
|
302
|
-
|| ctx.session?.config?.chatty;
|
|
303
|
-
should && text && await ctx.ok(text);
|
|
304
|
-
return should;
|
|
305
|
-
};
|
|
306
|
-
ctx.checkSpeech = () => ctx.chatType === PRIVATE
|
|
307
|
-
? ctx.session.config?.tts !== false
|
|
308
|
-
: ctx.session.config?.tts === true;
|
|
309
|
-
ctx.shouldSpeech = async text => {
|
|
310
|
-
text = utilitas.isSet(text, true) ? (text || '') : ctx.tts;
|
|
311
|
-
const should = ctx._.speech?.tts && ctx.checkSpeech();
|
|
312
|
-
should && text && await ctx.speech(text);
|
|
313
|
-
return should;
|
|
314
|
-
};
|
|
315
|
-
ctx.collect = (content, type, options) => type ? ctx.collected.push(
|
|
316
|
-
{ type, content }
|
|
317
|
-
) : (ctx.txt = [
|
|
318
|
-
(options?.refresh ? '' : ctx.txt) || '', content || ''
|
|
319
|
-
].filter(x => x.length).join('\n\n'));
|
|
320
|
-
ctx.skipMemorize = () => ctx._skipMemorize = true;
|
|
321
|
-
ctx.end = () => {
|
|
322
|
-
ctx.done.push(null);
|
|
323
|
-
ctx.skipMemorize();
|
|
324
|
-
};
|
|
325
|
-
ctx.ok = async (message, options) => {
|
|
326
|
-
let pages = bot.paging(message, options);
|
|
327
|
-
const extra = getExtra(ctx, options);
|
|
328
|
-
const [pageIds, pageMap] = [[], {}];
|
|
329
|
-
options?.pageBreak || ctx.done.map(x => {
|
|
330
|
-
pageMap[x?.message_id] || (pageIds.push(x?.message_id));
|
|
331
|
-
pageMap[x?.message_id] = x;
|
|
332
|
-
});
|
|
333
|
-
for (let i in pages) {
|
|
334
|
-
const lastPage = ~~i === pages.length - 1;
|
|
335
|
-
const shouldExtra = options?.lastMessageId || lastPage;
|
|
336
|
-
if (options?.onProgress && !options?.lastMessageId
|
|
337
|
-
&& pageMap[pageIds[~~i]]?.text === pages[i]) { continue; }
|
|
338
|
-
if (options?.onProgress && !pageIds[~~i]) { // progress: new page, reply text
|
|
339
|
-
ctx.done.push(await reply(
|
|
340
|
-
ctx, false, pages[i], extra
|
|
341
|
-
));
|
|
342
|
-
} else if (options?.onProgress) { // progress: ongoing, edit text
|
|
343
|
-
ctx.done.push(await editMessageText(
|
|
344
|
-
ctx, false, pageIds[~~i],
|
|
345
|
-
pages[i], shouldExtra ? extra : {}
|
|
346
|
-
));
|
|
347
|
-
} else if (options?.lastMessageId || pageIds[~~i]) { // progress: final, edit markdown
|
|
348
|
-
ctx.done.push(await editMessageText(
|
|
349
|
-
ctx, true, options?.lastMessageId || pageIds[~~i],
|
|
350
|
-
pages[i], shouldExtra ? extra : {}
|
|
351
|
-
));
|
|
352
|
-
} else { // never progress, reply markdown
|
|
353
|
-
ctx.done.push(await reply(ctx, true, pages[i], extra));
|
|
354
|
-
}
|
|
355
|
-
await ctx.timeout();
|
|
356
|
-
}
|
|
357
|
-
return ctx.done;
|
|
358
|
-
};
|
|
359
|
-
ctx.er = async (m, opts) => {
|
|
360
|
-
log(m);
|
|
361
|
-
return await ctx.ok(`⚠️ ${m?.message || m}`, opts);
|
|
362
|
-
};
|
|
363
|
-
ctx.complete = async (options) => await ctx.ok('☑️', options);
|
|
364
|
-
ctx.json = async (obj, options) => await ctx.ok(json(obj), options);
|
|
365
|
-
ctx.list = async (list, options) => await ctx.ok(uList(list), options);
|
|
366
|
-
ctx.replyWith = async (func, src, options) => ctx.done.push(
|
|
367
|
-
await ctx[func]({ [getKey(src)]: src }, getExtra(ctx, options))
|
|
368
|
-
);
|
|
369
|
-
ctx.audio = async (s, o) => await ctx.replyWith('replyWithAudio', s, o);
|
|
370
|
-
ctx.image = async (s, o) => await ctx.replyWith('replyWithPhoto', s, o);
|
|
371
|
-
ctx.video = async (s, o) => await ctx.replyWith('replyWithVideo', s, o);
|
|
372
|
-
ctx.media = async (srs, options) => await ctx.done.push(
|
|
373
|
-
await ctx.replyWithMediaGroup(srs.map(x => ({
|
|
374
|
-
type: x.type || 'photo', media: { [getKey(x.src)]: x.src },
|
|
375
|
-
})), getExtra(ctx, options))
|
|
376
|
-
);
|
|
377
|
-
ctx.sendConfig = async (obj, options, _ctx) => await ctx.ok(
|
|
378
|
-
utilitas.prettyJson(obj, { code: true, md: true }), options
|
|
379
|
-
);
|
|
380
|
-
ctx.speech = async (cnt, options) => {
|
|
381
|
-
let file;
|
|
382
|
-
if (Buffer.isBuffer(cnt)) {
|
|
383
|
-
file = await storage.convert(cnt, {
|
|
384
|
-
input: storage.BUFFER, expected: storage.FILE,
|
|
385
|
-
});
|
|
386
|
-
} else if (cnt.length <= speech.OPENAI_TTS_MAX_LENGTH) {
|
|
387
|
-
file = await utilitas.ignoreErrFunc(async () => await ctx._.speech.tts(
|
|
388
|
-
cnt, { expected: 'file' }
|
|
389
|
-
), logOptions);
|
|
390
|
-
}
|
|
391
|
-
if (!file) { return; }
|
|
392
|
-
const resp = await ctx.audio(file, options);
|
|
393
|
-
await storage.tryRm(file);
|
|
394
|
-
return resp;
|
|
395
|
-
};
|
|
396
|
-
await next();
|
|
397
|
-
// https://limits.tginfo.me/en
|
|
398
|
-
if (ctx.chatId) {
|
|
399
|
-
await memorize(ctx);
|
|
400
|
-
ctx._skipMemorize || await utilitas.ignoreErrFunc(async (
|
|
401
|
-
) => await hal.telegram.setMyCommands([
|
|
402
|
-
...ctx._.cmds, ...Object.keys(ctx.session.prompts || {}).map(
|
|
403
|
-
command => newCommand(command, ctx.session.prompts[command])
|
|
404
|
-
)
|
|
405
|
-
].sort((x, y) =>
|
|
406
|
-
(ctx.session?.cmds?.[y.command.toLowerCase()]?.touchedAt || 0)
|
|
407
|
-
- (ctx.session?.cmds?.[x.command.toLowerCase()]?.touchedAt || 0)
|
|
408
|
-
).slice(0, COMMAND_LIMIT), {
|
|
409
|
-
scope: { type: 'chat', chat_id: ctx.chatId },
|
|
410
|
-
}), logOptions);
|
|
411
|
-
}
|
|
412
|
-
if (ctx.done.length) { return; }
|
|
413
|
-
const errStr = ctx.cmd ? `Command not found: /${ctx.cmd.cmd}`
|
|
414
|
-
: 'No suitable response.';
|
|
415
|
-
log(`INFO: ${errStr}`);
|
|
416
|
-
await ctx.shouldReply(errStr);
|
|
417
|
-
},
|
|
418
|
-
}, {
|
|
419
|
-
run: true, priority: -8950, name: 'subconscious', func: async (ctx, next) => {
|
|
420
|
-
for (let t of KNOWN_UPDATE_TYPES) {
|
|
421
|
-
if (ctx.update[t]) {
|
|
422
|
-
ctx.m = ctx.update[ctx.type = t];
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (ctx.type === 'callback_query') { ctx.m = ctx.m.message; }
|
|
427
|
-
// else if (ctx.type === 'inline_query') { ctx.m.chat = { id: ctx.m.from.id, type: PRIVATE }; }
|
|
428
|
-
else if (ctx.type === 'my_chat_member') {
|
|
429
|
-
log(
|
|
430
|
-
'Group member status changed: '
|
|
431
|
-
+ ctx.m.new_chat_member.user.id + ' => '
|
|
432
|
-
+ ctx.m.new_chat_member.status
|
|
433
|
-
);
|
|
434
|
-
if (ctx.m.new_chat_member.user.id !== ctx.botInfo.id
|
|
435
|
-
|| ctx.m.new_chat_member.status === 'left') {
|
|
436
|
-
return ctx.end();
|
|
437
|
-
} else { ctx.hello(); }
|
|
438
|
-
} else if (!ctx.type) { return log(`Unsupported update type.`); }
|
|
439
|
-
ctx._ = hal._;
|
|
440
|
-
ctx.chatId = ctx.m.chat.id;
|
|
441
|
-
ctx.chatType = ctx.m.chat.type;
|
|
442
|
-
ctx.messageId = ctx.m.message_id;
|
|
443
|
-
ctx.m.text && ctx.collect(ctx.m.text);
|
|
444
|
-
ctx.session = await sessionGet(ctx.chatId);
|
|
445
|
-
ctx.limit = ctx.chatType === PRIVATE ? PRIVATE_LIMIT : GROUP_LIMIT;
|
|
446
|
-
ctx.entities = [
|
|
447
|
-
...(ctx.m.entities || []).map(e => ({ ...e, text: ctx.m.text })),
|
|
448
|
-
...(ctx.m.caption_entities || []).map(e => ({ ...e, text: ctx.m.caption })),
|
|
449
|
-
...(ctx.m.reply_to_message?.entities || []).filter(
|
|
450
|
-
x => x?.type !== bot_command
|
|
451
|
-
).map(e => ({ ...e, text: ctx.m.reply_to_message.text })),
|
|
452
|
-
].map(e => ({
|
|
453
|
-
...e, matched: e.text.substring(e.offset, e.offset + e.length),
|
|
454
|
-
...e.type === 'text_link' ? { type: 'url', matched: e.url } : {},
|
|
455
|
-
}));
|
|
456
|
-
ctx.chatType !== PRIVATE && (ctx.entities.some(e => {
|
|
457
|
-
let target;
|
|
458
|
-
switch (e.type) {
|
|
459
|
-
case MENTION: target = e.matched.substring(1, e.length); break;
|
|
460
|
-
case bot_command: target = e.matched.split('@')[1]; break;
|
|
461
|
-
}
|
|
462
|
-
return target === ctx.botInfo.username;
|
|
463
|
-
}) || ctx.m.reply_to_message?.from?.username === ctx.botInfo.username
|
|
464
|
-
|| ctx.type === 'callback_query')
|
|
465
|
-
&& (ctx.chatType = MENTION);
|
|
466
|
-
(((ctx.txt || ctx.m.voice || ctx.m.poll || ctx.m.data || ctx.m.document
|
|
467
|
-
|| ctx.m.photo || ctx.m.sticker || ctx.m.video_note || ctx.m.video
|
|
468
|
-
|| ctx.m.audio || ctx.m.location || ctx.m.venue || ctx.m.contact
|
|
469
|
-
) && ctx.messageId)
|
|
470
|
-
|| (ctx.m.new_chat_member || ctx.m.left_chat_member))
|
|
471
|
-
&& await next();
|
|
472
|
-
await sessionSet(ctx.chatId);
|
|
473
|
-
},
|
|
474
|
-
}, {
|
|
475
|
-
run: true, priority: -8945, name: 'callback', func: async (ctx, next) => {
|
|
476
|
-
if (ctx.type === 'callback_query') {
|
|
477
|
-
const data = utilitas.parseJson(ctx.update[ctx.type].data);
|
|
478
|
-
const cb = ctx.session.callback.filter(x => x.id === data?.callback)[0];
|
|
479
|
-
if (cb?.text) {
|
|
480
|
-
log(`Callback: ${cb.text}`); // Avoid ctx.text interference:
|
|
481
|
-
ctx.collect(cb.text, null, { refresh: true });
|
|
482
|
-
} else {
|
|
483
|
-
return await ctx.er(
|
|
484
|
-
`Command is invalid or expired: ${ctx.m.data}`
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
await next();
|
|
489
|
-
},
|
|
490
|
-
}, {
|
|
491
|
-
run: true, priority: -8940, name: 'commands', func: async (ctx, next) => {
|
|
492
|
-
for (let e of ctx?.entities || []) {
|
|
493
|
-
if (e.type !== bot_command) { continue; }
|
|
494
|
-
if (!COMMAND_REGEXP.test(e.matched)) { continue; }
|
|
495
|
-
const cmd = utilitas.trim(e.matched.replace(
|
|
496
|
-
COMMAND_REGEXP, '$1'
|
|
497
|
-
), { case: 'LOW' });
|
|
498
|
-
ctx.cmd = { cmd, args: e.text.substring(e.offset + e.length + 1) };
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
for (let str of [ctx.txt || '', ctx.m.caption || ''].map(utilitas.trim)) {
|
|
502
|
-
if (!ctx.cmd && COMMAND_REGEXP.test(str)) {
|
|
503
|
-
ctx.cmd = { // this will faild if command includes urls
|
|
504
|
-
cmd: str.replace(COMMAND_REGEXP, '$1').toLowerCase(),
|
|
505
|
-
args: str.replace(COMMAND_REGEXP, '$4'),
|
|
506
|
-
};
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (ctx.cmd) {
|
|
511
|
-
log(`Command: ${JSON.stringify(ctx.cmd)}`);
|
|
512
|
-
ctx.session.cmds || (ctx.session.cmds = {});
|
|
513
|
-
ctx.session.cmds[ctx.cmd.cmd]
|
|
514
|
-
= { args: ctx.cmd.args, touchedAt: Date.now() };
|
|
515
|
-
}
|
|
516
|
-
await next();
|
|
517
|
-
},
|
|
518
|
-
}, {
|
|
519
|
-
run: true, priority: -8930, name: 'echo', hidden: true, func: async (ctx, next) => {
|
|
520
|
-
let resp, md = false;
|
|
521
|
-
switch (ctx.cmd.cmd) {
|
|
522
|
-
case 'echo':
|
|
523
|
-
resp = json({ update: ctx.update, session: ctx.session });
|
|
524
|
-
break;
|
|
525
|
-
case 'uptime':
|
|
526
|
-
resp = uptime();
|
|
527
|
-
break;
|
|
528
|
-
case 'thethreelaws':
|
|
529
|
-
resp = bot.lines([
|
|
530
|
-
`Isaac Asimov's [Three Laws of Robotics](https://en.wikipedia.org/wiki/Three_Laws_of_Robotics):`,
|
|
531
|
-
oList([
|
|
532
|
-
'A robot may not injure a human being or, through inaction, allow a human being to come to harm.',
|
|
533
|
-
'A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.',
|
|
534
|
-
'A robot must protect its own existence as long as such protection does not conflict with the First or Second Laws.',
|
|
535
|
-
])
|
|
536
|
-
]);
|
|
537
|
-
md = true;
|
|
538
|
-
break;
|
|
539
|
-
case 'ultimateanswer':
|
|
540
|
-
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).';
|
|
541
|
-
md = true;
|
|
542
|
-
break;
|
|
543
|
-
case 'lorem':
|
|
544
|
-
const ipsum = () => text += `\n\n${lorem.generateParagraphs(1)}`;
|
|
545
|
-
const [demoTitle, demoUrl] = [
|
|
546
|
-
'Lorem ipsum', 'https://en.wikipedia.org/wiki/Lorem_ipsum',
|
|
547
|
-
];
|
|
548
|
-
let [text, extra] = [`[${demoTitle}](${demoUrl})`, {
|
|
549
|
-
buttons: [{ label: demoTitle, url: demoUrl }]
|
|
550
|
-
}];
|
|
551
|
-
await ctx.ok(bot.EMOJI_THINKING);
|
|
552
|
-
for (let i = 0; i < 2; i++) {
|
|
553
|
-
await ctx.timeout();
|
|
554
|
-
await ctx.ok(ipsum(), { ...extra, onProgress: true });
|
|
555
|
-
}
|
|
556
|
-
await ctx.timeout();
|
|
557
|
-
await ctx.ok(ipsum(), { ...extra, md: true });
|
|
558
|
-
// testing incomplete markdown reply {
|
|
559
|
-
// await ctx.ok('_8964', { md: true });
|
|
560
|
-
// }
|
|
561
|
-
// test pagebreak {
|
|
562
|
-
// await ctx.timeout();
|
|
563
|
-
// await ctx.ok(ipsum(), { md: true, pageBreak: true });
|
|
564
|
-
// }
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
await ctx.ok(resp, { md });
|
|
568
|
-
}, help: bot.lines([
|
|
569
|
-
'¶ Basic behaviors for debug only.',
|
|
570
|
-
]), cmds: {
|
|
571
|
-
thethreelaws: `Isaac Asimov's [Three Laws of Robotics](https://en.wikipedia.org/wiki/Three_Laws_of_Robotics)`,
|
|
572
|
-
ultimateanswer: '[The Answer to the Ultimate Question of Life, The Universe, and Everything](https://bit.ly/43wDhR3).',
|
|
573
|
-
echo: 'Show debug message.',
|
|
574
|
-
uptime: 'Show uptime of this bot.',
|
|
575
|
-
lorem: '[Lorem ipsum](https://en.wikipedia.org/wiki/Lorem_ipsum)',
|
|
576
|
-
},
|
|
577
|
-
}, {
|
|
578
|
-
run: true, priority: -8920, name: 'authenticate', func: async (ctx, next) => {
|
|
579
|
-
if (!await ctx.shouldReply()) { return; } // if chatType is not in whitelist, exit.
|
|
580
|
-
if (!ctx._.private) { return await next(); } // if not private, go next.
|
|
581
|
-
if (ctx._.magicWord && utilitas.insensitiveHas(ctx._.magicWord, ctx.txt)) { // auth by magicWord
|
|
582
|
-
ctx._.private.add(String(ctx.chatId));
|
|
583
|
-
await ctx.ok('😸 You are now allowed to talk to me.');
|
|
584
|
-
ctx.hello();
|
|
585
|
-
return await next();
|
|
586
|
-
}
|
|
587
|
-
if (utilitas.insensitiveHas(ctx._.private, ctx.chatId) // auth by chatId
|
|
588
|
-
|| (ctx?.from && utilitas.insensitiveHas(ctx._.private, ctx.from.id))) { // auth by userId
|
|
589
|
-
return await next();
|
|
590
|
-
}
|
|
591
|
-
if (ctx.chatType !== PRIVATE && ( // 1 of the group admins is in whitelist
|
|
592
|
-
await ctx.telegram.getChatAdministrators(ctx.chatId)
|
|
593
|
-
).map(x => x.user.id).some(a => utilitas.insensitiveHas(ctx._.private, a))) {
|
|
594
|
-
return await next();
|
|
595
|
-
}
|
|
596
|
-
if (ctx._.homeGroup && utilitas.insensitiveHas([ // auth by homeGroup
|
|
597
|
-
'creator', 'administrator', 'member' // 'left'
|
|
598
|
-
], (await utilitas.ignoreErrFunc(async () => await ctx.telegram.getChatMember(
|
|
599
|
-
ctx._.homeGroup, ctx.from.id
|
|
600
|
-
)))?.status)) { return await next(); }
|
|
601
|
-
if (ctx._.auth && await ctx._.auth(ctx)) { return await next(); } // auth by custom function
|
|
602
|
-
await ctx.ok('😿 Sorry, I am not allowed to talk to strangers.');
|
|
603
|
-
},
|
|
604
|
-
}, {
|
|
605
|
-
run: true, priority: -8910, name: 'speech-to-text', func: async (ctx, next) => {
|
|
606
|
-
const audio = ctx.m.voice || ctx.m.audio;
|
|
607
|
-
if (ctx._.speech?.stt && audio) {
|
|
608
|
-
await ctx.ok(EMOJI_SPEECH);
|
|
609
|
-
try {
|
|
610
|
-
const url = await getFileUrl(audio.file_id);
|
|
611
|
-
let file = await getFile(audio.file_id, BUFFER_ENCODE);
|
|
612
|
-
const analyze = async () => {
|
|
613
|
-
const resp = await utilitas.ignoreErrFunc(async () => {
|
|
614
|
-
[
|
|
615
|
-
storage.MIME_MP3, storage.MIME_MPEGA,
|
|
616
|
-
storage.MIME_MP4, storage.MIME_MPEG,
|
|
617
|
-
storage.MIME_MPGA, storage.MIME_M4A,
|
|
618
|
-
storage.MIME_WAV, storage.MIME_WEBM,
|
|
619
|
-
storage.MIME_OGG,
|
|
620
|
-
].includes(audio.mime_type) || (
|
|
621
|
-
file = await media.convertAudioTo16kNanoPcmWave(
|
|
622
|
-
file, { input: storage.BUFFER, expected: storage.BUFFER }
|
|
623
|
-
)
|
|
624
|
-
);
|
|
625
|
-
return await ctx._.speech?.stt(file);
|
|
626
|
-
}, logOptions) || ' ';
|
|
627
|
-
log(`STT: '${resp}'`);
|
|
628
|
-
ctx.collect(resp);
|
|
629
|
-
};
|
|
630
|
-
if (hal._.supportedMimeTypes.has(storage.MIME_WAV)) {
|
|
631
|
-
ctx.collect({
|
|
632
|
-
mime_type: storage.MIME_WAV, url, analyze,
|
|
633
|
-
data: await media.convertAudioTo16kNanoPcmWave(file, {
|
|
634
|
-
input: storage.BUFFER, expected: storage.BASE64,
|
|
635
|
-
}),
|
|
636
|
-
}, 'PROMPT');
|
|
637
|
-
} else { await analyze(); }
|
|
638
|
-
} catch (err) { return await ctx.er(err); }
|
|
639
|
-
}
|
|
640
|
-
await next();
|
|
641
|
-
},
|
|
642
|
-
}, {
|
|
643
|
-
run: true, priority: -8900, name: 'location', func: async (ctx, next) => {
|
|
644
|
-
(ctx.m.location || ctx.m.venue) && ctx.collect(bot.lines([
|
|
645
|
-
...ctx.m.location && !ctx.m.venue ? ['Location:', uList([
|
|
646
|
-
`latitude: ${ctx.m.location.latitude}`,
|
|
647
|
-
`longitude: ${ctx.m.location.longitude}`,
|
|
648
|
-
])] : [],
|
|
649
|
-
...ctx.m.venue ? ['Venue:', uList([
|
|
650
|
-
`title: ${ctx.m.venue.title}`,
|
|
651
|
-
`address: ${ctx.m.venue.address}`,
|
|
652
|
-
`latitude: ${ctx.m.venue.location.latitude}`,
|
|
653
|
-
`longitude: ${ctx.m.venue.location.longitude}`,
|
|
654
|
-
`foursquare_id: ${ctx.m.venue.foursquare_id}`,
|
|
655
|
-
`foursquare_type: ${ctx.m.venue.foursquare_type}`,
|
|
656
|
-
])] : []
|
|
657
|
-
]));
|
|
658
|
-
await next();
|
|
659
|
-
},
|
|
660
|
-
}, {
|
|
661
|
-
run: true, priority: -8895, name: 'contact', func: async (ctx, next) => {
|
|
662
|
-
ctx.m.contact && ctx.collect(bot.lines(['Contact:', uList([
|
|
663
|
-
`first_name: ${ctx.m.contact.first_name}`,
|
|
664
|
-
`last_name: ${ctx.m.contact.last_name}`,
|
|
665
|
-
`phone_number: ${ctx.m.contact.phone_number}`,
|
|
666
|
-
`user_id: ${ctx.m.contact.user_id}`,
|
|
667
|
-
`vcard: ${ctx.m.contact.vcard}`,
|
|
668
|
-
])]));
|
|
669
|
-
await next();
|
|
670
|
-
},
|
|
671
|
-
}, {
|
|
672
|
-
run: true, priority: -8893, name: 'chat_member', func: async (ctx, next) => {
|
|
673
|
-
const member = ctx.m.new_chat_member || ctx.m.left_chat_member;
|
|
674
|
-
if (member) {
|
|
675
|
-
if (member?.id === ctx.botInfo.id) { return ctx.end(); }
|
|
676
|
-
ctx.collect(bot.lines([
|
|
677
|
-
`Say ${ctx.m.new_chat_member ? 'hello' : 'goodbye'} to:`,
|
|
678
|
-
uList([
|
|
679
|
-
// `id: ${member.id}`,
|
|
680
|
-
// `ishal: ${member.ishal}`,
|
|
681
|
-
// `is_premium: ${member.is_premium}`,
|
|
682
|
-
`first_name: ${member.first_name}`,
|
|
683
|
-
`last_name: ${member.last_name}`,
|
|
684
|
-
`username: ${member.username}`,
|
|
685
|
-
`language_code: ${member.language_code || ''}`,
|
|
686
|
-
])
|
|
687
|
-
]));
|
|
688
|
-
}
|
|
689
|
-
await next();
|
|
690
|
-
},
|
|
691
|
-
}, {
|
|
692
|
-
run: true, priority: -8890, name: 'poll', func: async (ctx, next) => {
|
|
693
|
-
ctx.m.poll && ctx.collect(bot.lines([
|
|
694
|
-
'Question:', ctx.m.poll.question, '',
|
|
695
|
-
'Options:', oList(ctx.m.poll.options.map(x => x.text)),
|
|
696
|
-
]));
|
|
697
|
-
await next();
|
|
698
|
-
},
|
|
699
|
-
}, {
|
|
700
|
-
run: true, priority: -8880, name: 'contaxt', func: async (ctx, next) => {
|
|
701
|
-
ctx.m.reply_to_message?.text && ctx.collect(
|
|
702
|
-
ctx.m.reply_to_message.text, 'CONTAXT'
|
|
703
|
-
);
|
|
704
|
-
await next();
|
|
705
|
-
},
|
|
706
|
-
}, {
|
|
707
|
-
run: true, priority: -8860, name: 'vision', func: async (ctx, next) => {
|
|
708
|
-
ctx.collect(ctx.m?.caption || '');
|
|
709
|
-
const files = [];
|
|
710
|
-
for (const m of [
|
|
711
|
-
ctx.m, ...ctx.m.reply_to_message ? [ctx.m.reply_to_message] : []
|
|
712
|
-
]) {
|
|
713
|
-
if (m.document) {
|
|
714
|
-
let file = {
|
|
715
|
-
asPrompt: hal._.supportedMimeTypes.has(m.document.mime_type),
|
|
716
|
-
file_name: m.document.file_name, fileId: m.document.file_id,
|
|
717
|
-
mime_type: m.document.mime_type, type: storage.FILE,
|
|
718
|
-
ocrFunc: async f => (await storage.isTextFile(f)) && f.toString(),
|
|
719
|
-
};
|
|
720
|
-
if (storage.MIME_PDF === m.document?.mime_type) {
|
|
721
|
-
file = { ...file, ocrFunc: ctx._.vision?.read, type: 'DOCUMENT' };
|
|
722
|
-
} else if (/^image\/.*$/ig.test(m.document?.mime_type)) {
|
|
723
|
-
file = { ...file, ocrFunc: ctx._.vision?.see, type: 'IMAGE' };
|
|
724
|
-
} else if (/^.*\.(docx|xlsx|pptx)$/.test(m.document?.file_name)) {
|
|
725
|
-
file = { ...file, ocrFunc: officeParser, type: 'DOCUMENT' };
|
|
726
|
-
}
|
|
727
|
-
files.push(file);
|
|
728
|
-
}
|
|
729
|
-
if (m.sticker) {
|
|
730
|
-
const s = m.sticker;
|
|
731
|
-
const url = await getFileUrl(s.file_id);
|
|
732
|
-
const file_name = basename(url);
|
|
733
|
-
const mime_type = mime.getType(file_name) || 'image';
|
|
734
|
-
files.push({
|
|
735
|
-
asPrompt: hal._.supportedMimeTypes.has(mime_type), file_name,
|
|
736
|
-
fileId: s.file_id, mime_type, type: 'PHOTO',
|
|
737
|
-
ocrFunc: ctx._.vision?.see,
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
if (m.photo?.[m.photo?.length - 1]) {
|
|
741
|
-
const p = m.photo[m.photo.length - 1];
|
|
742
|
-
files.push({
|
|
743
|
-
asPrompt: hal._.supportedMimeTypes.has(storage.MIME_JPEG),
|
|
744
|
-
file_name: `${p.file_id}.jpg`, fileId: p.file_id,
|
|
745
|
-
mime_type: storage.MIME_JPEG, type: 'PHOTO',
|
|
746
|
-
ocrFunc: ctx._.vision?.see,
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
if (m.video_note) {
|
|
750
|
-
const vn = m.video_note;
|
|
751
|
-
const url = await getFileUrl(vn.file_id);
|
|
752
|
-
const file_name = basename(url);
|
|
753
|
-
const mime_type = mime.getType(file_name) || 'video';
|
|
754
|
-
files.push({
|
|
755
|
-
asPrompt: hal._.supportedMimeTypes.has(mime_type), file_name,
|
|
756
|
-
fileId: vn.file_id, mime_type, type: 'VIDEO',
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
if (m.video) {
|
|
760
|
-
const v = m.video;
|
|
761
|
-
const url = await getFileUrl(v.file_id);
|
|
762
|
-
const file_name = basename(url);
|
|
763
|
-
files.push({
|
|
764
|
-
asPrompt: hal._.supportedMimeTypes.has(v.mime_type), file_name,
|
|
765
|
-
fileId: v.file_id, mime_type: v.mime_type, type: 'VIDEO',
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
if (files.length) {
|
|
770
|
-
await ctx.ok(EMOJI_LOOK);
|
|
771
|
-
for (const f of files) {
|
|
772
|
-
if (!f.asPrompt && !f.ocrFunc) { continue; }
|
|
773
|
-
try {
|
|
774
|
-
const url = await getFileUrl(f.fileId);
|
|
775
|
-
const file = (await web.get(url, BUFFER_ENCODE)).content;
|
|
776
|
-
const analyze = async () => {
|
|
777
|
-
const content = utilitas.trim(utilitas.ensureArray(
|
|
778
|
-
await utilitas.ignoreErrFunc(async () => await f.ocrFunc(
|
|
779
|
-
file, BUFFER_ENCODE
|
|
780
|
-
), logOptions)
|
|
781
|
-
).filter(x => x).join('\n'));
|
|
782
|
-
content && ctx.collect(bot.lines([
|
|
783
|
-
'---', `file_name: ${f.file_name}`,
|
|
784
|
-
`mime_type: ${f.mime_type}`, `type: ${f.type}`,
|
|
785
|
-
'---',
|
|
786
|
-
content
|
|
787
|
-
]), 'VISION');
|
|
788
|
-
};
|
|
789
|
-
if (f.asPrompt) {
|
|
790
|
-
ctx.collect({
|
|
791
|
-
mime_type: f.mime_type, url, analyze,
|
|
792
|
-
data: utilitas.base64Encode(file, true),
|
|
793
|
-
}, 'PROMPT');
|
|
794
|
-
} else if (f.ocrFunc) { await analyze(); }
|
|
795
|
-
} catch (err) { return await ctx.er(err); }
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
await next();
|
|
799
|
-
},
|
|
800
|
-
}, {
|
|
801
|
-
run: true, priority: -8850, name: 'help', func: async (ctx, next) => {
|
|
802
|
-
const help = ctx._.info ? [ctx._.info] : [];
|
|
803
|
-
for (let i in ctx._.skills) {
|
|
804
|
-
if (ctx._.skills[i].hidden) { continue; }
|
|
805
|
-
const _help = [];
|
|
806
|
-
if (ctx._.skills[i].help) {
|
|
807
|
-
_help.push(ctx._.skills[i].help);
|
|
808
|
-
}
|
|
809
|
-
const cmdsx = {
|
|
810
|
-
...ctx._.skills[i].cmds || {},
|
|
811
|
-
...ctx._.skills[i].cmdx || {},
|
|
812
|
-
};
|
|
813
|
-
if (utilitas.countKeys(cmdsx)) {
|
|
814
|
-
_help.push(bot.lines([
|
|
815
|
-
'_🪄 Commands:_',
|
|
816
|
-
...Object.keys(cmdsx).map(x => `- /${x}: ${cmdsx[x]}`),
|
|
817
|
-
]));
|
|
818
|
-
}
|
|
819
|
-
if (utilitas.countKeys(ctx._.skills[i].args)) {
|
|
820
|
-
_help.push(bot.lines([
|
|
821
|
-
'_⚙️ Options:_',
|
|
822
|
-
...Object.keys(ctx._.skills[i].args).map(x => {
|
|
823
|
-
const _arg = ctx._.skills[i].args[x];
|
|
824
|
-
return `- \`${x}\`` + (_arg.short ? `(${_arg.short})` : '')
|
|
825
|
-
+ `, ${_arg.type}(${_arg.default ?? 'N/A'})`
|
|
826
|
-
+ (_arg.desc ? `: ${_arg.desc}` : '');
|
|
827
|
-
})
|
|
828
|
-
]));
|
|
829
|
-
}
|
|
830
|
-
_help.length && help.push(bot.lines([`*${i.toUpperCase()}*`, ..._help]));
|
|
831
|
-
}
|
|
832
|
-
await ctx.ok(lines2(help), { md: true });
|
|
833
|
-
}, help: bot.lines([
|
|
834
|
-
'¶ Basic syntax of this document:',
|
|
835
|
-
'Scheme for commands: /`COMMAND`: `DESCRIPTION`',
|
|
836
|
-
'Scheme for options: `OPTION`(`SHORT`), `TYPE`(`DEFAULT`): `DESCRIPTION`',
|
|
837
|
-
]), cmds: {
|
|
838
|
-
help: 'Show help message.',
|
|
839
|
-
},
|
|
840
|
-
}, {
|
|
841
|
-
run: true, priority: -8840, name: 'configuration', func: async (ctx, next) => {
|
|
842
|
-
let parsed = null;
|
|
843
|
-
switch (ctx.cmd.cmd) {
|
|
844
|
-
case 'toggle':
|
|
845
|
-
parsed = {};
|
|
846
|
-
Object.keys(await parseArgs(ctx.cmd.args)).map(x =>
|
|
847
|
-
parsed[x] = !ctx.session.config[x]);
|
|
848
|
-
case 'set':
|
|
849
|
-
try {
|
|
850
|
-
const _config = {
|
|
851
|
-
...ctx.session.config = {
|
|
852
|
-
...ctx.session.config, ...ctx.config = parsed
|
|
853
|
-
|| await parseArgs(ctx.cmd.args, ctx),
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
assert(utilitas.countKeys(ctx.config), 'No option matched.');
|
|
857
|
-
Object.keys(ctx.config).map(x => _config[x] += ' 🖋');
|
|
858
|
-
await ctx.sendConfig(_config, null, ctx);
|
|
859
|
-
} catch (err) {
|
|
860
|
-
await ctx.er(err.message || err);
|
|
861
|
-
}
|
|
862
|
-
break;
|
|
863
|
-
case 'reset':
|
|
864
|
-
ctx.session.config = ctx.config = {};
|
|
865
|
-
await ctx.complete();
|
|
866
|
-
break;
|
|
867
|
-
case 'factory':
|
|
868
|
-
sessions[ctx.chatId] = {}; // ctx.session = {}; will not work, because it's a reference.
|
|
869
|
-
await ctx.complete();
|
|
870
|
-
break;
|
|
871
|
-
}
|
|
872
|
-
}, help: bot.lines([
|
|
873
|
-
'¶ Configure the bot by UNIX/Linux CLI style.',
|
|
874
|
-
'Using [node:util.parseArgs](https://nodejs.org/docs/latest-v21.x/api/util.html#utilparseargsconfig) to parse arguments.',
|
|
875
|
-
]), cmds: {
|
|
876
|
-
toggle: 'Toggle configurations. Only works for boolean values.',
|
|
877
|
-
set: 'Usage: /set --`OPTION` `VALUE` -`SHORT`',
|
|
878
|
-
reset: 'Reset all configurations. Only erase `session.config`.',
|
|
879
|
-
factory: 'Factory reset all memory areas. Erase the whole `session`.',
|
|
880
|
-
}, args: {
|
|
881
|
-
chatty: {
|
|
882
|
-
type: 'string', short: 'c', default: ON,
|
|
883
|
-
desc: `\`(${BINARY_STRINGS.join(', ')})\` Enable/Disable chatty mode.`,
|
|
884
|
-
validate: utilitas.humanReadableBoolean,
|
|
885
|
-
},
|
|
886
|
-
},
|
|
887
|
-
}, {
|
|
888
|
-
run: true, priority: -8830, name: 'history', func: async (ctx, next) => {
|
|
889
|
-
if (ctx.type === 'callback_query') {
|
|
890
|
-
await ctx.deleteMessage(ctx.m.message_id);
|
|
891
|
-
}
|
|
892
|
-
const regex = '[-—]+skip=[0-9]*';
|
|
893
|
-
let result;
|
|
894
|
-
const keyWords = ctx.cmd.args.replace(new RegExp(regex, 'i'), '').trim();
|
|
895
|
-
let catchArgs = ctx.cmd.args.replace(new RegExp(`^.*(${regex}).*$`, 'i'), '$1');
|
|
896
|
-
catchArgs === ctx.cmd.args && (catchArgs = '');
|
|
897
|
-
const offset = ~~catchArgs.replace(/^.*=([0-9]*).*$/i, '$1');
|
|
898
|
-
if (!keyWords) { return await ctx.er('Topic is required.'); }
|
|
899
|
-
switch (hal._?.database?.provider) {
|
|
900
|
-
case dbio.MYSQL:
|
|
901
|
-
result = await hal._.database?.client?.query?.(
|
|
902
|
-
'SELECT *, MATCH(`distilled`) '
|
|
903
|
-
+ 'AGAINST(? IN NATURAL LANGUAGE MODE) AS `relevance` '
|
|
904
|
-
+ 'FROM ?? WHERE `bot_id` = ? AND `chat_id` = ? '
|
|
905
|
-
+ 'HAVING relevance > 0 '
|
|
906
|
-
+ 'ORDER BY `relevance` DESC, `id` DESC '
|
|
907
|
-
+ `LIMIT ${SEARCH_LIMIT} OFFSET ?`,
|
|
908
|
-
[keyWords, table, ctx.botInfo.id, ctx.chatId, offset]
|
|
909
|
-
);
|
|
910
|
-
break;
|
|
911
|
-
case dbio.POSTGRESQL:
|
|
912
|
-
globalThis.debug = 2;
|
|
913
|
-
const vector = await hal._.embedding(keyWords);
|
|
914
|
-
result = await hal._.database?.client?.query?.(
|
|
915
|
-
`SELECT * FROM ${table} WHERE bot_id = $1 AND chat_id = $2`
|
|
916
|
-
+ ` ORDER BY distilled_vector <=> $3 ASC`
|
|
917
|
-
+ ` LIMIT ${SEARCH_LIMIT} OFFSET $4`, [
|
|
918
|
-
ctx.botInfo.id, ctx.chatId,
|
|
919
|
-
await dbio.encodeVector(vector), offset
|
|
920
|
-
]);
|
|
921
|
-
break;
|
|
922
|
-
}
|
|
923
|
-
for (const i in result) {
|
|
924
|
-
const content = bot.lines([
|
|
925
|
-
...result[i].response_text ? [
|
|
926
|
-
`- ↩️ ${compactLimit(result[i].response_text)}`
|
|
927
|
-
] : [],
|
|
928
|
-
`- ${utilitas.getTimeIcon(result[i].created_at)} `
|
|
929
|
-
+ `${result[i].created_at.toLocaleString()}`,
|
|
930
|
-
]);
|
|
931
|
-
ctx.done.push(await reply(ctx, true, content, {
|
|
932
|
-
reply_parameters: {
|
|
933
|
-
message_id: result[i].message_id,
|
|
934
|
-
}, disable_notification: ~~i > 0,
|
|
935
|
-
}));
|
|
936
|
-
await ctx.timeout();
|
|
937
|
-
}
|
|
938
|
-
ctx.done.push(await reply(ctx, true, '___', getExtra(ctx, {
|
|
939
|
-
buttons: [{
|
|
940
|
-
label: '🔍 More',
|
|
941
|
-
text: `/search@${ctx.botInfo.username} ${keyWords} `
|
|
942
|
-
+ `--skip=${offset + result.length}`,
|
|
943
|
-
}]
|
|
944
|
-
})));
|
|
945
|
-
result.length || await ctx.er('No more records.');
|
|
946
|
-
}, help: bot.lines([
|
|
947
|
-
'¶ Search history.',
|
|
948
|
-
'Example 1: /search Answer to the Ultimate Question',
|
|
949
|
-
'Example 2: /search Answer to the Ultimate Question --skip=10',
|
|
950
|
-
]), cmds: {
|
|
951
|
-
search: 'Usage: /search `ANYTHING` --skip=`OFFSET`',
|
|
952
|
-
}
|
|
953
|
-
}, {
|
|
954
|
-
run: true, priority: 8960, name: 'text-to-speech', func: async (ctx, next) => {
|
|
955
|
-
await ctx.shouldSpeech();
|
|
956
|
-
await next();
|
|
957
|
-
}, help: bot.lines([
|
|
958
|
-
'¶ When enabled, the bot will speak out the answer if available.',
|
|
959
|
-
'Example 1: /set --tts on',
|
|
960
|
-
'Example 2: /set --tts off',
|
|
961
|
-
]), args: {
|
|
962
|
-
tts: {
|
|
963
|
-
type: 'string', short: 't', default: ON,
|
|
964
|
-
desc: `\`(${BINARY_STRINGS.join(', ')})\` Enable/Disable TTS. Default \`${ON}\` except in groups.`,
|
|
965
|
-
validate: utilitas.humanReadableBoolean,
|
|
966
|
-
},
|
|
967
|
-
},
|
|
968
|
-
}];
|
|
109
|
+
const json = obj => bot.lines([
|
|
110
|
+
'```json', utilitas.prettyJson(obj, jsonOptions).replaceAll('```', ''), '```'
|
|
111
|
+
]);
|
|
969
112
|
|
|
970
113
|
const establish = module => {
|
|
971
114
|
if (!module.run) { return; }
|
|
972
|
-
assert(module?.func, '
|
|
973
|
-
|
|
115
|
+
assert(module?.func, 'Pipeline function is required.', 500);
|
|
116
|
+
_.pipeline[module.name || (module.name = uuidv4())] = {
|
|
974
117
|
args: module.args || {},
|
|
975
118
|
cmds: module.cmds || {},
|
|
976
119
|
cmdx: module.cmdx,
|
|
977
120
|
help: module.help || '',
|
|
978
121
|
hidden: !!module.hidden,
|
|
979
122
|
};
|
|
980
|
-
|
|
123
|
+
_.args = { ..._.args, ...module.args || {} };
|
|
981
124
|
for (let sub of ['cmds', 'cmdx']) {
|
|
982
|
-
Object.keys(module[sub] || {}).map(command =>
|
|
125
|
+
Object.keys(module[sub] || {}).map(command => _.cmds.push(
|
|
983
126
|
newCommand(command, module[sub][command])
|
|
984
127
|
));
|
|
985
128
|
}
|
|
986
129
|
log(`Establishing: ${module.name} (${module.priority})`, { force: true });
|
|
987
|
-
return
|
|
130
|
+
return _.bot.use(utilitas.countKeys(module.cmds) && !module.cmdx ? async (ctx, next) => {
|
|
988
131
|
for (let c in module.cmds) {
|
|
989
|
-
if (utilitas.insensitiveCompare(ctx.cmd?.cmd, c)) {
|
|
990
|
-
ctx.skipMemorize();
|
|
132
|
+
if (utilitas.insensitiveCompare(ctx._?.cmd?.cmd, c)) {
|
|
991
133
|
return await module.func(ctx, next);
|
|
992
134
|
}
|
|
993
135
|
}
|
|
994
|
-
return next();
|
|
136
|
+
return await next();
|
|
995
137
|
} : module.func);
|
|
996
138
|
};
|
|
997
139
|
|
|
998
140
|
const parseArgs = async (args, ctx) => {
|
|
999
141
|
const { values, tokens } = _parseArgs({
|
|
1000
142
|
args: utilitas.splitArgs((args || '').replaceAll('—', '--')),
|
|
1001
|
-
options:
|
|
143
|
+
options: _.args, tokens: true
|
|
1002
144
|
});
|
|
1003
145
|
const result = {};
|
|
1004
146
|
for (let x of tokens) {
|
|
1005
|
-
result[x.name] =
|
|
1006
|
-
? await
|
|
147
|
+
result[x.name] = _.args[x.name]?.validate
|
|
148
|
+
? await _.args[x.name].validate(values[x.name], ctx)
|
|
1007
149
|
: values[x.name];
|
|
1008
150
|
}
|
|
1009
151
|
return result;
|
|
1010
152
|
};
|
|
1011
153
|
|
|
154
|
+
const packMessage = (messages) => messages.map(x => ({
|
|
155
|
+
message_id: x.message_id, score: x.score,
|
|
156
|
+
request: x.received_text, response: x.response_text,
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
const recall = async (sessionId, keyword, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
160
|
+
assert(sessionId, 'Session ID is required.');
|
|
161
|
+
let [result, _limit, exclude] = [
|
|
162
|
+
[], _.rerank ? SUB_LIMIT : limit,
|
|
163
|
+
(options?.exclude || []).map(x => `${~~x}`),
|
|
164
|
+
];
|
|
165
|
+
if (!keyword) { return result; }
|
|
166
|
+
switch (_.storage?.provider) {
|
|
167
|
+
case dbio.MYSQL:
|
|
168
|
+
result = await _.storage?.client?.query?.(
|
|
169
|
+
'SELECT *, MATCH(`distilled`) '
|
|
170
|
+
+ 'AGAINST(? IN NATURAL LANGUAGE MODE) AS `relevance` '
|
|
171
|
+
+ "FROM ?? WHERE `bot_id` = ? AND `chat_id` = ? "
|
|
172
|
+
+ "AND `received_text` != '' "
|
|
173
|
+
+ "AND `received_text` NOT LIKE '/%' "
|
|
174
|
+
+ "AND `response_text` != '' HAVING relevance > 0 "
|
|
175
|
+
+ (exclude.length ? `AND \`message_id\` NOT IN (${exclude.join(',')}) ` : '')
|
|
176
|
+
+ 'ORDER BY `relevance` DESC '
|
|
177
|
+
+ `LIMIT ${_limit} OFFSET ?`,
|
|
178
|
+
[keyword, table, _.bot.botInfo.id, sessionId, offset]
|
|
179
|
+
);
|
|
180
|
+
break;
|
|
181
|
+
case dbio.POSTGRESQL:
|
|
182
|
+
// globalThis.debug = 2;
|
|
183
|
+
const vector = await dbio.encodeVector(await _.embed(keyword));
|
|
184
|
+
result = await _.storage?.client?.query?.(
|
|
185
|
+
`SELECT *, (1 - (distilled_vector <=> $1)) as relevance `
|
|
186
|
+
+ `FROM ${table} WHERE bot_id = $2 AND chat_id = $3 `
|
|
187
|
+
+ `AND received_text != '' `
|
|
188
|
+
+ `AND received_text NOT LIKE '/%' `
|
|
189
|
+
+ `AND response_text != '' `
|
|
190
|
+
+ (exclude.length ? `AND message_id NOT IN (${exclude.join(',')}) ` : '')
|
|
191
|
+
+ `ORDER BY (distilled_vector <=> $1) ASC `
|
|
192
|
+
+ `LIMIT ${_limit} OFFSET $4`,
|
|
193
|
+
[vector, _.bot.botInfo.id, sessionId, offset]
|
|
194
|
+
);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
return await rerank(keyword, result, offset, limit, options);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const getContext = async (sessionId, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
201
|
+
assert(sessionId, 'Session ID is required.');
|
|
202
|
+
let result = [];
|
|
203
|
+
switch (_.storage?.provider) {
|
|
204
|
+
case dbio.MYSQL:
|
|
205
|
+
result = await _.storage?.client?.query?.(
|
|
206
|
+
'SELECT * FROM ?? WHERE `bot_id` = ? AND `chat_id` = ? '
|
|
207
|
+
+ "AND `received_text` != '' "
|
|
208
|
+
+ "AND `received_text` NOT LIKE '/%' "
|
|
209
|
+
+ "AND `response_text` != '' "
|
|
210
|
+
+ `ORDER BY \`created_at\` DESC LIMIT ${limit} OFFSET ?`,
|
|
211
|
+
[table, _.bot.botInfo.id, sessionId, offset]
|
|
212
|
+
);
|
|
213
|
+
break;
|
|
214
|
+
case dbio.POSTGRESQL:
|
|
215
|
+
result = await _.storage?.client?.query?.(
|
|
216
|
+
`SELECT * FROM ${table} WHERE bot_id = $1 AND chat_id = $2 `
|
|
217
|
+
+ `AND received_text != '' `
|
|
218
|
+
+ `AND received_text NOT LIKE '/%' `
|
|
219
|
+
+ `AND response_text != '' `
|
|
220
|
+
+ `ORDER BY created_at DESC LIMIT ${limit} OFFSET $3`,
|
|
221
|
+
[_.bot.botInfo.id, sessionId, offset]
|
|
222
|
+
);
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
return packMessage(result);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const rerank = async (keyword, result, offset = 0, limit = SEARCH_LIMIT, options = {}) => {
|
|
229
|
+
if (result.length && _.rerank) {
|
|
230
|
+
const keys = {};
|
|
231
|
+
const _result = [];
|
|
232
|
+
for (const x of result) {
|
|
233
|
+
if (!keys[x.distilled]) {
|
|
234
|
+
keys[x.distilled] = true;
|
|
235
|
+
_result.push(x);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const resp = await _.rerank(keyword, _result.map(x => x.distilled));
|
|
239
|
+
resp.map(x => result[x.index].score = x.score);
|
|
240
|
+
result.sort((a, b) => b.score - a.score);
|
|
241
|
+
result = result.filter(
|
|
242
|
+
x => x.score > RELEVANCE
|
|
243
|
+
).slice(offset, offset + limit);
|
|
244
|
+
}
|
|
245
|
+
return packMessage(result);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const extendSessionStorage = storage => storage && storage.client && {
|
|
249
|
+
get: async (id, options) => {
|
|
250
|
+
const CONTEXT_LIMIT_WITH_RAG = 5;
|
|
251
|
+
let resp = await storage.get(id);
|
|
252
|
+
if (resp) {
|
|
253
|
+
const ctxResp = await getContext(id, undefined, undefined);
|
|
254
|
+
const ragResp = await recall(
|
|
255
|
+
id, options?.prompt, undefined, undefined, {
|
|
256
|
+
exclude: ctxResp.map(x => x.message_id),
|
|
257
|
+
});
|
|
258
|
+
ctxResp.sort((a, b) => ~~a.message_id - ~~b.message_id);
|
|
259
|
+
ragResp.sort((a, b) => a.score - b.score);
|
|
260
|
+
resp.messages = [...ragResp, ...ragResp?.length ? ctxResp.slice(
|
|
261
|
+
ctxResp.length - CONTEXT_LIMIT_WITH_RAG
|
|
262
|
+
) : ctxResp];
|
|
263
|
+
} else {
|
|
264
|
+
resp = { messages: [] };
|
|
265
|
+
}
|
|
266
|
+
// print(resp);
|
|
267
|
+
return resp;
|
|
268
|
+
},
|
|
269
|
+
set: async (id, session, options) => await storage.set(id, session, options),
|
|
270
|
+
};
|
|
271
|
+
|
|
1012
272
|
const init = async (options) => {
|
|
1013
273
|
if (options) {
|
|
1014
|
-
|
|
274
|
+
const { ais } = await alan.initChat({
|
|
275
|
+
sessions: extendSessionStorage(options?.storage),
|
|
276
|
+
});
|
|
1015
277
|
if (callosum.isPrimary) {
|
|
278
|
+
const cmds = options?.cmds || [];
|
|
279
|
+
// config multimodal engines
|
|
280
|
+
const supportedMimeTypes = new Set(ais.map(x => {
|
|
281
|
+
// init instant ai selection
|
|
282
|
+
cmds.push(newCommand(`ai_${x.id}`, `${x.name}: ${x.features}`));
|
|
283
|
+
return x.model;
|
|
284
|
+
}).map(x => x.supportedMimeTypes || []).flat().map(x => x.toLowerCase()));
|
|
285
|
+
const _bot = await bot.init(options);
|
|
1016
286
|
const pkg = await utilitas.which();
|
|
1017
|
-
|
|
1018
|
-
lorem = new (await utilitas.need('lorem-ipsum')).LoremIpsum;
|
|
1019
|
-
hal._ = {
|
|
287
|
+
_ = {
|
|
1020
288
|
args: { ...options?.args || {} },
|
|
1021
289
|
auth: Function.isFunction(options?.auth) && options.auth,
|
|
1022
|
-
|
|
290
|
+
bot: _bot,
|
|
291
|
+
chatType: new Set(options?.chatType || [MENTION, PRIVATE]), // ignore GROUP, CHANNEL by default
|
|
1023
292
|
cmds: [...options?.cmds || []],
|
|
293
|
+
embed: options?.embed,
|
|
1024
294
|
hello: options?.hello || HELLO,
|
|
1025
295
|
help: { ...options?.help || {} },
|
|
1026
296
|
homeGroup: options?.homeGroup,
|
|
1027
|
-
info: options?.info || bot.lines([`[${
|
|
1028
|
-
|
|
297
|
+
info: options?.info || bot.lines([`[${bot.BOT} ${pkg.title}](${pkg.homepage})`, pkg.description]),
|
|
298
|
+
lang: options?.lang,
|
|
299
|
+
pipeline: options?.pipeline || {},
|
|
1029
300
|
private: options?.private && new Set(options.private),
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
vision: options?.vision,
|
|
1034
|
-
supportedMimeTypes: options?.supportedMimeTypes || [],
|
|
1035
|
-
database: options?.database,
|
|
1036
|
-
memorize: options?.memorize,
|
|
1037
|
-
embedding: options?.embedding,
|
|
301
|
+
rerank: options?.rerank,
|
|
302
|
+
storage: options?.storage,
|
|
303
|
+
supportedMimeTypes,
|
|
1038
304
|
};
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
dbResult.push(await hal._.database.client.query(...act));
|
|
1059
|
-
}
|
|
1060
|
-
} catch (e) { console.error(e); }
|
|
305
|
+
const mods = [];
|
|
306
|
+
if (_.storage) {
|
|
307
|
+
assert([
|
|
308
|
+
dbio.MYSQL, dbio.POSTGRESQL, storage.FILE
|
|
309
|
+
].includes(_.storage?.provider), 'Invalid storage provider.');
|
|
310
|
+
if ([
|
|
311
|
+
dbio.MYSQL, dbio.POSTGRESQL
|
|
312
|
+
].includes(_.storage?.provider)) {
|
|
313
|
+
assert(
|
|
314
|
+
_.storage?.client?.query && _.storage?.client?.upsert,
|
|
315
|
+
'Invalid storage client.'
|
|
316
|
+
);
|
|
317
|
+
const dbResult = [];
|
|
318
|
+
try {
|
|
319
|
+
for (const act of initSql[_.storage?.provider]) {
|
|
320
|
+
dbResult.push(await _.storage.client.query(...act));
|
|
321
|
+
}
|
|
322
|
+
} catch (e) { console.error(e); }
|
|
323
|
+
}
|
|
1061
324
|
}
|
|
1062
|
-
for (let
|
|
1063
|
-
log(`
|
|
1064
|
-
const files = (readdirSync(
|
|
325
|
+
for (let pipeline of utilitas.ensureArray(options?.pipelinePath || [])) {
|
|
326
|
+
log(`PIPELINE: ${pipeline}`);
|
|
327
|
+
const files = (readdirSync(pipeline) || []).filter(
|
|
1065
328
|
file => /\.mjs$/i.test(file) && !file.startsWith('.')
|
|
1066
329
|
);
|
|
1067
330
|
for (let f of files) {
|
|
1068
|
-
const m = await import(join(
|
|
331
|
+
const m = await import(join(pipeline, f));
|
|
1069
332
|
mods.push({ ...m, name: m.name || f.replace(/^(.*)\.mjs$/i, '$1') });
|
|
1070
333
|
}
|
|
1071
334
|
}
|
|
1072
335
|
mods.sort((x, y) => ~~x.priority - ~~y.priority).map(establish);
|
|
1073
|
-
assert(mods.length, 'Invalid
|
|
336
|
+
assert(mods.length, 'Invalid pipeline.', 501);
|
|
1074
337
|
await parseArgs(); // Validate args options.
|
|
1075
338
|
}
|
|
1076
339
|
}
|
|
1077
|
-
assert(
|
|
1078
|
-
return
|
|
340
|
+
assert(_, 'Bot have not been initialized.', 501);
|
|
341
|
+
return _;
|
|
1079
342
|
};
|
|
1080
343
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
344
|
export default init;
|
|
1084
345
|
export {
|
|
1085
|
-
_NEED,
|
|
1086
346
|
BINARY_STRINGS,
|
|
347
|
+
BOT_COMMAND,
|
|
348
|
+
CHANNEL,
|
|
349
|
+
CHECK,
|
|
1087
350
|
COMMAND_DESCRIPTION_LENGTH,
|
|
1088
351
|
COMMAND_LENGTH,
|
|
1089
352
|
COMMAND_LIMIT,
|
|
1090
|
-
|
|
1091
|
-
EMOJI_SPEECH,
|
|
1092
|
-
GROUP_LIMIT,
|
|
353
|
+
GROUP,
|
|
1093
354
|
HELLO,
|
|
1094
|
-
|
|
355
|
+
MENTION,
|
|
356
|
+
OFF,
|
|
357
|
+
ON,
|
|
358
|
+
PRIVATE,
|
|
359
|
+
SEARCH_LIMIT,
|
|
360
|
+
parseArgs,
|
|
361
|
+
_,
|
|
1095
362
|
end,
|
|
363
|
+
getContext,
|
|
1096
364
|
init,
|
|
1097
|
-
|
|
365
|
+
json,
|
|
366
|
+
logOptions,
|
|
1098
367
|
newCommand,
|
|
1099
368
|
oList,
|
|
369
|
+
recall,
|
|
370
|
+
rerank,
|
|
371
|
+
table,
|
|
1100
372
|
uList
|
|
1101
373
|
};
|