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/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, shekel, shell,
45
- shot, sms, speech, ssl, storage, tape, uoid, utilitas, vision, web
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, shekel, shot, speech,
51
- storage, uoid, utilitas, uuid,
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 { fileTypeFromBuffer } from 'file-type';
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 { distill } from './web.mjs';
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
- renderText as _renderText,
12
- base64Encode, ensureArray, ensureString, extract,
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, mimeJson, mimeText, pcm16,
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', 'application/json',
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, mimeText, webp, pdf],
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 ? mimeJson : mimeText,
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 './shot.mjs';
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
@@ -1,7 +1,7 @@
1
1
  const manifest = {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1999.1.12",
4
+ "version": "1999.1.13",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
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 './shot.mjs';
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 './shot.mjs';
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, analyzeFile,
472
- assertPath, BASE64, blobToBuffer, BUFFER, convert, DATAURL, decodeBase64DataURL,
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, FILE, getConfig,
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, MIME_BINARY, readFile,
503
+ mergeFile,
504
+ readFile,
491
505
  readJson,
492
506
  sanitizeFilename,
493
507
  setConfig,
494
- sliceFile, STREAM, touchPath,
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().map(key => result[key] = {
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]) } : { value: ensureString(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 { getJson, getParsedHtml } from './shot.mjs';
2
- import { convert } from './storage.mjs';
3
- import { assembleUrl, ignoreErrFunc, need, throwError } from './utilitas.mjs';
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
- const _NEED = ['jsdom', 'youtube-transcript', '@mozilla/readability'];
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 TEXT = 'TEXT';
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
- isYoutubeUrl
378
+ initSearch,
379
+ isYoutubeUrl,
380
+ search,
118
381
  };
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": "1999.1.12",
4
+ "version": "1999.1.13",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
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 };