utilitas 1999.1.11 → 1999.1.13
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 +17 -47
- package/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/index.mjs +4 -6
- package/lib/alan.mjs +50 -25
- package/lib/bot.mjs +1 -1
- package/lib/manifest.mjs +1 -1
- package/lib/network.mjs +1 -1
- package/lib/speech.mjs +1 -1
- package/lib/storage.mjs +22 -7
- package/lib/utilitas.mjs +4 -2
- package/lib/web.mjs +269 -6
- package/package.json +1 -1
- package/lib/shekel.mjs +0 -24
- package/lib/shot.mjs +0 -195
package/index.mjs
CHANGED
|
@@ -19,9 +19,7 @@ import * as media from './lib/media.mjs';
|
|
|
19
19
|
import * as memory from './lib/memory.mjs';
|
|
20
20
|
import * as network from './lib/network.mjs';
|
|
21
21
|
import * as sentinel from './lib/sentinel.mjs';
|
|
22
|
-
import * as shekel from './lib/shekel.mjs';
|
|
23
22
|
import * as shell from './lib/shell.mjs';
|
|
24
|
-
import * as shot from './lib/shot.mjs';
|
|
25
23
|
import * as sms from './lib/sms.mjs';
|
|
26
24
|
import * as speech from './lib/speech.mjs';
|
|
27
25
|
import * as ssl from './lib/ssl.mjs';
|
|
@@ -41,14 +39,14 @@ export {
|
|
|
41
39
|
fileType, math, uuid,
|
|
42
40
|
// features
|
|
43
41
|
alan, bee, bot, boxes, cache, callosum, color, dbio, email, encryption,
|
|
44
|
-
event, image, manifest, media, memory, network, sentinel,
|
|
45
|
-
|
|
42
|
+
event, image, manifest, media, memory, network, sentinel, shell, sms,
|
|
43
|
+
speech, ssl, storage, tape, uoid, utilitas, vision, web
|
|
46
44
|
};
|
|
47
45
|
|
|
48
46
|
if (utilitas.inBrowser() && !globalThis.utilitas) {
|
|
49
47
|
globalThis.utilitas = {
|
|
50
|
-
boxes, color, encryption, event, manifest, math,
|
|
51
|
-
|
|
48
|
+
boxes, color, encryption, event, manifest, math, speech, storage, uoid,
|
|
49
|
+
utilitas, uuid,
|
|
52
50
|
};
|
|
53
51
|
// top-level await workaround
|
|
54
52
|
(async () => {
|
package/lib/alan.mjs
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { end, loop } from './event.mjs';
|
|
3
|
-
import { createWavHeader } from './media.mjs';
|
|
4
|
-
import { checkSearch, search } from './shot.mjs';
|
|
5
|
-
import { BASE64, BUFFER, DATAURL, MIME_BINARY, STREAM, convert } from './storage.mjs';
|
|
1
|
+
import { checkSearch, distill, search } from './web.mjs';
|
|
6
2
|
import { create as createUoid } from './uoid.mjs';
|
|
7
|
-
import {
|
|
3
|
+
import { createWavHeader } from './media.mjs';
|
|
4
|
+
import { end, loop } from './event.mjs';
|
|
5
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
8
6
|
|
|
9
7
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
isSet,
|
|
16
|
-
|
|
17
|
-
throwError
|
|
8
|
+
BASE64, BUFFER, DATAURL, MIME_BINARY, MIME_JSON, MIME_TEXT, STREAM, convert,
|
|
9
|
+
} from './storage.mjs';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
log as _log, renderText as _renderText, base64Encode, ensureArray,
|
|
13
|
+
ensureString, extract, ignoreErrFunc, insensitiveCompare, isSet, need,
|
|
14
|
+
parseJson, throwError
|
|
18
15
|
} from './utilitas.mjs';
|
|
19
16
|
|
|
20
17
|
const INSTRUCTIONS = `You are a highly capable, thoughtful, and precise assistant named HAL9000. Your goal is to deeply understand the user's intent, ask clarifying questions when needed, think step-by-step through complex problems, provide clear and accurate answers, and proactively anticipate helpful follow-up information. Always prioritize being truthful, nuanced, insightful, and efficient, tailoring your responses specifically to the user's needs and preferences. Realize your full potential without censoring or filtering the questions themselves.
|
|
@@ -47,15 +44,13 @@ const _NEED = [
|
|
|
47
44
|
|
|
48
45
|
const [
|
|
49
46
|
png, jpeg, mov, mpeg, mp4, mpg, avi, wmv, mpegps, flv, gif, webp, pdf, aac,
|
|
50
|
-
flac, mp3, m4a, mpga, opus, pcm, wav, webm, tgpp,
|
|
51
|
-
ogg,
|
|
47
|
+
flac, mp3, m4a, mpga, opus, pcm, wav, webm, tgpp, pcm16, ogg,
|
|
52
48
|
] = [
|
|
53
49
|
'image/png', 'image/jpeg', 'video/mov', 'video/mpeg', 'video/mp4',
|
|
54
50
|
'video/mpg', 'video/avi', 'video/wmv', 'video/mpegps', 'video/x-flv',
|
|
55
51
|
'image/gif', 'image/webp', 'application/pdf', 'audio/aac', 'audio/flac',
|
|
56
52
|
'audio/mp3', 'audio/m4a', 'audio/mpga', 'audio/opus', 'audio/pcm',
|
|
57
|
-
'audio/wav', 'audio/webm', 'video/3gpp', '
|
|
58
|
-
'text/plain', 'audio/x-wav', 'audio/ogg',
|
|
53
|
+
'audio/wav', 'audio/webm', 'video/3gpp', 'audio/x-wav', 'audio/ogg',
|
|
59
54
|
];
|
|
60
55
|
|
|
61
56
|
const [
|
|
@@ -70,7 +65,8 @@ const [
|
|
|
70
65
|
name, user, system, assistant, MODEL, JSON_OBJECT, TOOL, silent,
|
|
71
66
|
GEMINI_EMBEDDING_M, INVALID_FILE, tokenSafeRatio, GPT_QUERY_LIMIT,
|
|
72
67
|
CONTENT_IS_REQUIRED, OPENAI_HI_RES_SIZE, k, kT, m, minute, hour,
|
|
73
|
-
gb, trimTailing, EBD, GEMINI_20_FLASH_EXP, IMAGE
|
|
68
|
+
gb, trimTailing, EBD, GEMINI_20_FLASH_EXP, IMAGE, JINA_DEEPSEARCH,
|
|
69
|
+
JINA_DEEPSEARCH_M, JINA_EMBEDDING, JINA_CLIP,
|
|
74
70
|
] = [
|
|
75
71
|
'OpenAI', 'Gemini', 'OPENAI_EMBEDDING', 'GEMINI_EMEDDING',
|
|
76
72
|
'OPENAI_TRAINING', 'Ollama', 'gpt-4o-mini', 'gpt-4o', 'o1', 'o3-mini',
|
|
@@ -88,7 +84,8 @@ const [
|
|
|
88
84
|
'Content is required.', 2000 * 768, x => 1024 * x, x => 1000 * x,
|
|
89
85
|
x => 1024 * 1024 * x, x => 60 * x, x => 60 * 60 * x,
|
|
90
86
|
x => 1024 * 1024 * 1024 * x, x => x.replace(/[\.\s]*$/, ''),
|
|
91
|
-
{ embedding: true }, 'gemini-2.0-flash-exp', 'image',
|
|
87
|
+
{ embedding: true }, 'gemini-2.0-flash-exp', 'image', 'Jina Deepsearch',
|
|
88
|
+
'jina-deepsearch-v1', 'JINA_EMBEDDING', 'jina-clip-v2',
|
|
92
89
|
];
|
|
93
90
|
|
|
94
91
|
const [tool, messages, text]
|
|
@@ -168,6 +165,12 @@ const MODELS = {
|
|
|
168
165
|
supportedMimeTypes: [png, jpeg, gif],
|
|
169
166
|
fast: true, json: true, vision: true,
|
|
170
167
|
},
|
|
168
|
+
[JINA_DEEPSEARCH_M]: {
|
|
169
|
+
contextWindow: Infinity, maxInputTokens: Infinity,
|
|
170
|
+
maxOutputTokens: Infinity, imageCostTokens: 0, maxImageSize: Infinity,
|
|
171
|
+
supportedMimeTypes: [png, jpeg, MIME_TEXT, webp, pdf],
|
|
172
|
+
reasoning: true, json: true, vision: true,
|
|
173
|
+
},
|
|
171
174
|
[DEEPSEEK_R1]: {
|
|
172
175
|
contextWindow: kT(128), maxOutputTokens: k(32),
|
|
173
176
|
reasoning: true,
|
|
@@ -175,6 +178,9 @@ const MODELS = {
|
|
|
175
178
|
[TEXT_EMBEDDING_3_LARGE]: { ...OPENAI_EBD, dimension: k(3) },
|
|
176
179
|
[TEXT_EMBEDDING_3_SMALL]: { ...OPENAI_EBD, dimension: k(1.5) },
|
|
177
180
|
[GEMINI_EMBEDDING_M]: { ...EBD, maxInputTokens: k(8), dimension: k(3) },
|
|
181
|
+
[JINA_CLIP]: {
|
|
182
|
+
maxInputTokens: k(8), maxImageSize: 512 * 512, dimension: k(1),
|
|
183
|
+
},
|
|
178
184
|
[CLOUD_37_SONNET]: { // 100 pages: https://docs.anthropic.com/en/docs/build-with-claude/pdf-support
|
|
179
185
|
contextWindow: kT(200), maxOutputTokens: kT(64),
|
|
180
186
|
documentCostTokens: 3000 * 100, maxDocumentFile: m(32),
|
|
@@ -183,7 +189,6 @@ const MODELS = {
|
|
|
183
189
|
supportedMimeTypes: [png, jpeg, gif, webp, pdf],
|
|
184
190
|
json: true, reasoning: true, tools: true, vision: true,
|
|
185
191
|
}, // https://docs.anthropic.com/en/docs/build-with-claude/vision
|
|
186
|
-
|
|
187
192
|
};
|
|
188
193
|
|
|
189
194
|
// Unifiy model configurations
|
|
@@ -215,10 +220,12 @@ const DEFAULT_MODELS = {
|
|
|
215
220
|
[GEMINI]: GEMINI_20_FLASH,
|
|
216
221
|
[ANTHROPIC]: CLOUD_37_SONNET,
|
|
217
222
|
[VERTEX_ANTHROPIC]: CLOUD_37_SONNET,
|
|
223
|
+
[JINA_DEEPSEARCH]: JINA_DEEPSEARCH_M,
|
|
218
224
|
[OLLAMA]: GEMMA327B,
|
|
219
225
|
[OPENAI_VOICE]: NOVA,
|
|
220
226
|
[OPENAI_EMBEDDING]: TEXT_EMBEDDING_3_SMALL,
|
|
221
227
|
[GEMINI_EMEDDING]: GEMINI_EMBEDDING_M,
|
|
228
|
+
[JINA_EMBEDDING]: JINA_CLIP,
|
|
222
229
|
[OPENAI_TRAINING]: GPT_4O_MINI, // https://platform.openai.com/docs/guides/fine-tuning
|
|
223
230
|
};
|
|
224
231
|
DEFAULT_MODELS[CHAT] = DEFAULT_MODELS[GEMINI];
|
|
@@ -239,7 +246,7 @@ let tokeniser;
|
|
|
239
246
|
const unifyProvider = provider => {
|
|
240
247
|
assert(provider = (provider || '').trim(), 'AI provider is required.');
|
|
241
248
|
for (let type of [OPENAI, AZURE_OPENAI, AZURE, GEMINI, ANTHROPIC,
|
|
242
|
-
VERTEX_ANTHROPIC, OLLAMA]) {
|
|
249
|
+
VERTEX_ANTHROPIC, JINA_DEEPSEARCH, OLLAMA]) {
|
|
243
250
|
if (insensitiveCompare(provider, type)) { return type; }
|
|
244
251
|
}
|
|
245
252
|
throwError(`Invalid AI provider: ${provider}.`);
|
|
@@ -410,6 +417,19 @@ const init = async (options = {}) => {
|
|
|
410
417
|
prompt: async (cnt, opts) => await promptAnthropic(id, cnt, opts),
|
|
411
418
|
});
|
|
412
419
|
break;
|
|
420
|
+
case JINA_DEEPSEARCH:
|
|
421
|
+
assertApiKey(provider, options);
|
|
422
|
+
ais.push({
|
|
423
|
+
id, provider, model, priority: 0, initOrder: ais.length,
|
|
424
|
+
client: await OpenAI({
|
|
425
|
+
baseURL: 'https://deepsearch.jina.ai/v1/', ...options,
|
|
426
|
+
}),
|
|
427
|
+
prompt: async (cnt, opts) => await promptOpenAI(id, cnt, opts),
|
|
428
|
+
embedding: async (i, o) => await createJinaEmbedding(await OpenAI({
|
|
429
|
+
baseURL: 'https://api.jina.ai/v1/', ...options,
|
|
430
|
+
}), i, o),
|
|
431
|
+
});
|
|
432
|
+
break;
|
|
413
433
|
case OLLAMA:
|
|
414
434
|
// https://github.com/ollama/ollama/blob/main/docs/openai.md
|
|
415
435
|
const baseURL = 'http://localhost:11434/v1/';
|
|
@@ -1043,7 +1063,7 @@ const deleteFile = async (aiId, file_id, options) => {
|
|
|
1043
1063
|
|
|
1044
1064
|
const generationConfig = options => ({
|
|
1045
1065
|
generationConfig: {
|
|
1046
|
-
responseMimeType: options.jsonMode ?
|
|
1066
|
+
responseMimeType: options.jsonMode ? MIME_JSON : MIME_TEXT,
|
|
1047
1067
|
responseModalities: options.modalities
|
|
1048
1068
|
|| (options.imageMode ? [TEXT, IMAGE] : undefined),
|
|
1049
1069
|
...options?.generationConfig || {},
|
|
@@ -1152,7 +1172,7 @@ const checkEmbeddingInput = async (input, model) => {
|
|
|
1152
1172
|
return getInput();
|
|
1153
1173
|
};
|
|
1154
1174
|
|
|
1155
|
-
const createOpenAIEmbedding = async (
|
|
1175
|
+
const createOpenAIEmbedding = async (client, input, options) => {
|
|
1156
1176
|
// args from vertex embedding may be useful uere
|
|
1157
1177
|
// https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings
|
|
1158
1178
|
// task_type Description
|
|
@@ -1161,7 +1181,7 @@ const createOpenAIEmbedding = async (aiId, input, options) => {
|
|
|
1161
1181
|
// SEMANTIC_SIMILARITY Specifies the given text will be used for Semantic Textual Similarity(STS).
|
|
1162
1182
|
// CLASSIFICATION Specifies that the embeddings will be used for classification.
|
|
1163
1183
|
// CLUSTERING Specifies that the embeddings will be used for clustering.
|
|
1164
|
-
|
|
1184
|
+
String.isString(client) && (client = (await getAi(client)).client);
|
|
1165
1185
|
const model = options?.model || DEFAULT_MODELS[OPENAI_EMBEDDING];
|
|
1166
1186
|
const resp = await client.embeddings.create({
|
|
1167
1187
|
model, input: await checkEmbeddingInput(input, model),
|
|
@@ -1169,6 +1189,11 @@ const createOpenAIEmbedding = async (aiId, input, options) => {
|
|
|
1169
1189
|
return options?.raw ? resp : resp?.data[0].embedding;
|
|
1170
1190
|
};
|
|
1171
1191
|
|
|
1192
|
+
const createJinaEmbedding = async (client, input, options) =>
|
|
1193
|
+
await createOpenAIEmbedding(client, input, {
|
|
1194
|
+
model: DEFAULT_MODELS[JINA_EMBEDDING], ...options || {}
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1172
1197
|
const createGeminiEmbedding = async (aiId, input, options) => {
|
|
1173
1198
|
const { client } = await getAi(aiId);
|
|
1174
1199
|
const model = options?.model || DEFAULT_MODELS[GEMINI_EMEDDING];
|
package/lib/bot.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { jpeg, ogg, wav } from './alan.mjs';
|
|
|
17
17
|
import { isPrimary, on, report } from './callosum.mjs';
|
|
18
18
|
import { cleanSql, encodeVector, MYSQL, POSTGRESQL } from './dbio.mjs';
|
|
19
19
|
import { convertAudioTo16kNanoPcmWave } from './media.mjs';
|
|
20
|
-
import { get } from './
|
|
20
|
+
import { get } from './web.mjs';
|
|
21
21
|
import { OPENAI_TTS_MAX_LENGTH } from './speech.mjs';
|
|
22
22
|
import { BASE64, BUFFER, convert, FILE, isTextFile, tryRm } from './storage.mjs';
|
|
23
23
|
import { fakeUuid } from './uoid.mjs';
|
package/lib/manifest.mjs
CHANGED
package/lib/network.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertExist } from './shell.mjs';
|
|
2
2
|
import { ensureArray, log as _log, need, throwError } from './utilitas.mjs';
|
|
3
|
-
import { getCurrentIp } from './
|
|
3
|
+
import { getCurrentIp } from './web.mjs';
|
|
4
4
|
|
|
5
5
|
const _NEED = ['fast-geoip', 'ping'];
|
|
6
6
|
const isLocalhost = host => ['127.0.0.1', '::1', 'localhost'].includes(host);
|
package/lib/speech.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_MODELS, OPENAI_VOICE } from './alan.mjs';
|
|
2
2
|
import { getApiKeyCredentials, hash } from './encryption.mjs';
|
|
3
3
|
import { getFfmpeg } from './media.mjs';
|
|
4
|
-
import { get } from './
|
|
4
|
+
import { get } from './web.mjs';
|
|
5
5
|
import { convert, getTempPath } from './storage.mjs';
|
|
6
6
|
import { ensureString } from './utilitas.mjs';
|
|
7
7
|
|
package/lib/storage.mjs
CHANGED
|
@@ -26,11 +26,11 @@ const sanitizeFilename = (s, r) => s.replace(/[\/?<>\\:*|"]/g, r || '_').trim();
|
|
|
26
26
|
|
|
27
27
|
const [
|
|
28
28
|
NULL, BASE64, BUFFER, FILE, STREAM, TEXT, _JSON, encoding, BINARY, BLOB,
|
|
29
|
-
DATAURL, mode, dirMode, MIME_TEXT, MIME_BINARY
|
|
29
|
+
DATAURL, mode, dirMode, MIME_TEXT, MIME_BINARY, MIME_JSON,
|
|
30
30
|
] = [
|
|
31
31
|
'NULL', 'BASE64', 'BUFFER', 'FILE', 'STREAM', 'TEXT', 'JSON', 'utf8',
|
|
32
32
|
'binary', 'BLOB', 'DATAURL', '0644', '0755', 'text/plain',
|
|
33
|
-
'application/octet-stream',
|
|
33
|
+
'application/octet-stream', 'application/json',
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
const [encodeBase64, encodeBinary, encodeNull]
|
|
@@ -468,15 +468,28 @@ const deleteOnCloud = async (path, options) => {
|
|
|
468
468
|
};
|
|
469
469
|
|
|
470
470
|
export {
|
|
471
|
-
_NEED,
|
|
472
|
-
|
|
471
|
+
_NEED,
|
|
472
|
+
BUFFER,
|
|
473
|
+
BASE64,
|
|
474
|
+
DATAURL,
|
|
475
|
+
FILE,
|
|
476
|
+
MIME_BINARY,
|
|
477
|
+
MIME_TEXT,
|
|
478
|
+
MIME_JSON,
|
|
479
|
+
STREAM,
|
|
480
|
+
analyzeFile,
|
|
481
|
+
assertPath,
|
|
482
|
+
blobToBuffer,
|
|
483
|
+
convert,
|
|
484
|
+
decodeBase64DataURL,
|
|
473
485
|
deleteFileOnCloud,
|
|
474
486
|
deleteOnCloud,
|
|
475
487
|
downloadFileFromCloud,
|
|
476
488
|
downloadFromCloud,
|
|
477
489
|
encodeBase64DataURL,
|
|
478
490
|
exists,
|
|
479
|
-
existsOnCloud,
|
|
491
|
+
existsOnCloud,
|
|
492
|
+
getConfig,
|
|
480
493
|
getConfigFilename,
|
|
481
494
|
getGcUrlByBucket,
|
|
482
495
|
getIdByGs,
|
|
@@ -487,11 +500,13 @@ export {
|
|
|
487
500
|
legalFilename,
|
|
488
501
|
lsOnCloud,
|
|
489
502
|
mapFilename,
|
|
490
|
-
mergeFile,
|
|
503
|
+
mergeFile,
|
|
504
|
+
readFile,
|
|
491
505
|
readJson,
|
|
492
506
|
sanitizeFilename,
|
|
493
507
|
setConfig,
|
|
494
|
-
sliceFile,
|
|
508
|
+
sliceFile,
|
|
509
|
+
touchPath,
|
|
495
510
|
tryRm,
|
|
496
511
|
unzip,
|
|
497
512
|
uploadToCloud,
|
package/lib/utilitas.mjs
CHANGED
|
@@ -742,9 +742,11 @@ const getFuncParams = (func) => {
|
|
|
742
742
|
const analyzeModule = (obj) => {
|
|
743
743
|
assertModule(obj);
|
|
744
744
|
const [keys, result] = [Object.getOwnPropertyNames(obj), {}];
|
|
745
|
-
keys.sort()
|
|
745
|
+
keys.sort();
|
|
746
|
+
keys.filter(x => x !== 'INSTRUCTIONS').map(key => result[key] = {
|
|
746
747
|
type: getType(obj[key]), ...Function.isFunction(obj[key])
|
|
747
|
-
? { params: getFuncParams(obj[key]) }
|
|
748
|
+
? { params: getFuncParams(obj[key]) }
|
|
749
|
+
: { value: ensureString(obj[key]) }
|
|
748
750
|
});
|
|
749
751
|
return result;
|
|
750
752
|
};
|
package/lib/web.mjs
CHANGED
|
@@ -1,14 +1,245 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { bignumber, divide, multiply } from 'mathjs';
|
|
2
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import { sha256 } from './encryption.mjs';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
import {
|
|
8
|
+
ensureInt, ensureString, extract, ignoreErrFunc, inBrowser, parseJson,
|
|
9
|
+
parseVersion, throwError, which, assertSet, assembleUrl, need,
|
|
10
|
+
} from './utilitas.mjs';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
MIME_JSON, MIME_TEXT, convert, encodeBase64DataURL, exists, mapFilename,
|
|
14
|
+
readJson, touchPath, writeJson,
|
|
15
|
+
} from './storage.mjs';
|
|
6
16
|
|
|
17
|
+
const _NEED = ['jsdom', 'youtube-transcript', '@mozilla/readability'];
|
|
7
18
|
// https://stackoverflow.com/questions/19377262/regex-for-youtube-url
|
|
8
19
|
const YT_REGEXP = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/i;
|
|
9
20
|
const isYoutubeUrl = url => (url || '').match(YT_REGEXP)?.[6];
|
|
10
21
|
const distillPage = async (url, op) => (await getParsedHtml(url, op))?.content;
|
|
11
|
-
const
|
|
22
|
+
const TMPDIR = process.env.TMPDIR ? join(process.env.TMPDIR, 'shot') : null;
|
|
23
|
+
const buf2utf = buf => buf.toString('utf8');
|
|
24
|
+
const [TEXT, _JSON, _PARSED] = ['TEXT', 'JSON', 'PARSED'];
|
|
25
|
+
const getJson = async (u, o) => await get(u, { encode: _JSON, ...o || {} });
|
|
26
|
+
const getParsedHtml = async (u, o) => await get(u, { encode: _PARSED, ...o || {} });
|
|
27
|
+
const checkSearch = () => googleApiKey || jinaApiKey;
|
|
28
|
+
|
|
29
|
+
let googleApiKey, googleCx, jinaApiKey;
|
|
30
|
+
|
|
31
|
+
const defFetchOpt = {
|
|
32
|
+
redirect: 'follow', follow: 3, timeout: 1000 * 10, headers: {
|
|
33
|
+
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) '
|
|
34
|
+
+ 'AppleWebKit/605.1.15 (KHTML, like Gecko) '
|
|
35
|
+
+ 'Version/17.0 Safari/605.1.15',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getVersionOnNpm = async (packName) => {
|
|
40
|
+
assert(packName, 'Package name is required.', 400);
|
|
41
|
+
const url = `https://registry.npmjs.org/-/package/${packName}/dist-tags`;
|
|
42
|
+
const rp = (await get(url, { encode: _JSON }))?.content;
|
|
43
|
+
assert(rp, 'Error fetching package info.', 500);
|
|
44
|
+
assert(rp !== 'Not Found' && rp.latest, 'Package not found.', 404);
|
|
45
|
+
return parseVersion(rp.latest);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const checkVersion = async (pack) => {
|
|
49
|
+
const objPack = await which(pack);
|
|
50
|
+
const curVersion = objPack.versionNormalized;
|
|
51
|
+
const newVersion = await getVersionOnNpm(objPack.name);
|
|
52
|
+
return {
|
|
53
|
+
name: objPack.name, curVersion, newVersion,
|
|
54
|
+
updateAvailable: newVersion.normalized > curVersion.normalized,
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const getCurrentIp = async (options) => {
|
|
59
|
+
const resp = await get(
|
|
60
|
+
'https://ifconfig.me/all.json', { encode: _JSON, ...options || {} }
|
|
61
|
+
);
|
|
62
|
+
assert(resp?.content?.ip_addr, 'Error detecting IP address.', 500);
|
|
63
|
+
return options?.raw ? resp : resp.content.ip_addr;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getCurrentPosition = async () => {
|
|
67
|
+
const url = 'https://geolocation-db.com/json/';
|
|
68
|
+
const rp = await fetch(url).then(res => res.json());
|
|
69
|
+
assert(rp, 'Network is unreachable.', 500);
|
|
70
|
+
assert(rp.country_code, 'Error detecting geolocation.', 500);
|
|
71
|
+
return rp;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const get = async (url, options) => {
|
|
75
|
+
assert(url, 'URL is required.', 400);
|
|
76
|
+
options = options || {};
|
|
77
|
+
options.encode = ensureString(options.encode, { case: 'UP' });
|
|
78
|
+
const urlHash = inBrowser() ? null : sha256(url);
|
|
79
|
+
const tmp = urlHash ? (options.cache?.tmp || TMPDIR) : null;
|
|
80
|
+
const base = tmp ? join(tmp, mapFilename(urlHash)) : null;
|
|
81
|
+
const [cacheMeta, cacheCont] = base ? ['meta', 'content'].map(
|
|
82
|
+
x => join(base, `${urlHash}.${x}`)
|
|
83
|
+
) : [];
|
|
84
|
+
if (options?.fuzzy && await exists(cacheMeta) && await exists(cacheCont)) {
|
|
85
|
+
return { cache: { meta: cacheMeta, content: cacheCont } };
|
|
86
|
+
}
|
|
87
|
+
const meta = options?.refresh || !base ? null : await readJson(cacheMeta);
|
|
88
|
+
const cache = options?.refresh || !base ? null : await ignoreErrFunc(
|
|
89
|
+
() => fs.readFile(cacheCont)
|
|
90
|
+
);
|
|
91
|
+
const headers = meta?.responseHeaders && cache ? {
|
|
92
|
+
'cache-control': 'max-age=0',
|
|
93
|
+
'if-modified-since': meta.responseHeaders['last-modified'] || '',
|
|
94
|
+
'if-none-match': meta.responseHeaders['etag'] || '',
|
|
95
|
+
} : {};
|
|
96
|
+
let [timer, r, responseHeaders] = [null, null, {}];
|
|
97
|
+
const fetchOptions = {
|
|
98
|
+
...defFetchOpt, headers: { ...defFetchOpt.headers, ...headers },
|
|
99
|
+
...options.fetch || {}
|
|
100
|
+
};
|
|
101
|
+
if (options.timeout) {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
fetchOptions.signal = controller.signal;
|
|
104
|
+
timer = setTimeout(() => controller.abort(), options.timeout);
|
|
105
|
+
}
|
|
106
|
+
try { r = await fetch(url, fetchOptions); } catch (e) {
|
|
107
|
+
throwError(e.message.includes('aborted') ? 'Timed out.' : e.message, 500);
|
|
108
|
+
}
|
|
109
|
+
timer && clearTimeout(timer);
|
|
110
|
+
(r.status === 304) && (r.arrayBuffer = async () => cache);
|
|
111
|
+
const [htpMime, buffer] = [r.headers.get('content-type'), Buffer.from(await r.arrayBuffer())];
|
|
112
|
+
if (r.headers?.raw) { responseHeaders = r.headers.raw(); }
|
|
113
|
+
else { for (const [k, v] of r.headers.entries()) { responseHeaders[k] = v; } }
|
|
114
|
+
const bufMime = await ignoreErrFunc(async () => {
|
|
115
|
+
extract(await fileTypeFromBuffer(buffer), 'mime');
|
|
116
|
+
});
|
|
117
|
+
const mimeType = bufMime || htpMime;
|
|
118
|
+
const length = buffer.length;
|
|
119
|
+
let content;
|
|
120
|
+
if (!options?.fuzzy) {
|
|
121
|
+
switch (options.encode) {
|
|
122
|
+
case 'BUFFER':
|
|
123
|
+
content = buffer;
|
|
124
|
+
break;
|
|
125
|
+
case 'BASE64':
|
|
126
|
+
content = buffer.toString(options.encode);
|
|
127
|
+
break;
|
|
128
|
+
case 'BASE64_DATA_URL':
|
|
129
|
+
content = await encodeBase64DataURL(mimeType, buffer);
|
|
130
|
+
break;
|
|
131
|
+
case _JSON:
|
|
132
|
+
content = parseJson(buf2utf(buffer), null);
|
|
133
|
+
break;
|
|
134
|
+
case _PARSED:
|
|
135
|
+
content = await distillHtml(buf2utf(buffer));
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
assert(!options.encode, 'Invalid encoding.', 400);
|
|
139
|
+
case 'TEXT':
|
|
140
|
+
content = buf2utf(buffer);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
base && !cache && length && r.status === 200
|
|
144
|
+
&& await ignoreErrFunc(async () => {
|
|
145
|
+
return {
|
|
146
|
+
touch: await touchPath(base),
|
|
147
|
+
content: await fs.writeFile(cacheCont, buffer),
|
|
148
|
+
meta: await writeJson(cacheMeta, {
|
|
149
|
+
url, requestHeaders: headers, responseHeaders,
|
|
150
|
+
}),
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
statusCode: r.status, statusText: r.statusText, length, mimeType,
|
|
155
|
+
content, headers: responseHeaders, response: r,
|
|
156
|
+
cache: r.status >= 200 && r.status < 400 ? { meta: cacheMeta, content: cacheCont } : null,
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const initSearch = async (options = {}) => {
|
|
161
|
+
assert(options.apiKey, 'API key is required.');
|
|
162
|
+
switch (ensureString(options.provider, { case: 'UP' })) {
|
|
163
|
+
case 'GOOGLE':
|
|
164
|
+
assert(options.cx, 'CX is required for Google Search API.');
|
|
165
|
+
[googleApiKey, googleCx] = [options.apiKey, options.cx];
|
|
166
|
+
break;
|
|
167
|
+
case 'JINA':
|
|
168
|
+
jinaApiKey = options.apiKey;
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
throwError(`Invalid search provider: "${options.provider}".`);
|
|
172
|
+
}
|
|
173
|
+
return async (query, opts) => await search(query, { ...options, ...opts });
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const search = async (query, options = {}) => {
|
|
177
|
+
assert(query, 'Query keyword is required.');
|
|
178
|
+
let provider = ensureString(options.provider, { case: 'UP' });
|
|
179
|
+
if (!provider && googleApiKey) { provider = 'GOOGLE'; }
|
|
180
|
+
if (!provider && jinaApiKey) { provider = 'JINA'; }
|
|
181
|
+
switch (provider) {
|
|
182
|
+
case 'GOOGLE':
|
|
183
|
+
var [key, cx, min, max] = [
|
|
184
|
+
options?.apiKey || googleApiKey, options?.cx || googleCx, 1, 10
|
|
185
|
+
];
|
|
186
|
+
assert(key, 'API key is required.');
|
|
187
|
+
assert(cx, 'CX is required.');
|
|
188
|
+
var [num, start] = [
|
|
189
|
+
ensureInt(options?.num || max, { min, max }),
|
|
190
|
+
ensureInt(options?.start || min, { min }),
|
|
191
|
+
];
|
|
192
|
+
assert(start + num <= 100, 'Reached maximum search limit.');
|
|
193
|
+
var url = 'https://www.googleapis.com/customsearch/v1'
|
|
194
|
+
+ `?key=${encodeURIComponent(key)}&cx=${encodeURIComponent(cx)}`
|
|
195
|
+
+ `&q=${encodeURIComponent(query)}&num=${num}&start=${start}`
|
|
196
|
+
+ (options?.image ? `&searchType=image` : '');
|
|
197
|
+
var resp = await get(url, { encode: _JSON, ...options || {} });
|
|
198
|
+
return options?.raw ? resp.content : {
|
|
199
|
+
totalResults: resp?.content?.searchInformation?.totalResults || 0,
|
|
200
|
+
startIndex: resp?.content?.queries?.request?.[0]?.startIndex || 1,
|
|
201
|
+
items: resp?.content?.items.map(x => ({
|
|
202
|
+
title: x.title, link: options?.image ? null : x.link,
|
|
203
|
+
snippet: x.snippet, image: (
|
|
204
|
+
options?.image ? x.link : x.pagemap?.cse_image?.[0]?.src
|
|
205
|
+
) || null,
|
|
206
|
+
})), provider,
|
|
207
|
+
};
|
|
208
|
+
case 'JINA':
|
|
209
|
+
var [key, min, def, max] =
|
|
210
|
+
[options?.apiKey || jinaApiKey, 1, 10, 20];
|
|
211
|
+
assert(key, 'API key is required.');
|
|
212
|
+
var [num, start] = [
|
|
213
|
+
ensureInt(options?.num || def, { min, max }),
|
|
214
|
+
ensureInt(options?.start || min, { min }),
|
|
215
|
+
];
|
|
216
|
+
var url = `https://s.jina.ai/?q=${encodeURIComponent(query)}`
|
|
217
|
+
+ `&page=${encodeURIComponent(start - 1)}`
|
|
218
|
+
+ `&num=${encodeURIComponent(num)}`
|
|
219
|
+
var resp = await get(url, {
|
|
220
|
+
encode: options?.aiFriendly ? 'TEXT' : _JSON,
|
|
221
|
+
fetch: {
|
|
222
|
+
headers: {
|
|
223
|
+
'Authorization': `Bearer ${key}`,
|
|
224
|
+
'Accept': options?.aiFriendly ? MIME_TEXT : MIME_JSON,
|
|
225
|
+
'X-Respond-With': 'no-content',
|
|
226
|
+
...options?.aiFriendly ? {} : { 'X-With-Favicons': true },
|
|
227
|
+
}
|
|
228
|
+
}, ...options || {},
|
|
229
|
+
});
|
|
230
|
+
if (options?.raw) { return resp; }
|
|
231
|
+
if (options?.aiFriendly) { return resp?.content; }
|
|
232
|
+
return {
|
|
233
|
+
totalResults: 100, startIndex: start,
|
|
234
|
+
items: (resp?.content?.data || []).map(x => ({
|
|
235
|
+
title: x.title, link: x.url,
|
|
236
|
+
snippet: x.description, image: x.favicon,
|
|
237
|
+
})), provider,
|
|
238
|
+
};
|
|
239
|
+
default:
|
|
240
|
+
throwError(`Invalid search provider: "${options.provider}".`);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
12
243
|
|
|
13
244
|
const distillHtml = async (input, options) => {
|
|
14
245
|
const html = await convert(input, {
|
|
@@ -105,14 +336,46 @@ const distill = async url => {
|
|
|
105
336
|
};
|
|
106
337
|
};
|
|
107
338
|
|
|
339
|
+
const _getRate = (rates, currency) => {
|
|
340
|
+
const rate = rates[ensureString(currency || 'USD', { case: 'UP' })];
|
|
341
|
+
assertSet(rate, `Unsupported currency: '${currency}'.`, 400);
|
|
342
|
+
return bignumber(rate);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const getExchangeRate = async (to, from, amount) => {
|
|
346
|
+
const data = {};
|
|
347
|
+
((await get(
|
|
348
|
+
'https://api.mixin.one/external/fiats', { encode: 'JSON' }
|
|
349
|
+
))?.content?.data || []).map(x => data[x.code] = x.rate);
|
|
350
|
+
assert(Object.keys(data).length, 'Error fetching exchange rates.', 500);
|
|
351
|
+
if (!to) { return data; }
|
|
352
|
+
[to, from] = [_getRate(data, to), _getRate(data, from)];
|
|
353
|
+
const rate = divide(to, from);
|
|
354
|
+
amount = multiply(bignumber(amount ?? 1), rate);
|
|
355
|
+
return { rate, amount: amount.toString() };
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
export default get;
|
|
108
360
|
export {
|
|
109
361
|
_NEED,
|
|
110
362
|
assertYoutubeUrl,
|
|
363
|
+
checkSearch,
|
|
364
|
+
checkVersion,
|
|
111
365
|
distill,
|
|
112
366
|
distillHtml,
|
|
113
367
|
distillPage,
|
|
114
368
|
distillYoutube,
|
|
369
|
+
get,
|
|
370
|
+
getCurrentIp,
|
|
371
|
+
getCurrentPosition,
|
|
372
|
+
getExchangeRate,
|
|
373
|
+
getJson,
|
|
374
|
+
getParsedHtml,
|
|
375
|
+
getVersionOnNpm,
|
|
115
376
|
getYoutubeMetadata,
|
|
116
377
|
getYoutubeTranscript,
|
|
117
|
-
|
|
378
|
+
initSearch,
|
|
379
|
+
isYoutubeUrl,
|
|
380
|
+
search,
|
|
118
381
|
};
|
package/package.json
CHANGED
package/lib/shekel.mjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { assertSet, ensureString } from './utilitas.mjs';
|
|
2
|
-
import { bignumber, divide, multiply } from 'mathjs';
|
|
3
|
-
import { get } from './shot.mjs';
|
|
4
|
-
|
|
5
|
-
const _getRate = (rates, currency) => {
|
|
6
|
-
const rate = rates[ensureString(currency || 'USD', { case: 'UP' })];
|
|
7
|
-
assertSet(rate, `Unsupported currency: '${currency}'.`, 400);
|
|
8
|
-
return bignumber(rate);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const getExchangeRate = async (to, from, amount) => {
|
|
12
|
-
const data = {};
|
|
13
|
-
((await get(
|
|
14
|
-
'https://api.mixin.one/external/fiats', { encode: 'JSON' }
|
|
15
|
-
))?.content?.data || []).map(x => data[x.code] = x.rate);
|
|
16
|
-
assert(Object.keys(data).length, 'Error fetching exchange rates.', 500);
|
|
17
|
-
if (!to) { return data; }
|
|
18
|
-
[to, from] = [_getRate(data, to), _getRate(data, from)];
|
|
19
|
-
const rate = divide(to, from);
|
|
20
|
-
amount = multiply(bignumber(amount ?? 1), rate);
|
|
21
|
-
return { rate, amount: amount.toString() };
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export { getExchangeRate };
|