halbot 1993.2.89 → 1993.2.91

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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { alan, bot, image, web, speech, utilitas } from 'utilitas';
1
+ import { alan, bot, gen, web, speech, utilitas } from 'utilitas';
2
2
  import * as hal from './lib/hal.mjs';
3
3
 
4
4
  await utilitas.locate(utilitas.__(import.meta.url, 'package.json'));
@@ -25,7 +25,7 @@ const init = async (options = {}) => {
25
25
  ...apiKey, model: options.openaiModel || '*',
26
26
  priority: options.openaiPriority, ...options
27
27
  });
28
- await image.init(apiKey);
28
+ await gen.init(apiKey);
29
29
  await speech.init({ ...apiKey, ...speechOptions });
30
30
  _speech.tts = speech.tts;
31
31
  }
@@ -37,7 +37,6 @@ const init = async (options = {}) => {
37
37
  ...apiKey, provider: 'GEMINI', model: options.geminiModel || '*',
38
38
  priority: options.geminiPriority, ...options
39
39
  });
40
- await image.init({ ...apiKey, provider: 'GEMINI' });
41
40
  if (!_speech.tts) {
42
41
  await speech.init({ ...apiKey, ...speechOptions });
43
42
  _speech.tts = speech.tts;
@@ -46,6 +45,13 @@ const init = async (options = {}) => {
46
45
  ...apiKey, cx: options.googleCx,
47
46
  });
48
47
  }
48
+ const geminiGenReady = options.googleApiKey
49
+ || (options.googleCredentials && options.googleProjectId);
50
+ geminiGenReady && await gen.init({
51
+ apiKey: options.googleApiKey, provider: 'GEMINI',
52
+ credentials: options.googleCredentials,
53
+ projectId: options.googleProjectId,
54
+ });
49
55
  if (options.anthropicApiKey) {
50
56
  await alan.init({
51
57
  provider: 'ANTHROPIC', model: options.anthropicModel || '*',
@@ -53,11 +59,11 @@ const init = async (options = {}) => {
53
59
  priority: options.anthropicPriority, ...options
54
60
  });
55
61
  }
56
- if (options.anthropicCredentials && options.anthropicProjectId) {
62
+ if (options.googleCredentials && options.googleProjectId) {
57
63
  await alan.init({
58
64
  provider: 'VERTEX ANTHROPIC', model: options.anthropicModel || '*',
59
- credentials: options.anthropicCredentials,
60
- projectId: options.anthropicProjectId,
65
+ credentials: options.googleCredentials,
66
+ projectId: options.googleProjectId,
61
67
  priority: options.anthropicPriority, ...options
62
68
  });
63
69
  }
@@ -130,7 +136,8 @@ const init = async (options = {}) => {
130
136
  speech: _speech, vision,
131
137
  });
132
138
  _hal._.lang = options?.lang || 'English';
133
- _hal._.image = options?.openaiApiKey && image;
139
+ _hal._.gen = options?.gen
140
+ || (options?.openaiApiKey || geminiGenReady ? gen : null);
134
141
  return _hal;
135
142
  };
136
143
 
package/lib/hal.mjs CHANGED
@@ -1,8 +1,5 @@
1
- import {
2
- alan, bot, callosum, dbio, storage, uoid, utilitas, web,
3
- } from 'utilitas';
4
-
5
1
  import { basename, join } from 'path';
2
+ import { bot, callosum, dbio, storage, uoid, utilitas, web } from 'utilitas';
6
3
  import { parseArgs as _parseArgs } from 'node:util';
7
4
  import { readdirSync } from 'fs';
8
5
 
@@ -20,6 +17,7 @@ const isMarkdownError = e => e?.description?.includes?.("can't parse entities");
20
17
  const getFile = async (id, op) => (await web.get(await getFileUrl(id), op)).content;
21
18
  const compact = (str, op) => utilitas.ensureString(str, { ...op || {}, compact: true });
22
19
  const compactLimit = (str, op) => compact(str, { ...op || {}, limit: 140 });
20
+ const getKey = s => s?.toLowerCase?.()?.startsWith?.('http') ? 'url' : 'source';
23
21
  const SEARCH_LIMIT = 10;
24
22
 
25
23
  const [ // https://limits.tginfo.me/en
@@ -362,18 +360,26 @@ const subconscious = [{
362
360
  ctx.complete = async (options) => await ctx.ok('☑️', options);
363
361
  ctx.json = async (obj, options) => await ctx.ok(json(obj), options);
364
362
  ctx.list = async (list, options) => await ctx.ok(uList(list), options);
365
- ctx.media = async (fnc, src, options) => ctx.done.push(await ctx[fnc]({
366
- [src?.toLowerCase?.()?.startsWith?.('http') ? 'url' : 'source']: src
367
- }, getExtra(ctx, options)));
368
- ctx.audio = async (sr, op) => await ctx.media('replyWithAudio', sr, op);
369
- ctx.image = async (sr, op) => await ctx.media('replyWithPhoto', sr, op);
370
- ctx.sendConfig = async (obj, options, _ctx) => await ctx.ok(utilitas.prettyJson(
371
- obj, { code: true, md: true }
372
- ), options);
363
+ ctx.replyWith = async (func, src, options) => ctx.done.push(
364
+ await ctx[func]({ [getKey(src)]: src }, getExtra(ctx, options))
365
+ );
366
+ ctx.audio = async (s, o) => await ctx.replyWith('replyWithAudio', s, o);
367
+ ctx.image = async (s, o) => await ctx.replyWith('replyWithPhoto', s, o);
368
+ ctx.video = async (s, o) => await ctx.replyWith('replyWithVideo', s, o);
369
+ ctx.media = async (srs, options) => await ctx.done.push(
370
+ await ctx.replyWithMediaGroup(srs.map(x => ({
371
+ type: x.type || 'photo', media: { [getKey(x.src)]: x.src },
372
+ })), getExtra(ctx, options))
373
+ );
374
+ ctx.sendConfig = async (obj, options, _ctx) => await ctx.ok(
375
+ utilitas.prettyJson(obj, { code: true, md: true }), options
376
+ );
373
377
  ctx.speech = async (cnt, options) => {
374
378
  let file;
375
379
  if (Buffer.isBuffer(cnt)) {
376
- file = await storage.convert(cnt, { input: storage.BUFFER, expected: storage.FILE });
380
+ file = await storage.convert(cnt, {
381
+ input: storage.BUFFER, expected: storage.FILE,
382
+ });
377
383
  } else if (cnt.length <= speech.OPENAI_TTS_MAX_LENGTH) {
378
384
  file = await utilitas.ignoreErrFunc(async () => await ctx._.speech.tts(
379
385
  cnt, { expected: 'file' }
@@ -603,7 +609,11 @@ const subconscious = [{
603
609
  const analyze = async () => {
604
610
  const resp = await utilitas.ignoreErrFunc(async () => {
605
611
  [
606
- alan.mp3, alan.mpega, alan.mp4, alan.mpeg, alan.mpga, alan.m4a, alan.wav, alan.webm, alan.ogg
612
+ storage.MIME_MP3, storage.MIME_MPEGA,
613
+ storage.MIME_MP4, storage.MIME_MPEG,
614
+ storage.MIME_MPGA, storage.MIME_M4A,
615
+ storage.MIME_WAV, storage.MIME_WEBM,
616
+ storage.MIME_OGG,
607
617
  ].includes(audio.mime_type) || (
608
618
  file = await media.convertAudioTo16kNanoPcmWave(
609
619
  file, { input: storage.BUFFER, expected: storage.BUFFER }
@@ -614,9 +624,9 @@ const subconscious = [{
614
624
  log(`STT: '${resp}'`);
615
625
  ctx.collect(resp);
616
626
  };
617
- if (hal._.supportedMimeTypes.has(alan.wav)) {
627
+ if (hal._.supportedMimeTypes.has(MIME_WAV)) {
618
628
  ctx.collect({
619
- mime_type: alan.wav, url, analyze,
629
+ mime_type: MIME_WAV, url, analyze,
620
630
  data: await media.convertAudioTo16kNanoPcmWave(file, {
621
631
  input: storage.BUFFER, expected: storage.BASE64,
622
632
  }),
@@ -727,9 +737,10 @@ const subconscious = [{
727
737
  if (m.photo?.[m.photo?.length - 1]) {
728
738
  const p = m.photo[m.photo.length - 1];
729
739
  files.push({
730
- asPrompt: hal._.supportedMimeTypes.has(alan.jpeg),
740
+ asPrompt: hal._.supportedMimeTypes.has(storage.MIME_JPEG),
731
741
  file_name: `${p.file_id}.jpg`, fileId: p.file_id,
732
- mime_type: alan.jpeg, type: 'PHOTO', ocrFunc: ctx._.vision?.see,
742
+ mime_type: storage.MIME_JPEG, type: 'PHOTO',
743
+ ocrFunc: ctx._.vision?.see,
733
744
  });
734
745
  }
735
746
  if (m.video_note) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "halbot",
3
3
  "description": "Just another `ChatGPT` / `Gemini` / `Claude` / `Azure` / `Jina` / `Ollama` Telegram bob, which is simple design, easy to use, extendable and fun.",
4
- "version": "1993.2.89",
4
+ "version": "1993.2.91",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/halbot",
7
7
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "@google-cloud/speech": "^7.0.1",
37
37
  "@google-cloud/text-to-speech": "^6.0.1",
38
38
  "@google-cloud/vision": "^5.1.0",
39
- "@google/genai": "^0.10.0",
39
+ "@google/genai": "^0.12.0",
40
40
  "@mozilla/readability": "^0.6.0",
41
41
  "fluent-ffmpeg": "^2.1.3",
42
42
  "ioredis": "^5.6.1",
@@ -46,12 +46,12 @@
46
46
  "mime": "^4.0.7",
47
47
  "mysql2": "^3.14.1",
48
48
  "office-text-extractor": "^3.0.3",
49
- "openai": "^4.96.2",
49
+ "openai": "^4.97.0",
50
50
  "pg": "^8.15.6",
51
51
  "pgvector": "^0.2.0",
52
52
  "telegraf": "^4.16.3",
53
53
  "tesseract.js": "^6.0.1",
54
- "utilitas": "^1999.1.54",
54
+ "utilitas": "^1999.1.61",
55
55
  "youtube-transcript": "^1.2.1"
56
56
  }
57
57
  }
@@ -1,29 +1,39 @@
1
- import { bot } from '../index.mjs';
1
+ import { bot, storage } from '../index.mjs';
2
+
3
+ const GEMINI = 'GEMINI';
4
+ const types = { image: 'photo', video: 'video' };
2
5
 
3
6
  const action = async (ctx, next) => {
4
- let provider = '';
7
+ let [provider, func, reference] = [GEMINI, 'image', null];
5
8
  switch (ctx.cmd.cmd) {
6
- case 'gptimage': provider = 'OPENAI'; break;
7
- case 'dream': case 'imagen': default: provider = 'GEMINI';
9
+ case 'fantasy': func = 'video'; break;
10
+ case 'gptimage':
11
+ provider = 'OPENAI';
12
+ reference = ctx.collected.filter(x => [
13
+ storage.MIME_JPEG, storage.MIME_PNG, storage.MIME_WEBP
14
+ ].includes(x?.content?.mime_type)).slice(0, 16).map(
15
+ x => x?.content?.data
16
+ );
8
17
  }
9
18
  if (!ctx.cmd.args) {
10
19
  return await ctx.ok('Please input your prompt.');
11
20
  }
12
- let [objMsg, images] = [(await ctx.ok('💭'))[0], null]; //tts = null
21
+ let [objMsg, output] = [(await ctx.ok('💭'))[0], null]; //tts = null
13
22
  try {
14
- images = await ctx._.image.generate(ctx.cmd.args, {
15
- provider, expected: 'FILE'
16
- });
23
+ output = (await ctx._.gen[func](ctx.cmd.args, {
24
+ provider, expected: 'FILE',
25
+ ...reference?.length ? { reference, input: 'BASE64' } : {},
26
+ })) || [];
17
27
  } catch (err) {
18
- return await ctx.er(err.message || 'Error generating image.',
28
+ return await ctx.er(err.message || `Error generating ${func}.`,
19
29
  { lastMessageId: objMsg.message_id });
20
30
  }
21
31
  await ctx.deleteMessage(objMsg.message_id);
22
- for (let image of images || []) {
23
- // tts = image.tts || '';
24
- await ctx.image(image.data, { caption: image.caption || '' });
25
- await ctx.timeout();
26
- }
32
+ await ctx.media(
33
+ output.map(x => ({ type: types[func], src: x.data })),
34
+ { caption: output[0]?.caption || '' }
35
+ );
36
+ // tts = output.tts || '';
27
37
  // await ctx.shouldSpeech(tts);
28
38
  };
29
39
 
@@ -35,13 +45,16 @@ export const { name, run, priority, func, cmds, help } = {
35
45
  help: bot.lines([
36
46
  '¶ Use Google `Imagen` (default) or OpenAI `GPT Image` to generate images.',
37
47
  'Example 1: /dream a cat in a rocket',
48
+ '¶ Use Google `Veo` to generate videos.',
49
+ 'Example 2: /fantasy two cats are kissing each other',
38
50
  '¶ Use `Imagen` to generate images.',
39
- 'Example 2: /imagen a cat in a car',
51
+ 'Example 3: /imagen a cat in a car',
40
52
  '¶ Use `GPT Image` to generate images.',
41
- 'Example: /gptimage a cat on a bike',
53
+ 'Example 4: /gptimage a cat on a bike',
42
54
  ]),
43
55
  cmds: {
44
56
  dream: 'Generate images with default model: /dream `PROMPT`',
57
+ fantasy: 'Generate videos with `Veo`: /fantasy `PROMPT`',
45
58
  imagen: 'Generate images with `Imagen`: /imagen `PROMPT`',
46
59
  gptimage: 'Generate images with `GPT Image`: /gptimage `PROMPT`',
47
60
  },