utilitas 1999.1.12 → 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 +14 -44
- package/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/index.mjs +4 -6
- package/lib/alan.mjs +15 -20
- 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';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
BASE64, BUFFER, DATAURL, MIME_BINARY, MIME_JSON, MIME_TEXT, STREAM, convert,
|
|
9
|
+
} from './storage.mjs';
|
|
8
10
|
|
|
9
11
|
import {
|
|
10
|
-
log as _log,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
ignoreErrFunc,
|
|
14
|
-
insensitiveCompare,
|
|
15
|
-
isSet,
|
|
16
|
-
need, parseJson,
|
|
17
|
-
throwError
|
|
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 [
|
|
@@ -173,7 +168,7 @@ const MODELS = {
|
|
|
173
168
|
[JINA_DEEPSEARCH_M]: {
|
|
174
169
|
contextWindow: Infinity, maxInputTokens: Infinity,
|
|
175
170
|
maxOutputTokens: Infinity, imageCostTokens: 0, maxImageSize: Infinity,
|
|
176
|
-
supportedMimeTypes: [png, jpeg,
|
|
171
|
+
supportedMimeTypes: [png, jpeg, MIME_TEXT, webp, pdf],
|
|
177
172
|
reasoning: true, json: true, vision: true,
|
|
178
173
|
},
|
|
179
174
|
[DEEPSEEK_R1]: {
|
|
@@ -1068,7 +1063,7 @@ const deleteFile = async (aiId, file_id, options) => {
|
|
|
1068
1063
|
|
|
1069
1064
|
const generationConfig = options => ({
|
|
1070
1065
|
generationConfig: {
|
|
1071
|
-
responseMimeType: options.jsonMode ?
|
|
1066
|
+
responseMimeType: options.jsonMode ? MIME_JSON : MIME_TEXT,
|
|
1072
1067
|
responseModalities: options.modalities
|
|
1073
1068
|
|| (options.imageMode ? [TEXT, IMAGE] : undefined),
|
|
1074
1069
|
...options?.generationConfig || {},
|
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 };
|