html-get 2.24.10 → 2.24.11

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +65 -56
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "html-get",
3
3
  "description": "Get the HTML from any website, fine-tuned for correction & speed",
4
4
  "homepage": "https://nicedoc.com/microlinkhq/html-get",
5
- "version": "2.24.10",
5
+ "version": "2.24.11",
6
6
  "types": "index.d.ts",
7
7
  "main": "src/index.js",
8
8
  "bin": {
package/src/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { parseUrl, isMediaUrl, isPdfUrl } = require('@metascraper/helpers')
3
+ const { parseUrl, isMediaUrl, isPdfUrl, memoizeOne } = require('@metascraper/helpers')
4
4
  const { readFile, writeFile } = require('fs/promises')
5
5
  const timeSpan = require('@kikobeats/time-span')()
6
6
  const debug = require('debug-logfmt')('html-get')
7
- const { execSync } = require('child_process')
7
+ const { execFileSync } = require('child_process')
8
8
  const PCancelable = require('p-cancelable')
9
9
  const { AbortError } = require('p-retry')
10
10
  const htmlEncode = require('html-encode')
@@ -28,15 +28,7 @@ const PDF_SIZE_TRESHOLD = 150 * 1024 // 150kb
28
28
  const fetch = PCancelable.fn(
29
29
  async (
30
30
  url,
31
- {
32
- getTemporalFile,
33
- mutool,
34
- pandoc,
35
- reflect = false,
36
- timeout = REQ_TIMEOUT,
37
- toEncode,
38
- ...opts
39
- },
31
+ { getTemporalFile, mutool, pandoc, reflect = false, timeout = REQ_TIMEOUT, toEncode, ...opts },
40
32
  onCancel
41
33
  ) => {
42
34
  const reqTimeout = reflect ? timeout / 2 : timeout
@@ -55,9 +47,7 @@ const fetch = PCancelable.fn(
55
47
  })
56
48
 
57
49
  const redirects = []
58
- req.on('redirect', res =>
59
- redirects.push({ statusCode: res.statusCode, url: res.url })
60
- )
50
+ req.on('redirect', res => redirects.push({ statusCode: res.statusCode, url: res.url }))
61
51
 
62
52
  try {
63
53
  const res = await req
@@ -65,8 +55,7 @@ const fetch = PCancelable.fn(
65
55
  const html = await (async () => {
66
56
  const contentType = getContentType(res.headers)
67
57
 
68
- const officeFormat =
69
- pandoc && getOfficeFormat({ contentType, url: [res.url, url] })
58
+ const officeFormat = pandoc && getOfficeFormat({ contentType, url: [res.url, url] })
70
59
 
71
60
  // a recognized office file never goes through mutool, even if the
72
61
  // response is mislabeled as application/pdf
@@ -86,7 +75,17 @@ const fetch = PCancelable.fn(
86
75
  if (officeFormat) {
87
76
  const file = getTemporalFile(res.url, officeFormat)
88
77
  await writeFile(file.path, res.body)
89
- return pandoc(officeFormat, file.path)
78
+ try {
79
+ const converted = await pandoc(officeFormat, file.path)
80
+ if (converted) return converted
81
+ debug('pandoc:empty', { url: res.url, format: officeFormat })
82
+ } catch (error) {
83
+ debug('pandoc:error', {
84
+ url: res.url,
85
+ format: officeFormat,
86
+ message: error.message || error
87
+ })
88
+ }
90
89
  }
91
90
 
92
91
  return contentType === 'text/html' || !isMediaUrl(url)
@@ -232,12 +231,7 @@ const isFetchMode = url => {
232
231
  }
233
232
 
234
233
  const defaultGetMode = (url, { prerender }) => {
235
- if (
236
- prerender === false ||
237
- isMediaUrl(url) ||
238
- isPdfUrl(url) ||
239
- isOfficeUrl(url)
240
- ) {
234
+ if (prerender === false || isMediaUrl(url) || isPdfUrl(url) || isOfficeUrl(url)) {
241
235
  return 'fetch'
242
236
  }
243
237
  if (prerender === true) return 'prerender'
@@ -246,41 +240,60 @@ const defaultGetMode = (url, { prerender }) => {
246
240
 
247
241
  const defaultGetTemporalFile = (input, ext) => {
248
242
  const hash = crypto.createHash('sha256').update(input).digest('hex')
249
- const filepath = path.join(
250
- os.tmpdir(),
251
- ext === undefined ? hash : `${hash}.${ext}`
252
- )
243
+ const filepath = path.join(os.tmpdir(), ext === undefined ? hash : `${hash}.${ext}`)
253
244
  return { path: filepath }
254
245
  }
255
246
 
256
- const defaultMutool = () =>
257
- (() => {
258
- try {
259
- const mutoolPath = execSync('which mutool', {
260
- stdio: ['pipe', 'pipe', 'ignore']
261
- })
262
- .toString()
263
- .trim()
264
- return (...args) => $(`${mutoolPath} draw -q -F html ${args}`)
265
- } catch (_) {}
266
- })()
247
+ // use execFileSync (no shell) to resolve the binary path: faster than execSync
248
+ // and free of any interpolation surface.
249
+ const whichSync = bin => {
250
+ try {
251
+ return execFileSync('which', [bin], {
252
+ stdio: ['pipe', 'pipe', 'ignore']
253
+ })
254
+ .toString()
255
+ .trim()
256
+ } catch (_) {}
257
+ }
267
258
 
268
- const defaultPandoc = () =>
269
- (() => {
270
- try {
271
- const pandocPath = execSync('which pandoc', {
259
+ // probe the binary once, not per request: the default runners are wired as
260
+ // default parameters (evaluated on every call), so memoizeOne keeps the probe
261
+ // (`which` and, for pandoc, `--list-input-formats`) from re-spawning each time.
262
+ const defaultMutool = memoizeOne(() => {
263
+ const mutoolPath = whichSync('mutool')
264
+ if (!mutoolPath) return
265
+ return (...args) => $(`${mutoolPath} draw -q -F html ${args}`)
266
+ })
267
+
268
+ const defaultPandoc = memoizeOne(() => {
269
+ try {
270
+ const pandocPath = whichSync('pandoc')
271
+ if (!pandocPath) return
272
+ // a broken/incompatible pandoc can throw here (or on the runner below); in
273
+ // either case conversion stays disabled instead of breaking every getHTML
274
+ // call through the default-parameter evaluation
275
+ const supported = new Set(
276
+ execFileSync(pandocPath, ['--list-input-formats'], {
272
277
  stdio: ['pipe', 'pipe', 'ignore']
273
278
  })
274
279
  .toString()
275
280
  .trim()
276
- return async (format, filepath) => {
277
- const { stdout } = await $(
278
- `${pandocPath} --from=${format} --to=html --standalone --embed-resources ${filepath}`
279
- )
280
- return stdout
281
- }
282
- } catch (_) {}
283
- })()
281
+ .split(/\s+/)
282
+ )
283
+ return async (format, filepath) => {
284
+ if (!supported.has(format)) return
285
+ // array form keeps `filepath` a single argv entry even if it has spaces
286
+ const { stdout } = await $(pandocPath, [
287
+ `--from=${format}`,
288
+ '--to=html',
289
+ '--standalone',
290
+ '--embed-resources',
291
+ filepath
292
+ ])
293
+ return stdout.trim() ? stdout : undefined
294
+ }
295
+ } catch (_) {}
296
+ })
284
297
 
285
298
  const getContent = PCancelable.fn(
286
299
  (
@@ -372,12 +385,7 @@ module.exports = PCancelable.fn(
372
385
 
373
386
  let shadowDOM = hasShadowDOM($)
374
387
 
375
- if (
376
- mode === 'fetch' &&
377
- getBrowserless &&
378
- shadowDOM &&
379
- prerender !== false
380
- ) {
388
+ if (mode === 'fetch' && getBrowserless && shadowDOM && prerender !== false) {
381
389
  debug('shadow DOM detected, retrying with prerender', { url: targetUrl })
382
390
  const prerenderPromise = getContent(targetUrl, 'prerender', {
383
391
  getBrowserless,
@@ -411,3 +419,4 @@ module.exports.PDF_SIZE_TRESHOLD = PDF_SIZE_TRESHOLD
411
419
  module.exports.isFetchMode = isFetchMode
412
420
  module.exports.getContent = getContent
413
421
  module.exports.defaultMutool = defaultMutool
422
+ module.exports.defaultPandoc = defaultPandoc