halbot 1995.1.2 → 1995.1.4
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/lib/hal.mjs +16 -19
- package/package.json +1 -1
- package/pipeline/010_broca.mjs +3 -3
- package/pipeline/020_cmd.mjs +8 -2
- package/pipeline/030_echo.mjs +3 -3
- package/pipeline/040_help.mjs +5 -8
- package/pipeline/060_config.mjs +7 -5
- package/pipeline/080_history.mjs +4 -3
- package/pipeline/090_ai.mjs +14 -15
- package/pipeline/100_chat.mjs +44 -54
package/lib/hal.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { alan, bot, callosum, dbio, storage, utilitas, } from 'utilitas';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { parseArgs as _parseArgs } from 'node:util';
|
|
4
4
|
import { readdirSync } from 'fs';
|
|
5
|
+
import { ignoreErrFunc } from 'utilitas/lib/utilitas.mjs';
|
|
5
6
|
|
|
6
7
|
// 👇 https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this
|
|
7
8
|
const [ // https://limits.tginfo.me/en
|
|
@@ -111,30 +112,21 @@ const json = obj => bot.lines([
|
|
|
111
112
|
]);
|
|
112
113
|
|
|
113
114
|
const establish = module => {
|
|
114
|
-
if (!module.run) { return; }
|
|
115
115
|
assert(module?.func, 'Pipeline function is required.', 500);
|
|
116
116
|
_.pipeline[module.name || (module.name = uuidv4())] = {
|
|
117
|
-
args: module.args || {},
|
|
118
|
-
cmds: module.cmds || {},
|
|
119
|
-
cmdx: module.cmdx,
|
|
120
|
-
help: module.help || '',
|
|
117
|
+
help: module.help || '', args: module.args || {},
|
|
121
118
|
hidden: !!module.hidden,
|
|
122
119
|
};
|
|
123
120
|
_.args = { ..._.args, ...module.args || {} };
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
));
|
|
128
|
-
}
|
|
121
|
+
Object.keys(module?.cmds || {}).map(command => _.cmds.push(
|
|
122
|
+
newCommand(command, module?.cmds[command])
|
|
123
|
+
));
|
|
129
124
|
log(`Establishing: ${module.name} (${module.priority})`, { force: true });
|
|
130
|
-
return _.bot.use(utilitas.countKeys(module.cmds)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
return await next();
|
|
137
|
-
} : module.func);
|
|
125
|
+
return _.bot.use(utilitas.countKeys(module.cmds) ? (async (ctx, next) =>
|
|
126
|
+
await (utilitas.insensitiveHas(
|
|
127
|
+
Object.keys(module.cmds), ctx._?.cmd?.cmd
|
|
128
|
+
) || module.run ? module.func(ctx, next) : next())
|
|
129
|
+
) : module.func);
|
|
138
130
|
};
|
|
139
131
|
|
|
140
132
|
const parseArgs = async (args, ctx) => {
|
|
@@ -269,6 +261,11 @@ const extendSessionStorage = storage => storage && storage.client && {
|
|
|
269
261
|
set: async (id, session, options) => await storage.set(id, session, options),
|
|
270
262
|
};
|
|
271
263
|
|
|
264
|
+
const subconscious = {
|
|
265
|
+
name: 'Subconscious', run: true, priority: 0,
|
|
266
|
+
func: async (_, next) => ignoreErrFunc(next, logOptions), // non-blocking
|
|
267
|
+
};
|
|
268
|
+
|
|
272
269
|
const init = async (options) => {
|
|
273
270
|
if (options) {
|
|
274
271
|
const { ais } = await alan.initChat({
|
|
@@ -302,7 +299,6 @@ const init = async (options) => {
|
|
|
302
299
|
storage: options?.storage,
|
|
303
300
|
supportedMimeTypes,
|
|
304
301
|
};
|
|
305
|
-
const mods = [];
|
|
306
302
|
if (_.storage) {
|
|
307
303
|
assert([
|
|
308
304
|
dbio.MYSQL, dbio.POSTGRESQL, storage.FILE
|
|
@@ -322,6 +318,7 @@ const init = async (options) => {
|
|
|
322
318
|
} catch (e) { console.error(e); }
|
|
323
319
|
}
|
|
324
320
|
}
|
|
321
|
+
const mods = [subconscious];
|
|
325
322
|
for (let pipeline of utilitas.ensureArray(options?.pipelinePath || [])) {
|
|
326
323
|
log(`PIPELINE: ${pipeline}`);
|
|
327
324
|
const files = (readdirSync(pipeline) || []).filter(
|
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": "1995.1.
|
|
4
|
+
"version": "1995.1.4",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/Leask/halbot",
|
|
7
7
|
"type": "module",
|
package/pipeline/010_broca.mjs
CHANGED
|
@@ -135,11 +135,11 @@ const ctxExt = ctx => {
|
|
|
135
135
|
for (let i in pages) {
|
|
136
136
|
const lastPage = ~~i === pages.length - 1;
|
|
137
137
|
const shouldExtra = options?.lastMessageId || lastPage;
|
|
138
|
-
if (options?.
|
|
138
|
+
if (options?.processing && !options?.lastMessageId
|
|
139
139
|
&& pageMap[pageIds[~~i]]?.text === pages[i]) { continue; }
|
|
140
|
-
if (options?.
|
|
140
|
+
if (options?.processing && !pageIds[~~i]) { // progress: new page, reply text
|
|
141
141
|
await ctx.resp(pages[i], false, extra);
|
|
142
|
-
} else if (options?.
|
|
142
|
+
} else if (options?.processing) { // progress: ongoing, edit text
|
|
143
143
|
await ctx.edit(
|
|
144
144
|
pageIds[~~i], pages[i], false, shouldExtra ? extra : {}
|
|
145
145
|
);
|
package/pipeline/020_cmd.mjs
CHANGED
|
@@ -72,6 +72,12 @@ const action = async (ctx, next) => {
|
|
|
72
72
|
await next();
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
export const { name, run, priority, func,
|
|
76
|
-
name: _name, run: true, priority: 20, func: action,
|
|
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
|
+
},
|
|
77
83
|
};
|
package/pipeline/030_echo.mjs
CHANGED
|
@@ -38,7 +38,7 @@ const action = async (ctx, next) => {
|
|
|
38
38
|
await ctx.ok(bot.EMOJI_THINKING);
|
|
39
39
|
for (let i = 0; i < 2; i++) {
|
|
40
40
|
await ctx.timeout();
|
|
41
|
-
await ctx.ok(ipsum(), { ...extra,
|
|
41
|
+
await ctx.ok(ipsum(), { ...extra, processing: true });
|
|
42
42
|
}
|
|
43
43
|
await ctx.timeout();
|
|
44
44
|
await ctx.ok(ipsum(), { ...extra, md });
|
|
@@ -54,9 +54,9 @@ const action = async (ctx, next) => {
|
|
|
54
54
|
await ctx.ok(resp, { md });
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
export const { _NEED, name,
|
|
57
|
+
export const { _NEED, name, hidden, priority, func, help, cmds } = {
|
|
58
58
|
_NEED: ['lorem-ipsum'],
|
|
59
|
-
name: 'Echo',
|
|
59
|
+
name: 'Echo', hidden: true, priority: 30, func: action,
|
|
60
60
|
help: bot.lines([
|
|
61
61
|
'¶ Basic behaviors for debug only.',
|
|
62
62
|
]),
|
package/pipeline/040_help.mjs
CHANGED
|
@@ -10,14 +10,11 @@ const action = async (ctx, next) => {
|
|
|
10
10
|
if (hal._.pipeline[i].help) {
|
|
11
11
|
_help.push(hal._.pipeline[i].help);
|
|
12
12
|
}
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
...hal._.pipeline[i].cmdx || {},
|
|
16
|
-
};
|
|
17
|
-
if (utilitas.countKeys(cmdsx)) {
|
|
13
|
+
const cmds = hal._.pipeline[i].cmds || {};
|
|
14
|
+
if (utilitas.countKeys(cmds)) {
|
|
18
15
|
_help.push(bot.lines([
|
|
19
16
|
'_🪄 Commands:_',
|
|
20
|
-
...Object.keys(
|
|
17
|
+
...Object.keys(cmds).map(x => `- /${x}: ${cmds[x]}`),
|
|
21
18
|
]));
|
|
22
19
|
}
|
|
23
20
|
if (utilitas.countKeys(hal._.pipeline[i].args)) {
|
|
@@ -36,8 +33,8 @@ const action = async (ctx, next) => {
|
|
|
36
33
|
await ctx.ok(lines2(help), { md: true });
|
|
37
34
|
};
|
|
38
35
|
|
|
39
|
-
export const { name,
|
|
40
|
-
name: 'Help',
|
|
36
|
+
export const { name, priority, func, help, cmds } = {
|
|
37
|
+
name: 'Help', priority: 40, func: action,
|
|
41
38
|
help: bot.lines([
|
|
42
39
|
'¶ Basic syntax of this document:',
|
|
43
40
|
'Scheme for commands: /`COMMAND`: `DESCRIPTION`',
|
package/pipeline/060_config.mjs
CHANGED
|
@@ -11,7 +11,7 @@ const ctxExt = ctx => {
|
|
|
11
11
|
const action = async (ctx, next) => {
|
|
12
12
|
ctxExt(ctx);
|
|
13
13
|
let parsed = null;
|
|
14
|
-
switch (ctx._.cmd
|
|
14
|
+
switch (ctx._.cmd.cmd) {
|
|
15
15
|
case 'lang':
|
|
16
16
|
if (!ctx._.cmd.args) {
|
|
17
17
|
return await ctx.ok('Please specify a language.');
|
|
@@ -54,8 +54,8 @@ const action = async (ctx, next) => {
|
|
|
54
54
|
await next();
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
export const { name,
|
|
58
|
-
name: 'Config',
|
|
57
|
+
export const { name, priority, func, help, cmds, args } = {
|
|
58
|
+
name: 'Config', priority: 60, func: action,
|
|
59
59
|
help: bot.lines([
|
|
60
60
|
'¶ Configure the bot by UNIX/Linux CLI style.',
|
|
61
61
|
'Using [node:util.parseArgs](https://nodejs.org/api/util.html#utilparseargsconfig) to parse arguments.',
|
|
@@ -64,12 +64,14 @@ export const { name, run, priority, func, help, cmdx, args } = {
|
|
|
64
64
|
'¶ When enabled, the bot will speak out the answer if available.',
|
|
65
65
|
'Example 1: /set --tts on',
|
|
66
66
|
'Example 2: /set --tts off',
|
|
67
|
-
]),
|
|
67
|
+
]),
|
|
68
|
+
cmds: {
|
|
68
69
|
lang: 'Set your default language: /lang `LANG`',
|
|
69
70
|
toggle: 'Toggle configurations. Only works for boolean values.',
|
|
70
71
|
set: 'Usage: /set --`OPTION` `VALUE` -`SHORT`',
|
|
71
72
|
reset: 'Reset configurations.',
|
|
72
|
-
},
|
|
73
|
+
},
|
|
74
|
+
args: {
|
|
73
75
|
chatty: {
|
|
74
76
|
type: 'string', short: 'c', default: hal.ON,
|
|
75
77
|
desc: `\`(${hal.BINARY_STRINGS.join(', ')})\` Enable/Disable chatty mode.`,
|
package/pipeline/080_history.mjs
CHANGED
|
@@ -99,13 +99,14 @@ const action = async (ctx, next) => {
|
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
export const { name, run, priority, func, help,
|
|
102
|
+
export const { name, run, priority, func, help, cmds } = {
|
|
103
103
|
name: 'History', run: true, priority: 80, func: action,
|
|
104
104
|
help: bot.lines([
|
|
105
105
|
'¶ Search history.',
|
|
106
106
|
'Example 1: /search Answer to the Ultimate Question',
|
|
107
107
|
'Example 2: /search Answer to the Ultimate Question --skip=10',
|
|
108
|
-
]),
|
|
108
|
+
]),
|
|
109
|
+
cmds: {
|
|
109
110
|
search: 'Usage: /search `ANYTHING` --skip=`OFFSET`',
|
|
110
|
-
}
|
|
111
|
+
},
|
|
111
112
|
};
|
package/pipeline/090_ai.mjs
CHANGED
|
@@ -5,17 +5,19 @@ const TOP = 'top';
|
|
|
5
5
|
|
|
6
6
|
const listAIs = async ctx => {
|
|
7
7
|
const lastMessageId = ctx?.update?.callback_query?.message?.message_id;
|
|
8
|
-
const message =
|
|
9
|
-
+ hal.uList(Object.entries(alan.FEATURE_ICONS).
|
|
10
|
-
x =>
|
|
11
|
-
)
|
|
8
|
+
const message = `*Time:* \n${new Date().toLocaleString()}\n\n`
|
|
9
|
+
+ `*Features:*\n` + hal.uList(Object.entries(alan.FEATURE_ICONS).filter(
|
|
10
|
+
x => x[0] !== 'hidden'
|
|
11
|
+
).map(
|
|
12
|
+
x => `${x[1]} \`${x[0]}\``
|
|
13
|
+
)) + `\n\n*AI${ais.length > 0 ? 's' : ''}:*\n`;
|
|
12
14
|
const buttons = ais.map((x, i) => ({
|
|
13
15
|
label: `${ctx._.session.config?.ai === x.id
|
|
14
16
|
|| (!ctx._.session.config?.ai && i === 0) ? `${hal.CHECK} `
|
|
15
|
-
: ''}${x.
|
|
17
|
+
: ''}${x.label}`,
|
|
16
18
|
text: `/set --ai=${x.id}`,
|
|
17
19
|
}));
|
|
18
|
-
return await ctx.ok(message, { lastMessageId, buttons });
|
|
20
|
+
return await ctx.ok(message, { lastMessageId, buttons, md: true });
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
const action = async (ctx, next) => {
|
|
@@ -43,11 +45,8 @@ const validateAi = async (val, ctx) => {
|
|
|
43
45
|
utilitas.throwError('No AI engine matched.');
|
|
44
46
|
};
|
|
45
47
|
|
|
46
|
-
export const { name, run, priority, func, help, args,
|
|
47
|
-
name: 'AI',
|
|
48
|
-
run: true,
|
|
49
|
-
priority: 90,
|
|
50
|
-
func: action,
|
|
48
|
+
export const { name, run, priority, func, help, args, cmds } = {
|
|
49
|
+
name: 'AI', run: true, priority: 90, func: action,
|
|
51
50
|
help: bot.lines([
|
|
52
51
|
'¶ Set initial prompt to the AI model.',
|
|
53
52
|
"Tip 1: Set `hello=''` to reset to default initial prompt.",
|
|
@@ -59,6 +58,10 @@ export const { name, run, priority, func, help, args, cmdx } = {
|
|
|
59
58
|
'Tip 5: `/[AI_ID]` Tell me a joke.',
|
|
60
59
|
'Tip 6: `/all` Use the top 3 AI models simultaneously, for current prompt.',
|
|
61
60
|
]),
|
|
61
|
+
cmds: {
|
|
62
|
+
ai: 'List all available AIs.',
|
|
63
|
+
all: 'Use the top 3 AI models simultaneously: /all Say hello to all AIs!',
|
|
64
|
+
},
|
|
62
65
|
args: {
|
|
63
66
|
hello: {
|
|
64
67
|
type: 'string', short: 's', default: 'Hello!',
|
|
@@ -70,8 +73,4 @@ export const { name, run, priority, func, help, args, cmdx } = {
|
|
|
70
73
|
validate: validateAi,
|
|
71
74
|
},
|
|
72
75
|
},
|
|
73
|
-
cmdx: {
|
|
74
|
-
ai: 'List all available AIs.',
|
|
75
|
-
all: 'Use the top 3 AI models simultaneously: /all Say hello to all AIs!',
|
|
76
|
-
}
|
|
77
76
|
};
|
package/pipeline/100_chat.mjs
CHANGED
|
@@ -3,63 +3,53 @@ import { alan } from '../index.mjs';
|
|
|
3
3
|
const _name = 'Chat';
|
|
4
4
|
const log = (c, o) => utilitas.log(c, _name, { time: 1, ...o || {} });
|
|
5
5
|
|
|
6
|
-
const generate = async (ctx) => {
|
|
7
|
-
try {
|
|
8
|
-
let [resp, extra, lock, sResp, lastMsg, lastSent] =
|
|
9
|
-
[null, { buttons: [] }, 1000 * 5, null, null, 0];
|
|
10
|
-
const ok = async options => {
|
|
11
|
-
const curTime = Date.now();
|
|
12
|
-
if (options?.processing && (
|
|
13
|
-
curTime - lastSent < ctx._.limit || lastMsg === resp.text
|
|
14
|
-
)) { return; }
|
|
15
|
-
[lastSent, lastMsg] = [curTime + lock, resp.text];
|
|
16
|
-
if (!options?.processing) {
|
|
17
|
-
(resp.annotations || []).map((x, i) => extra.buttons.push({
|
|
18
|
-
label: `${i + 1}. ${x.title}`, url: x.url,
|
|
19
|
-
}));
|
|
20
|
-
}
|
|
21
|
-
sResp = await ctx.ok(resp.text, {
|
|
22
|
-
...ctx._.keyboards ? { keyboards: ctx._.keyboards } : {},
|
|
23
|
-
md: true, ...extra, ...options || {},
|
|
24
|
-
});
|
|
25
|
-
lastSent = curTime;
|
|
26
|
-
return sResp;
|
|
27
|
-
};
|
|
28
|
-
resp = await alan.talk(ctx._.text, {
|
|
29
|
-
...ctx._, sessionId: ctx._.chatId, // THIS LINE IS IMPORTANT
|
|
30
|
-
stream: async rsp => { resp = rsp; ok({ processing: true }); }, // Never await, it will block the stream.
|
|
31
|
-
});
|
|
32
|
-
for (let image of resp?.images || []) {
|
|
33
|
-
await ctx.timeout();
|
|
34
|
-
await ctx.image(image.data, { caption: image.caption });
|
|
35
|
-
}
|
|
36
|
-
for (let video of resp?.videos || []) {
|
|
37
|
-
await ctx.timeout();
|
|
38
|
-
await ctx.video(video.data, { caption: video.caption });
|
|
39
|
-
}
|
|
40
|
-
for (let audio of resp?.audios || []) {
|
|
41
|
-
await ctx.timeout();
|
|
42
|
-
await ctx.audio(audio.data, { caption: audio.caption });
|
|
43
|
-
}
|
|
44
|
-
// print(resp);
|
|
45
|
-
await resp.text.trim() ? ok({ processing: false })
|
|
46
|
-
: ctx.deleteMessage(ctx._.done[0].message_id);
|
|
47
|
-
ctx._.request = resp.request;
|
|
48
|
-
ctx._.response = resp.response;
|
|
49
|
-
ctx.memorize && await ctx.memorize();
|
|
50
|
-
} catch (err) { log(err); }
|
|
51
|
-
};
|
|
52
|
-
|
|
53
6
|
const action = async (ctx, next) => {
|
|
54
|
-
ctx.
|
|
7
|
+
if (!ctx._.text && !ctx._.collected.length) { return await next(); }
|
|
8
|
+
let [resp, extra, lock, sResp, lastMsg, lastSent] =
|
|
9
|
+
[null, { buttons: [] }, 1000 * 5, null, null, 0];
|
|
10
|
+
const ok = async options => {
|
|
11
|
+
const curTime = Date.now();
|
|
12
|
+
if (options?.processing && (
|
|
13
|
+
curTime - lastSent < ctx._.limit || lastMsg === resp.text
|
|
14
|
+
)) { return; }
|
|
15
|
+
[lastSent, lastMsg] = [curTime + lock, resp.text];
|
|
16
|
+
if (!options?.processing) {
|
|
17
|
+
(resp.annotations || []).map((x, i) => extra.buttons.push({
|
|
18
|
+
label: `${i + 1}. ${x.title}`, url: x.url,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
sResp = await ctx.ok(resp.text, {
|
|
22
|
+
...ctx._.keyboards ? { keyboards: ctx._.keyboards } : {},
|
|
23
|
+
md: true, ...extra, ...options || {},
|
|
24
|
+
});
|
|
25
|
+
lastSent = curTime;
|
|
26
|
+
return sResp;
|
|
27
|
+
};
|
|
28
|
+
resp = await alan.talk(ctx._.text, {
|
|
29
|
+
...ctx._, sessionId: ctx._.chatId, // THIS LINE IS IMPORTANT
|
|
30
|
+
stream: async rsp => { resp = rsp; ok({ processing: true }); }, // Never await, it will block the stream.
|
|
31
|
+
});
|
|
32
|
+
for (let image of resp?.images || []) {
|
|
33
|
+
await ctx.timeout();
|
|
34
|
+
await ctx.image(image.data, { caption: image.caption });
|
|
35
|
+
}
|
|
36
|
+
for (let video of resp?.videos || []) {
|
|
37
|
+
await ctx.timeout();
|
|
38
|
+
await ctx.video(video.data, { caption: video.caption });
|
|
39
|
+
}
|
|
40
|
+
for (let audio of resp?.audios || []) {
|
|
41
|
+
await ctx.timeout();
|
|
42
|
+
await ctx.audio(audio.data, { caption: audio.caption });
|
|
43
|
+
}
|
|
44
|
+
// print(resp);
|
|
45
|
+
await resp.text.trim() ? ok({ processing: false })
|
|
46
|
+
: ctx.deleteMessage(ctx._.done[0].message_id);
|
|
47
|
+
ctx._.request = resp.request;
|
|
48
|
+
ctx._.response = resp.response;
|
|
49
|
+
ctx.memorize && await ctx.memorize();
|
|
55
50
|
await next();
|
|
56
|
-
if (!ctx._.text && !ctx._.collected.length) { return; }
|
|
57
|
-
generate(ctx);
|
|
58
51
|
};
|
|
59
52
|
|
|
60
53
|
export const { name, run, priority, func } = {
|
|
61
|
-
name: _name,
|
|
62
|
-
run: true,
|
|
63
|
-
priority: 100,
|
|
64
|
-
func: action,
|
|
54
|
+
name: _name, run: true, priority: 100, func: action,
|
|
65
55
|
};
|