halbot 1994.1.7 → 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/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": "1994.1.7",
4
+ "version": "1995.1.2",
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/genai": "^1.31.0",
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
- "js-tiktoken": "^1.0.21",
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": "^6.0.1",
50
- "utilitas": "^2000.3.26",
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,77 @@
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, priority, func, cmdx } = {
76
+ name: _name, run: true, priority: 20, func: action, cmdx: {}
77
+ };
@@ -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, run, hidden, priority, func, help, cmds } = {
58
+ _NEED: ['lorem-ipsum'],
59
+ name: 'Echo', run: true, 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,49 @@
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 cmdsx = {
14
+ ...hal._.pipeline[i].cmds || {},
15
+ ...hal._.pipeline[i].cmdx || {},
16
+ };
17
+ if (utilitas.countKeys(cmdsx)) {
18
+ _help.push(bot.lines([
19
+ '_🪄 Commands:_',
20
+ ...Object.keys(cmdsx).map(x => `- /${x}: ${cmdsx[x]}`),
21
+ ]));
22
+ }
23
+ if (utilitas.countKeys(hal._.pipeline[i].args)) {
24
+ _help.push(bot.lines([
25
+ '_⚙️ Options:_',
26
+ ...Object.keys(hal._.pipeline[i].args).map(x => {
27
+ const _arg = hal._.pipeline[i].args[x];
28
+ return `- \`${x}\`` + (_arg.short ? `(${_arg.short})` : '')
29
+ + `, ${_arg.type}(${_arg.default ?? 'N/A'})`
30
+ + (_arg.desc ? `: ${_arg.desc}` : '');
31
+ })
32
+ ]));
33
+ }
34
+ _help.length && help.push(bot.lines([`*${i.toUpperCase()}*`, ..._help]));
35
+ }
36
+ await ctx.ok(lines2(help), { md: true });
37
+ };
38
+
39
+ export const { name, run, priority, func, help, cmds } = {
40
+ name: 'Help', run: true, priority: 40, func: action,
41
+ help: bot.lines([
42
+ '¶ Basic syntax of this document:',
43
+ 'Scheme for commands: /`COMMAND`: `DESCRIPTION`',
44
+ 'Scheme for options: `OPTION`(`SHORT`), `TYPE`(`DEFAULT`): `DESCRIPTION`',
45
+ ]),
46
+ cmds: {
47
+ help: 'Show help message.',
48
+ },
49
+ };
@@ -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,84 @@
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, run, priority, func, help, cmdx, args } = {
58
+ name: 'Config', run: true, 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
+ ]), cmdx: {
68
+ lang: 'Set your default language: /lang `LANG`',
69
+ toggle: 'Toggle configurations. Only works for boolean values.',
70
+ set: 'Usage: /set --`OPTION` `VALUE` -`SHORT`',
71
+ reset: 'Reset configurations.',
72
+ }, args: {
73
+ chatty: {
74
+ type: 'string', short: 'c', default: hal.ON,
75
+ desc: `\`(${hal.BINARY_STRINGS.join(', ')})\` Enable/Disable chatty mode.`,
76
+ validate: utilitas.humanReadableBoolean,
77
+ },
78
+ tts: {
79
+ type: 'string', short: 't', default: hal.ON,
80
+ desc: `\`(${hal.BINARY_STRINGS.join(', ')})\` Enable/Disable TTS. Default \`${hal.ON}\` except in groups.`,
81
+ validate: utilitas.humanReadableBoolean,
82
+ },
83
+ },
84
+ };