halbot 1990.1.65 → 1990.1.67

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 CHANGED
@@ -103,10 +103,10 @@ All supported configuration fields:
103
103
  "help": "[[help information]]",
104
104
 
105
105
  // OPTIONAL, object.
106
- // Session storage, support MariaDB/MySQL and Redis for now.
106
+ // Sessions/conversations storage, support MariaDB/MySQL and Redis for now.
107
107
  // If omitted, the bot will use memory storage and sync to this file.
108
108
  // Example: (Compatibility: https://github.com/sidorares/node-mysql2)
109
- "session": {
109
+ "storage": {
110
110
  "type": "[["MARIADB" || "MYSQL"]]",
111
111
  "host": "[[DATABASE HOST]]",
112
112
  "database": "[[DATABASE NAME]]",
@@ -115,7 +115,7 @@ All supported configuration fields:
115
115
  ...[[OTHER DATABASE OPTIONS]],
116
116
  },
117
117
  // OR: (Compatibility: https://github.com/luin/ioredis)
118
- "session": {
118
+ "storage": {
119
119
  "type": "REDIS",
120
120
  "host": "[[REDIS HOST]]",
121
121
  "password": "[[REDIS PASSWORD]]",
@@ -201,8 +201,10 @@ const config = {
201
201
  skillPath: [[pathToYourMiddlewares]],
202
202
 
203
203
  // OPTIONAL, object.
204
- // Using customized callback as storage engine.
205
- session: {
204
+ // Using customized storage engine.
205
+ // `storage` should Should be compatible with the `Map` interface:
206
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
207
+ storage: {
206
208
  get: async (key) => { /* return session object by chatId. */ },
207
209
  set: async (key, session) => { /* save session object by chatId. */ },
208
210
  },
package/bin/halbot.mjs CHANGED
@@ -1,34 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { cache, dbio, memory, storage, utilitas } from 'utilitas';
3
+ import { cache, dbio, memory, storage as _storage, utilitas } from 'utilitas';
4
4
  import halbot from '../index.mjs';
5
5
 
6
6
  const debug = utilitas.humanReadableBoolean(process.env['DEBUG']);
7
7
  const log = content => utilitas.log(content, import.meta.url);
8
- const getConfig = async () => (await storage.getConfig())?.config;
8
+ const MEMORY = 'memory';
9
+ const _getConfig = async () => await _storage.getConfig();
10
+ const getConfig = async key => (await _getConfig())?.config?.[key];
9
11
 
10
- let session = {
11
- get: async key => (await getConfig())?.sessions?.[key],
12
- set: async (k, v) => await storage.setConfig({ sessions: { [k]: v } }),
12
+ let storage = {
13
+ get: async key => (await getConfig(MEMORY))?.[key],
14
+ set: async (k, v) => await _storage.setConfig({ [MEMORY]: { [k]: v } }),
13
15
  };
14
16
 
15
17
  try {
16
- const { filename, config } = await storage.getConfig();
18
+ const { filename, config } = await _getConfig();
17
19
  assert(utilitas.countKeys(config), `Error loading config from ${filename}.`);
18
- const sessionType = utilitas.trim(config.session?.type, { case: 'UP' });
19
- if (config.session?.type) { delete config.session.type; }
20
+ const sessionType = utilitas.trim(config.storage?.type, { case: 'UP' });
21
+ if (config.storage?.type) { delete config.storage.type; }
20
22
  switch (sessionType) {
21
23
  case 'MARIADB': case 'MYSQL':
22
- await dbio.init(config.session);
23
- await memory.init();
24
- session = memory;
24
+ await dbio.init(config.storage);
25
+ storage = await memory.init();
25
26
  break;
26
27
  case 'REDIS':
27
- await cache.init(config.session);
28
- session = cache;
28
+ storage = await cache.init(config.storage);
29
29
  break;
30
30
  default:
31
- config.session && utilitas.throwError('Invalid session config.');
31
+ config.storage && utilitas.throwError('Invalid storage config.');
32
32
  }
33
- await halbot({ ...config, session });
33
+ await halbot({ ...config, storage });
34
34
  } catch (err) { debug ? utilitas.throwError(err) : log(err); }
package/index.mjs CHANGED
@@ -31,6 +31,7 @@ const init = async (options) => {
31
31
  assert(options?.telegramToken, 'Telegram Bot API Token is required.');
32
32
  const [pkg, ai, _speech] = [await utilitas.which(), {}, {}];
33
33
  const info = bot.lines([`[${bot.EMOJI_BOT} ${pkg.title}](${pkg.homepage})`, pkg.description]);
34
+ const cacheOptions = options?.storage ? { store: options.storage } : null;
34
35
  if (options?.googleApiKey) {
35
36
  const apiKey = { apiKey: options?.googleApiKey };
36
37
  await Promise.all([
@@ -41,17 +42,19 @@ const init = async (options) => {
41
42
  if (options?.chatGptKey) {
42
43
  ai['ChatGPT'] = await hal.init({
43
44
  provider: 'CHATGPT', clientOptions: { apiKey: options.chatGptKey },
45
+ cacheOptions,
44
46
  });
45
47
  }
46
48
  if (options?.bingToken) {
47
49
  ai['Bing'] = await hal.init({
48
50
  provider: 'BING', clientOptions: { userToken: options.bingToken },
51
+ cacheOptions,
49
52
  });
50
53
  }
51
54
  assert(utilitas.countKeys(ai), 'No AI provider is configured.');
52
55
  const _bot = await bot.init({
53
- ai, auth: options?.auth,
54
56
  args: options?.args,
57
+ auth: options?.auth,
55
58
  botToken: options?.telegramToken,
56
59
  chatType: options?.chatType,
57
60
  cmds: options?.cmds,
@@ -62,11 +65,12 @@ const init = async (options) => {
62
65
  magicWord: options?.magicWord,
63
66
  private: options?.private,
64
67
  provider: 'telegram',
65
- session: options?.session,
68
+ session: options?.storage,
66
69
  skillPath: options?.skillPath || skillPath,
67
70
  speech: options?.googleApiKey && speech,
68
71
  vision: options?.googleApiKey && vision,
69
72
  });
73
+ _bot._.ai = ai; // Should be an array of a map of AIs.
70
74
  _bot._.lang = options?.lang || 'English';
71
75
  _bot._.prompts = await fetchPrompts();
72
76
  return _bot;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "halbot",
3
3
  "description": "Just another ChatGPT/Bing Chat Telegram bob, which is simple design, easy to use, extendable and fun.",
4
- "version": "1990.1.65",
4
+ "version": "1990.1.67",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/halbot",
7
7
  "type": "module",
@@ -29,17 +29,17 @@
29
29
  "url": "https://github.com/Leask/halbot.git"
30
30
  },
31
31
  "dependencies": {
32
- "@google-cloud/speech": "^5.4.0",
33
- "@google-cloud/text-to-speech": "^4.2.1",
34
- "@google-cloud/vision": "^3.1.2",
32
+ "@google-cloud/speech": "^5.4.1",
33
+ "@google-cloud/text-to-speech": "^4.2.2",
34
+ "@google-cloud/vision": "^3.1.3",
35
35
  "@mozilla/readability": "^0.4.4",
36
- "@waylaidwanderer/chatgpt-api": "^1.35.0",
36
+ "@waylaidwanderer/chatgpt-api": "github:Leask/node-chatgpt-api",
37
37
  "csv-parse": "^5.3.6",
38
38
  "ioredis": "^5.3.1",
39
39
  "jsdom": "^21.1.1",
40
- "mysql2": "^3.2.0",
40
+ "mysql2": "^3.2.1",
41
41
  "telegraf": "^4.12.2",
42
- "utilitas": "^1993.3.52",
42
+ "utilitas": "^1994.0.3",
43
43
  "youtube-transcript": "^1.0.5"
44
44
  }
45
45
  }
@@ -6,11 +6,15 @@ const bingTones = [balanced, 'creative', 'precise'];
6
6
  let configuredAi;
7
7
 
8
8
  const action = async (ctx, next) => {
9
+ ctx.session.context || (ctx.session.context = {});
10
+ ctx.session.latest || (ctx.session.latest = {});
9
11
  ctx.isDefaultAi = name => name === ctx.firstAi;
10
- ctx.clear = () => (ctx.selectedAi || []).map(n => {
12
+ ctx.clear = context => (ctx.selectedAi || []).map(n => {
11
13
  ctx._.ai[n].clear(ctx.chatId);
12
- ctx.hello();
13
- });
14
+ delete ctx.session.context[n];
15
+ delete ctx.session.latest[n];
16
+ context && (ctx.session.context[n] = context);
17
+ }) && ctx.hello(context?.prompt);
14
18
  ctx.firstAi = (configuredAi = Object.keys(ctx._.ai))[0];
15
19
  switch (ctx.session.config?.ai) {
16
20
  case '': ctx.selectedAi = [ctx.firstAi]; break;
@@ -49,12 +53,21 @@ export const { run, priority, func, help, args } = {
49
53
  '¶ Tweak enhanced output rendering.',
50
54
  'Example 1: /set --render on',
51
55
  'Example 2: /set --render off',
56
+ // '¶ Select between [OpenAI models](https://platform.openai.com/docs/models).',
57
+ // "Tip !!!4!!!: Set `gptmodel=''` to use default OpenAI model.",
58
+ // 'Popular models:',
59
+ // '- [gpt-4](https://platform.openai.com/docs/models/gpt-4): 8192 tokens, trained Sep 2021 (Limited beta).',
60
+ // '- [gpt-4-32k](https://platform.openai.com/docs/models/gpt-4): 32768 tokens, trained Sep 2021 (Limited beta).',
61
+ // '- [gpt-3.5-turbo](https://platform.openai.com/docs/models/gpt-3-5): 4096 tokens, trained Sep 2021.',
62
+ // '- [text-davinci-003](https://platform.openai.com/docs/models/gpt-3-5): 4097 tokens, trained Sep 2021.',
63
+ // '- [text-davinci-002](https://platform.openai.com/docs/models/gpt-3-5): 4097 tokens, trained Sep 2021.',
64
+ // '- [code-davinci-002](https://platform.openai.com/docs/models/gpt-3-5): 8001 tokens, trained Sep 2021 (Coding Optimized).',
52
65
  '¶ Set tone-style for Bing.',
53
66
  "Tip 4: Set `tone=''` to use default tone-style.",
54
67
  ]),
55
68
  args: {
56
69
  hello: {
57
- type: 'string', short: 'h', default: 'Hello!',
70
+ type: 'string', short: 's', default: 'You are ChatGPT, a large...',
58
71
  desc: "Change initial prompt: /set --hello 'Bonjour!'",
59
72
  },
60
73
  ai: {
@@ -67,6 +80,10 @@ export const { run, priority, func, help, args } = {
67
80
  desc: `\`(${bot.BINARY_STRINGS.join(', ')})\` Enable/Disable enhanced output rendering.`,
68
81
  validate: utilitas.humanReadableBoolean,
69
82
  },
83
+ // gptmodel: {
84
+ // type: 'string', short: 'g', default: 'gpt-3.5-turbo',
85
+ // desc: 'Set OpenAI model: /set --gptmodel=`MODEL`.',
86
+ // },
70
87
  tone: {
71
88
  type: 'string', short: 't', default: balanced,
72
89
  desc: `\`(${bingTones.join(', ')})\` Set tone-style for Bing.`,
@@ -26,15 +26,16 @@ const action = async (ctx, next) => {
26
26
  }
27
27
  const cnf = {
28
28
  ...ctx.session.config = {
29
- ...ctx.session.config, ...ctx.config = {
29
+ ...ctx.session.config,
30
+ ...ctx.config = {
30
31
  lang: ctx.cmd.args,
31
- hello: `Please reply in ${ctx.cmd.args}. Hello! `,
32
- }
32
+ hello: `Please reply in ${ctx.cmd.args}. Hello!`,
33
+ },
33
34
  }
34
35
  };
35
- Object.keys(ctx.config).map(x => cnf[x] = `${cnf[x]} <-- SET`);
36
- ctx.action = bot.map(cnf);
37
- await ctx.hello();
36
+ Object.keys(ctx.config).map(x => cnf[x] += ' <-- SET');
37
+ ctx.result = bot.map(cnf);
38
+ ctx.hello();
38
39
  break;
39
40
  case 'translate': promptTranslate(ctx, ctx.cmd.args || ctx.session.config?.lang || ctx._.lang); break;
40
41
  case 'polish': promptPolish(ctx); break;
@@ -48,7 +49,7 @@ const action = async (ctx, next) => {
48
49
 
49
50
  export const { run, priority, func, cmds, help } = {
50
51
  run: true,
51
- priority: 50,
52
+ priority: 40,
52
53
  func: action,
53
54
  help: bot.lines([
54
55
  '¶ Set your default language.',
@@ -0,0 +1,16 @@
1
+
2
+ const action = async (ctx, next) => {
3
+ const cmd = ctx.cmd?.cmd;
4
+ if (!ctx.context) {
5
+ const prompt = ctx.session.prompts?.[cmd] || ctx._.prompts?.[cmd]?.prompt;
6
+ prompt && (ctx.context = { cmd, prompt });
7
+ }
8
+ ctx.context && ctx.clear(ctx.context);
9
+ await next();
10
+ };
11
+
12
+ export const { run, priority, func } = {
13
+ run: true,
14
+ priority: 50,
15
+ func: action,
16
+ };
@@ -5,16 +5,16 @@ const countTokens = text => text.split(/[^a-z0-9]/i).length;
5
5
 
6
6
  const action = async (ctx, next) => {
7
7
  // avatar
8
- if (ctx.action) {
8
+ if (ctx.result) {
9
9
  ctx.avatar = '⚙️';
10
10
  } else if (ctx.msg?.voice) {
11
- ctx.avatar = bot.EMOJI_SPEECH; ctx.action = utilitas.trim(ctx.text);
11
+ ctx.avatar = bot.EMOJI_SPEECH; ctx.result = utilitas.trim(ctx.text);
12
12
  } else if (ctx.msg?.data) {
13
- ctx.avatar = '🔘'; ctx.action = utilitas.trim(ctx.text);
13
+ ctx.avatar = '🔘'; ctx.result = utilitas.trim(ctx.text);
14
14
  } else if (ctx.msg?.poll) {
15
15
  ctx.avatar = '📊';
16
16
  } else if (ctx.cmd?.cmd && ctx.cmd?.cmd !== 'clear') {
17
- ctx.avatar = '🚀'; ctx.action = utilitas.trim(ctx.text);
17
+ ctx.avatar = '🚀'; ctx.result = utilitas.trim(ctx.text);
18
18
  } else {
19
19
  ctx.avatar = '😸';
20
20
  }
@@ -22,16 +22,15 @@ const action = async (ctx, next) => {
22
22
  const additionInfo = ctx.collected.length ? ctx.collected.map(
23
23
  x => x.content
24
24
  ).join('\n').split(' ') : [];
25
- ctx.text = (ctx.text || '') + '\n\n';
26
- while (countTokens(ctx.text) < 2250 && additionInfo.length) {
27
- ctx.text += ` ${additionInfo.shift()}`;
25
+ ctx.prompt = (ctx.text || '') + '\n\n';
26
+ while (countTokens(ctx.prompt) < 2250 && additionInfo.length) {
27
+ ctx.prompt += ` ${additionInfo.shift()}`;
28
28
  }
29
- ctx.text = utilitas.trim(ctx.text);
30
- additionInfo.filter(x => x).length && (ctx.text += '...');
29
+ ctx.prompt = utilitas.trim(ctx.prompt);
30
+ additionInfo.filter(x => x).length && (ctx.prompt += '...');
31
31
  // next
32
32
  ctx.carry = {
33
- session: ctx.chatId,
34
- context: ctx.context,
33
+ sessionId: ctx.chatId,
35
34
  toneStyle: ctx.session.config?.tone,
36
35
  };
37
36
  await next();
@@ -7,22 +7,22 @@ const enrich = name => name === 'Bing' ? `${name} (Sydney)` : name;
7
7
  const log = content => utilitas.log(content, import.meta.url);
8
8
 
9
9
  const action = async (ctx, next) => {
10
- if (!ctx.text) { return await next(); }
11
- const [YOU, msgs, ctxs, tts, pms, extra]
12
- = [`${ctx.avatar} You:`, {}, {}, {}, [], {}];
10
+ if (!ctx.prompt) { return await next(); }
11
+ const [YOU, msgs, tts, pms, extra] = [`${ctx.avatar} You:`, {}, {}, [], {}];
13
12
  let [lastMsg, lastSent] = [null, 0];
14
13
  const packMsg = options => {
15
- const said = !options?.tts && ctx.action ? ctx.action : '';
14
+ const said = !options?.tts && ctx.result ? ctx.result : '';
16
15
  const packed = [...said ? [joinL2([YOU, said])] : []];
17
16
  const source = options?.tts ? tts : msgs;
18
17
  const pure = [];
19
18
  ctx.selectedAi.map(n => {
20
19
  const content = source[n] || '';
20
+ const cmd = ctx.session.context[n]?.cmd;
21
+ const context = cmd && ` > \`${cmd}\` (exit by /clear)` || '';
21
22
  pure.push(content);
22
23
  packed.push(joinL2([
23
- ...ctx.multiAi || !ctx.isDefaultAi(n) || said || ctxs[n]?.cmd ? [
24
- `${BOT}${enrich(n)}${ctxs[n]?.cmd && ` > \`${ctxs[n].cmd}\` (exit by /clear)` || ''}:`
25
- ] : [], content,
24
+ ...ctx.multiAi || !ctx.isDefaultAi(n) || said || context
25
+ ? [`${BOT}${enrich(n)}${context}:`] : [], content,
26
26
  ]));
27
27
  });
28
28
  return options?.tts && !pure.join('').trim().length ? '' : joinL1(packed);
@@ -39,18 +39,19 @@ const action = async (ctx, next) => {
39
39
  for (let n of ctx.selectedAi) {
40
40
  pms.push((async () => {
41
41
  try {
42
- const resp = await ctx._.ai[n].send(ctx.text, ctx.carry, token => {
42
+ const response = await ctx._.ai[n].send(ctx.prompt, {
43
+ ...ctx.carry, session: ctx.session.latest[n], // promptPrefix: '',
44
+ }, token => {
43
45
  msgs[n] = `${(msgs[n] || '')}${token}`;
44
46
  ok(onProgress);
45
47
  });
46
- ctxs[n] = resp.context;
47
48
  msgs[n] = ctx.session.config?.render === false
48
- ? resp.response : resp.responseRendered;
49
- tts[n] = msgs[n].split('\n').some(x => /^```/.test(x)) ? '' : resp.spokenText;
50
- extra.buttons = resp?.suggestedResponses?.map?.(label => ({
49
+ ? response.response : response.responseRendered;
50
+ tts[n] = msgs[n].split('\n').some(x => /^```/.test(x)) ? '' : response.spokenText;
51
+ extra.buttons = response?.suggestedResponses?.map?.(label => ({
51
52
  label, text: `/bing@${ctx.botInfo.username} ${label}`,
52
53
  }));
53
- return resp;
54
+ return ctx.session.latest[n] = response;
54
55
  } catch (err) {
55
56
  msgs[n] = err?.message || err;
56
57
  tts[n] = msgs[n];
@@ -58,11 +59,7 @@ const action = async (ctx, next) => {
58
59
  }
59
60
  })());
60
61
  }
61
- ctx.session._latest = {
62
- prompt: ctx.text,
63
- carry: ctx.carry,
64
- responses: await Promise.all(pms),
65
- };
62
+ await Promise.all(pms);
66
63
  await ok();
67
64
  // ctx.responses = msgs; // save responses-to-user for next middleware
68
65
  ctx.tts = packMsg({ tts: true });
@@ -1,17 +0,0 @@
1
-
2
- const action = async (ctx, next) => {
3
- const cmd = ctx.cmd?.cmd;
4
- const prompt = ctx.session.prompts?.[cmd] || ctx._.prompts?.[cmd]?.prompt;
5
- if (prompt) {
6
- ctx.clear();
7
- ctx.context = { cmd, prompt };
8
- ctx.collect(prompt);
9
- }
10
- await next();
11
- };
12
-
13
- export const { run, priority, func } = {
14
- run: true,
15
- priority: 40,
16
- func: action,
17
- };
File without changes
File without changes