utilitas 1993.3.21 → 1993.3.22

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
@@ -26,6 +26,7 @@ import * as storage from './lib/storage.mjs';
26
26
  import * as tape from './lib/tape.mjs';
27
27
  import * as uoid from './lib/uoid.mjs';
28
28
  import * as utilitas from './lib/utilitas.mjs';
29
+ import * as vision from './lib/vision.mjs';
29
30
  import * as web from './lib/web.mjs';
30
31
  import color from './lib/color.mjs';
31
32
  import manifest from './lib/manifest.mjs';
@@ -38,7 +39,7 @@ export {
38
39
  // features
39
40
  bot, boxes, cache, callosum, color, dbio, email, encryption, event, hal,
40
41
  manifest, memory, network, sentinel, shekel, shell, shot, sms, speech, ssl,
41
- storage, tape, uoid, utilitas, web
42
+ storage, tape, uoid, utilitas, vision, web
42
43
  };
43
44
 
44
45
  if (utilitas.inBrowser() && !globalThis.utilitas) {
package/lib/bot.mjs CHANGED
@@ -42,6 +42,8 @@ const [ // https://limits.tginfo.me/en
42
42
  256, 'bot_command', '🗣️', '👀', '🤖',
43
43
  ];
44
44
 
45
+ const [BUFFER_ENCODE] = [{ encode: BUFFER }];
46
+
45
47
  const KNOWN_UPDATE_TYPES = [
46
48
  'message', 'callback_query', 'channel_post', 'edited_message',
47
49
  'my_chat_member'
@@ -230,8 +232,10 @@ const subconscious = [{
230
232
  }
231
233
  return target === ctx.botInfo.username;
232
234
  })) { ctx.chatType = MENTION; }
233
- if ((ctx.text || ctx.msg.voice || ctx.msg.poll || ctx.msg.data)
234
- && ctx.messageId) { await next(); }
235
+ if ((ctx.text || ctx.msg.voice || ctx.msg.poll || ctx.msg.data
236
+ || ctx.msg.document || ctx.msg.photo) && ctx.messageId) {
237
+ await next();
238
+ }
235
239
  await sessionSet(ctx.chatId);
236
240
  },
237
241
  }, {
@@ -281,9 +285,7 @@ const subconscious = [{
281
285
  if (ctx._.speech?.stt && ctx.msg.voice?.mime_type === 'audio/ogg') {
282
286
  await ctx.ok(EMOJI_SPEECH);
283
287
  try {
284
- const file = await getFile(
285
- ctx.msg.voice.file_id, { encode: BUFFER }
286
- );
288
+ const file = await getFile(ctx.msg.voice.file_id, BUFFER_ENCODE);
287
289
  const resp = await ignoreErrFunc(
288
290
  async () => await ctx._.speech?.stt(
289
291
  file, { config: sampleRateHertz }
@@ -334,7 +336,31 @@ const subconscious = [{
334
336
  await next();
335
337
  },
336
338
  }, {
337
- run: true, priority: -8880, name: 'commands', func: async (ctx, next) => {
339
+ run: true, priority: -8880, name: 'ocr', func: async (ctx, next) => {
340
+ if (!ctx._.vision?.ocrImage) { return await next(); }
341
+ let fileId;
342
+ if (/^image\/.*$/ig.test(ctx.msg?.document?.mime_type)) {
343
+ fileId = ctx.msg.document.file_id;
344
+ } else if (ctx.msg?.photo) {
345
+ fileId = ctx.msg.photo[ctx.msg.photo.length - 1]?.file_id;
346
+ }
347
+ if (fileId) {
348
+ await ctx.ok(EMOJI_LOOK);
349
+ try {
350
+ const file = await getFile(fileId, BUFFER_ENCODE);
351
+ const content = await ignoreErrFunc(
352
+ async () => await ctx._.vision?.ocrImage(
353
+ file, BUFFER_ENCODE
354
+ ), { log: true }
355
+ );
356
+ console.log(content);
357
+ ctx.collect(content, 'OCR');
358
+ } catch (err) { return await ctx.er(err); }
359
+ }
360
+ await next();
361
+ },
362
+ }, {
363
+ run: true, priority: -8870, name: 'commands', func: async (ctx, next) => {
338
364
  for (let ent of ctx.msg?.entities || []) {
339
365
  if (ent.type !== bot_command) { continue; }
340
366
  const rawCmd = ctx.text.substr(ent.offset, ent.length);
@@ -349,7 +375,7 @@ const subconscious = [{
349
375
  await next();
350
376
  },
351
377
  }, {
352
- run: true, priority: -8870, name: 'help', func: async (ctx, next) => {
378
+ run: true, priority: -8860, name: 'help', func: async (ctx, next) => {
353
379
  const help = ctx._.info ? [ctx._.info] : [];
354
380
  for (let i in ctx._.skills) {
355
381
  const _help = [];
@@ -387,7 +413,7 @@ const subconscious = [{
387
413
  echo: 'Show debug message.',
388
414
  },
389
415
  }, {
390
- run: true, priority: -8870, name: 'configuration', func: async (ctx, next) => {
416
+ run: true, priority: -8850, name: 'configuration', func: async (ctx, next) => {
391
417
  switch (ctx.cmd.cmd) {
392
418
  case 'set':
393
419
  try {
@@ -506,6 +532,7 @@ const init = async (options) => {
506
532
  private: options?.private && new Set(options.private),
507
533
  session: { get: options?.session?.get, set: options?.session?.set },
508
534
  speech: options?.speech,
535
+ vision: options?.vision,
509
536
  skills: { ...options?.skills || {} },
510
537
  };
511
538
  (!options?.session?.get || !options?.session?.set)
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomBytes as random } from 'crypto';
2
2
  import { createReadStream } from 'fs';
3
3
  import { ensureString } from './utilitas.mjs';
4
+ import { need } from './utilitas.mjs';
4
5
  import { networkInterfaces } from 'os';
5
6
 
6
7
  const defaultAlgorithm = 'sha256';
@@ -42,9 +43,20 @@ const hexToBigInt = (hex) => {
42
43
  return BigInt(hex, 16).toString(10);
43
44
  };
44
45
 
46
+ const getApiKeyCredentials = async (options) => {
47
+ // Included in @google-cloud/vision, @google-cloud/speech and @google-cloud/text-to-speech
48
+ const { GoogleAuth, grpc } = await need('google-gax');
49
+ const authClient = new GoogleAuth().fromAPIKey(options?.apiKey);
50
+ return grpc.credentials.combineChannelCredentials(
51
+ grpc.credentials.createSsl(),
52
+ grpc.credentials.createFromGoogleCredential(authClient)
53
+ );
54
+ };
55
+
45
56
  export {
46
57
  defaultAlgorithm,
47
58
  digestObject,
59
+ getApiKeyCredentials,
48
60
  getSortedQueryString,
49
61
  hash as sha256,
50
62
  hash,
package/lib/manifest.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  const manifest = {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1993.3.21",
4
+ "version": "1993.3.22",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
@@ -23,6 +23,7 @@ const manifest = {
23
23
  "devDependencies": {
24
24
  "@google-cloud/speech": "^5.4.0",
25
25
  "@google-cloud/text-to-speech": "^4.2.1",
26
+ "@google-cloud/vision": "^3.1.2",
26
27
  "@mozilla/readability": "^0.4.4",
27
28
  "@sentry/node": "^7.47.0",
28
29
  "@waylaidwanderer/chatgpt-api": "^1.34.0",
package/lib/speech.mjs CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  base64Encode, log as _log, countKeys, ensureString, need, throwError
3
3
  } from './utilitas.mjs';
4
4
 
5
+ import { getApiKeyCredentials } from './encryption.mjs';
5
6
  import { readFile, writeFile, writeTempFile } from './storage.mjs';
6
7
 
7
8
  const _NEED = ['@google-cloud/speech', '@google-cloud/text-to-speech'];
@@ -9,16 +10,6 @@ const log = (content) => _log(content, import.meta.url);
9
10
  const [BUFFER, BASE64, FILE, clients, languageCode, audioEncoding, suffix, encoding]
10
11
  = ['BUFFER', 'BASE64', 'FILE', {}, 'en-US', 'OGG_OPUS', 'OGG', { encoding: 'binary' }];
11
12
 
12
- const getApiKeyCredentials = async (options) => {
13
- // Included in @google-cloud/speech or @google-cloud/text-to-speech
14
- const { GoogleAuth, grpc } = await need('google-gax');
15
- const authClient = new GoogleAuth().fromAPIKey(options?.apiKey);
16
- return grpc.credentials.combineChannelCredentials(
17
- grpc.credentials.createSsl(),
18
- grpc.credentials.createFromGoogleCredential(authClient)
19
- );
20
- };
21
-
22
13
  const init = async (options) => {
23
14
  if (options) {
24
15
  assert(options?.apiKey, 'Google Cloud API Key is required.', 500);
package/lib/vision.mjs ADDED
@@ -0,0 +1,42 @@
1
+ import { base64Decode, ensureString, need, throwError } from './utilitas.mjs';
2
+ import { getApiKeyCredentials } from './encryption.mjs';
3
+ import { writeTempFile } from './storage.mjs';
4
+
5
+ const _NEED = ['@google-cloud/vision'];
6
+ const [BUFFER, BASE64, FILE, encoding]
7
+ = ['BUFFER', 'BASE64', 'FILE', { encoding: 'binary' }];
8
+
9
+ let client;
10
+
11
+ const init = async (options) => {
12
+ if (options) {
13
+ assert(options?.apiKey, 'Google Cloud API Key is required.', 500);
14
+ const sslCreds = await getApiKeyCredentials(options);
15
+ const vision = (await need('@google-cloud/vision')).default;
16
+ client = new vision.ImageAnnotatorClient({ sslCreds });
17
+ }
18
+ assert(client, 'Vision API client has not been initialized.', 501);
19
+ return client;
20
+ };
21
+
22
+ const ocrImage = async (image, options) => {
23
+ assert(client, 'Vision API has not been initialized.', 500);
24
+ assert(image, 'Image data is required.', 400);
25
+ let content;
26
+ switch (ensureString(options?.input, { case: 'UP' })) {
27
+ case BASE64: image = base64Decode(image, true);
28
+ case BUFFER: case '': content = await writeTempFile(image, encoding); break;
29
+ case FILE: content = image; break;
30
+ default: throwError('Invalid input format.', 400);
31
+ }
32
+ const [response] = await client.textDetection(content);
33
+ let detections = response.textAnnotations;
34
+ options?.raw || (detections = detections.map(t => t.description).join(' '));
35
+ return detections;
36
+ };
37
+
38
+ export {
39
+ _NEED,
40
+ init,
41
+ ocrImage,
42
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1993.3.21",
4
+ "version": "1993.3.22",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
@@ -34,6 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@google-cloud/speech": "^5.4.0",
36
36
  "@google-cloud/text-to-speech": "^4.2.1",
37
+ "@google-cloud/vision": "^3.1.2",
37
38
  "@mozilla/readability": "^0.4.4",
38
39
  "@sentry/node": "^7.47.0",
39
40
  "@waylaidwanderer/chatgpt-api": "^1.34.0",