webforai 1.5.1 → 1.6.0

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 (73) hide show
  1. package/dist/bin.js +277 -334
  2. package/dist/cjs/cli/bin.js +3 -4
  3. package/dist/cjs/cli/commands/{handlers → webforai}/index.js +33 -31
  4. package/dist/cjs/cli/commands/webforai/loadHtml.js +93 -0
  5. package/dist/cjs/cli/utils.js +18 -51
  6. package/dist/cjs/extract-hast/index.js +4 -3
  7. package/dist/cjs/extract-hast/readability.js +28 -61
  8. package/dist/cjs/extract-hast/strongly.js +2 -1
  9. package/dist/cjs/extract-hast/utils.js +0 -15
  10. package/dist/cjs/html-to-mdast.js +6 -6
  11. package/dist/cjs/link-replacer.js +10 -5
  12. package/dist/cjs/loaders/playwright.js +36 -7
  13. package/dist/cjs/mdast-handlers/custom-br-handler.js +31 -0
  14. package/dist/cjs/mdast-to-markdown.js +1 -1
  15. package/dist/cjs/utils/hast-utils.js +71 -0
  16. package/dist/cli/bin.js +3 -4
  17. package/dist/cli/commands/webforai/index.js +53 -0
  18. package/dist/cli/commands/webforai/loadHtml.js +60 -0
  19. package/dist/cli/utils.js +18 -51
  20. package/dist/extract-hast/index.js +4 -3
  21. package/dist/extract-hast/readability.js +29 -62
  22. package/dist/extract-hast/strongly.js +2 -1
  23. package/dist/extract-hast/utils.js +0 -14
  24. package/dist/html-to-mdast.js +6 -6
  25. package/dist/link-replacer.js +10 -5
  26. package/dist/loaders/playwright.js +36 -7
  27. package/dist/mdast-handlers/custom-br-handler.js +8 -0
  28. package/dist/mdast-to-markdown.js +1 -1
  29. package/dist/types/cli/bin.js.map +1 -1
  30. package/dist/types/cli/commands/webforai/index.d.ts +6 -0
  31. package/dist/types/cli/commands/webforai/index.js.map +1 -0
  32. package/dist/types/cli/commands/webforai/loadHtml.d.ts +3 -0
  33. package/dist/types/cli/commands/webforai/loadHtml.js.map +1 -0
  34. package/dist/types/cli/utils.d.ts +27 -0
  35. package/dist/types/cli/utils.js.map +1 -1
  36. package/dist/types/extract-hast/index.d.ts +9 -4
  37. package/dist/types/extract-hast/index.js.map +1 -1
  38. package/dist/types/extract-hast/readability.d.ts +2 -1
  39. package/dist/types/extract-hast/readability.js.map +1 -1
  40. package/dist/types/extract-hast/strongly.d.ts +2 -1
  41. package/dist/types/extract-hast/strongly.js.map +1 -1
  42. package/dist/types/extract-hast/utils.d.ts +0 -1
  43. package/dist/types/extract-hast/utils.js.map +1 -1
  44. package/dist/types/html-to-mdast.d.ts +4 -0
  45. package/dist/types/html-to-mdast.js.map +1 -1
  46. package/dist/types/link-replacer.d.ts +1 -1
  47. package/dist/types/link-replacer.js.map +1 -1
  48. package/dist/types/loaders/playwright.d.ts +8 -2
  49. package/dist/types/loaders/playwright.js.map +1 -1
  50. package/dist/types/mdast-handlers/custom-br-handler.d.ts +2 -0
  51. package/dist/types/mdast-handlers/custom-br-handler.js.map +1 -0
  52. package/dist/types/mdast-to-markdown.js.map +1 -1
  53. package/dist/types/utils/hast-utils.d.ts +4 -0
  54. package/dist/types/utils/hast-utils.js.map +1 -0
  55. package/dist/utils/hast-utils.js +46 -0
  56. package/package.json +21 -10
  57. package/dist/cjs/cli/commands/handlers/loadHtml.js +0 -127
  58. package/dist/cjs/html-to-markdown.test.js +0 -118
  59. package/dist/cjs/link-replacer.test.js +0 -47
  60. package/dist/cjs/loaders/fetch.test.js +0 -9
  61. package/dist/cjs/loaders/playwright.test.js +0 -17
  62. package/dist/cjs/loaders/puppeteer.test.js +0 -17
  63. package/dist/cli/commands/handlers/index.js +0 -51
  64. package/dist/cli/commands/handlers/loadHtml.js +0 -94
  65. package/dist/html-to-markdown.test.js +0 -118
  66. package/dist/link-replacer.test.js +0 -47
  67. package/dist/loaders/fetch.test.js +0 -9
  68. package/dist/loaders/playwright.test.js +0 -17
  69. package/dist/loaders/puppeteer.test.js +0 -17
  70. package/dist/types/cli/commands/handlers/index.d.ts +0 -13
  71. package/dist/types/cli/commands/handlers/index.js.map +0 -1
  72. package/dist/types/cli/commands/handlers/loadHtml.d.ts +0 -2
  73. package/dist/types/cli/commands/handlers/loadHtml.js.map +0 -1
@@ -25,10 +25,9 @@ var import_prompts = require("@clack/prompts");
25
25
  var import_commander = require("commander");
26
26
  var import_picocolors = __toESM(require("picocolors"), 1);
27
27
  var import_package = __toESM(require("../../package.json"), 1);
28
- var import_handlers = require("./commands/handlers");
28
+ var import_webforai = require("./commands/webforai");
29
29
  var import_constants = require("./constants");
30
- import_commander.program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(import_package.default.version, "-v, --version", "output the current version").argument("[source]", "URL or path to process").argument("[outputPath]", "Path to output file or directory").option("--mode <mode>", `Processing mode (${import_constants.MODES.join(", ")})`).option("--loader <loader>", `Loader to use (${import_constants.LOADERS.join(", ")})`).option("--baseUrl <baseUrl>", "Base URL for relative links").option("-o, --stdout", "output to stdout").option("-d, --debug", "output extra debugging information").on("option:debug", () => {
31
- process.env.DEBUG = "true";
32
- }).action(import_handlers.webforaiCommand);
30
+ import_commander.program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(import_package.default.version, "-v, --version", "output the current version");
31
+ import_commander.program.argument("[source]", "URL or path to process").option("-o, --output <output>", "Path to output file or directory").option("-m, --mode <mode>", `Processing mode (${import_constants.MODES.join(", ")})`).option("-l, --loader <loader>", `Loader to use (${import_constants.LOADERS.join(", ")})`).option("-d, --debug", "output extra debugging information").action(import_webforai.webforaiCommand);
33
32
  (0, import_prompts.intro)(import_picocolors.default.inverse(`webforai CLI version ${import_package.default.version}`));
34
33
  import_commander.program.parse();
@@ -26,14 +26,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
- var handlers_exports = {};
30
- __export(handlers_exports, {
29
+ var webforai_exports = {};
30
+ __export(webforai_exports, {
31
31
  webforaiCommand: () => webforaiCommand
32
32
  });
33
- module.exports = __toCommonJS(handlers_exports);
34
- var import_node_fs = __toESM(require("node:fs"), 1);
33
+ module.exports = __toCommonJS(webforai_exports);
34
+ var import_promises = __toESM(require("node:fs/promises"), 1);
35
35
  var import_node_path = __toESM(require("node:path"), 1);
36
36
  var import_prompts = require("@clack/prompts");
37
+ var import_picocolors = __toESM(require("picocolors"), 1);
38
+ var import_package = __toESM(require("../../../../package.json"), 1);
37
39
  var import_html_to_markdown = require("../../../html-to-markdown");
38
40
  var import_inputOutputPath = require("../../helpers/inputOutputPath");
39
41
  var import_inputSourcePath = require("../../helpers/inputSourcePath");
@@ -43,40 +45,40 @@ var import_utils = require("../../utils");
43
45
  var import_loadHtml = require("./loadHtml");
44
46
  const aiModeOptions = { linkAsText: true, tableAsText: true, hideImage: true };
45
47
  const readabilityModeOptions = { linkAsText: false, tableAsText: false, hideImage: false };
46
- const webforaiCommand = async (initialPath, initialOutputPath, options) => {
48
+ const webforaiCommand = async (initialPath, options) => {
49
+ (0, import_prompts.intro)(import_picocolors.default.bold(import_picocolors.default.green(`webforai CLI version ${import_package.default.version}`)));
47
50
  const sourcePath = initialPath ?? await (0, import_inputSourcePath.inputSourcePath)();
48
- if (options.debug) {
49
- console.debug(`sourcePath: ${sourcePath}`);
50
- }
51
- const loader = (0, import_utils.isUrl)(sourcePath) ? { type: "web", loader: options.loader ?? await (0, import_selectLoader.selectLoader)() } : { type: "local", loader: null };
52
- if (options.debug) {
53
- console.debug(`loader: ${loader}`);
54
- }
55
- const outputPath = initialOutputPath ?? await (0, import_inputOutputPath.inputOutputPath)(sourcePath);
56
- if (options.debug) {
57
- console.debug(`outputPath: ${outputPath}`);
58
- }
51
+ options.debug && console.debug(`sourcePath: ${sourcePath}`);
52
+ const loader = (0, import_utils.isUrl)(sourcePath) ? options.loader ?? await (0, import_selectLoader.selectLoader)() : "local";
53
+ options.debug && console.debug(`loader: ${loader}`);
54
+ const outputPath = options.output ?? await (0, import_inputOutputPath.inputOutputPath)(sourcePath);
55
+ options.debug && console.debug(`outputPath: ${outputPath}`);
59
56
  const mode = options.mode ?? await (0, import_selectExtractMode.selectExtractMode)();
60
- if (options.debug) {
61
- console.debug(`mode: ${mode}`);
57
+ options.debug && console.debug(`mode: ${mode}`);
58
+ let html;
59
+ const s = (0, import_prompts.spinner)();
60
+ try {
61
+ s.start("Loading content...");
62
+ html = await (0, import_loadHtml.loadHtml)(sourcePath, loader, { debug: options.debug });
63
+ s.stop(import_picocolors.default.green("Content loaded!"));
64
+ } catch (error) {
65
+ s.stop(import_picocolors.default.red("Content loading failed!"));
66
+ console.error(error);
67
+ process.exit(1);
62
68
  }
63
- const content = await (0, import_loadHtml.loadHtml)(sourcePath, loader);
64
- if (options.debug) {
65
- console.debug(`content: ${content}`);
66
- }
67
- const markdown = (0, import_html_to_markdown.htmlToMarkdown)(content, {
68
- baseUrl: (0, import_utils.isUrl)(sourcePath) ? sourcePath : options.baseUrl,
69
+ options.debug && console.debug(`html: ${html}`);
70
+ const markdown = (0, import_html_to_markdown.htmlToMarkdown)(html, {
71
+ baseUrl: (0, import_utils.isUrl)(sourcePath) ? sourcePath : void 0,
69
72
  ...mode === "ai" ? aiModeOptions : readabilityModeOptions
70
73
  });
71
- if (options.debug) {
72
- console.debug(`markdown: ${markdown}`);
73
- }
74
+ options.debug && console.debug(`markdown: ${markdown}`);
74
75
  const directory = import_node_path.default.dirname(outputPath);
75
- if (!import_node_fs.default.existsSync(directory)) {
76
- import_node_fs.default.mkdirSync(directory, { recursive: true });
76
+ const isDirectoryExists = await import_promises.default.stat(directory).then((stat) => stat.isDirectory());
77
+ if (!isDirectoryExists) {
78
+ await import_promises.default.mkdir(directory, { recursive: true });
77
79
  }
78
- import_node_fs.default.writeFileSync(outputPath, markdown);
79
- (0, import_prompts.outro)("Markdown conversion is complete!");
80
+ await import_promises.default.writeFile(outputPath, markdown);
81
+ (0, import_prompts.outro)(import_picocolors.default.green(`${import_picocolors.default.bold("Done!")} Markdown saved to ${outputPath}`));
80
82
  };
81
83
  // Annotate the CommonJS export names for ESM import in node:
82
84
  0 && (module.exports = {
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var loadHtml_exports = {};
30
+ __export(loadHtml_exports, {
31
+ loadHtml: () => loadHtml
32
+ });
33
+ module.exports = __toCommonJS(loadHtml_exports);
34
+ var import_promises = __toESM(require("node:fs/promises"), 1);
35
+ var import_picocolors = __toESM(require("picocolors"), 1);
36
+ var import_playwright_core = require("playwright-core");
37
+ var import_package = __toESM(require("playwright-core/package.json"), 1);
38
+ var import_fetch = require("../../../loaders/fetch");
39
+ var import_playwright = require("../../../loaders/playwright");
40
+ const checkPlaywrightAvailable = async () => {
41
+ const path = import_playwright_core.chromium.executablePath();
42
+ const isAvailable = await import_promises.default.access(path).catch(() => false);
43
+ return !!isAvailable;
44
+ };
45
+ const loadHtml = async (sourcePath, loader, options) => {
46
+ if (loader === "local") {
47
+ options.debug && console.debug(`Loading HTML from local file: ${sourcePath}`);
48
+ const content = await import_promises.default.readFile(sourcePath, "utf-8");
49
+ options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
50
+ return content;
51
+ }
52
+ if (loader === "fetch") {
53
+ options.debug && console.debug(`Loading HTML from URL: ${sourcePath}`);
54
+ const content = await (0, import_fetch.loadHtml)(sourcePath);
55
+ options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
56
+ return content;
57
+ }
58
+ if (loader === "playwright") {
59
+ options.debug && console.debug(`Loading HTML from playwright: ${sourcePath}`);
60
+ const isPlaywrightAvailable = await checkPlaywrightAvailable();
61
+ options.debug && console.debug(`Playwright available: ${isPlaywrightAvailable}`);
62
+ if (!isPlaywrightAvailable) {
63
+ const message = [
64
+ "To use playwright loader, you need to install playwright.",
65
+ "You can install it by running:",
66
+ "",
67
+ ` ${import_picocolors.default.bold(`npx playwright@${import_package.default.version} install chromium`)} `,
68
+ "",
69
+ "Hint1: If you get warning like this:",
70
+ "WARNING: It looks like you are running 'npx playwright install' without first installing your project's dependencies. ",
71
+ `${import_picocolors.default.bold("Ignore this warning.")}`,
72
+ "",
73
+ "Hint2: If you get message like this:",
74
+ "Host system is missing dependencies to run browsers.",
75
+ "You should install dependencies by running:",
76
+ "",
77
+ ` ${import_picocolors.default.bold(`sudo npx playwright@${import_package.default.version} install-deps`)} `,
78
+ "",
79
+ import_picocolors.default.gray("Note: Good luck with that.")
80
+ ];
81
+ console.info(message.join("\n"));
82
+ throw new Error("Playwright is not available");
83
+ }
84
+ const content = await (0, import_playwright.loadHtml)(sourcePath);
85
+ options.debug && console.debug(`HTML loaded: ${content.slice(0, 100)}`);
86
+ return content;
87
+ }
88
+ throw new Error(`Unsupported loader: ${loader}`);
89
+ };
90
+ // Annotate the CommonJS export names for ESM import in node:
91
+ 0 && (module.exports = {
92
+ loadHtml
93
+ });
@@ -46,71 +46,38 @@ const isUrl = (maybeUrl) => {
46
46
  }
47
47
  };
48
48
  function changeFileExtension(filePath, newExtension) {
49
- const parsedPath = filePath.split("/");
50
- const fileName = parsedPath[parsedPath.length - 1];
51
- const formattedNewExtension = newExtension.startsWith(".") ? newExtension : `.${newExtension}`;
52
- if (fileName.startsWith(".")) {
53
- const parts = fileName.split(".");
54
- if (parts.length === 2) {
55
- return parsedPath.slice(0, -1).concat(`${fileName}${formattedNewExtension}`).join("/");
56
- }
57
- parts[parts.length - 1] = newExtension.replace(/^\./, "");
58
- return parsedPath.slice(0, -1).concat(parts.join(".")).join("/");
59
- }
60
- const lastDotIndex = fileName.lastIndexOf(".");
61
- const baseName = lastDotIndex !== -1 ? fileName.slice(0, lastDotIndex) : fileName;
62
- const newFileName = `${baseName}${formattedNewExtension}`;
63
- parsedPath[parsedPath.length - 1] = newFileName;
64
- return parsedPath.join("/");
49
+ const parsed = import_node_path.default.parse(filePath);
50
+ const ext = newExtension.startsWith(".") ? newExtension : `.${newExtension}`;
51
+ return import_node_path.default.format({ ...parsed, ext });
65
52
  }
66
53
  function urlToFilename(url) {
67
54
  try {
68
55
  const urlObj = new URL(url);
69
- const domainParts = urlObj.hostname.split(".").reverse().reduce((acc, part, index) => {
70
- if (index === 0) {
71
- return acc;
72
- }
73
- if (acc.length >= 2) {
74
- return acc;
75
- }
76
- if (part === "www") {
77
- return acc;
78
- }
79
- return [part, ...acc];
80
- }, []);
81
- const domainString = domainParts.reverse().join("-");
56
+ const hostname = urlObj.hostname.startsWith("www.") ? urlObj.hostname.slice(4) : urlObj.hostname;
57
+ const domainParts = hostname.split(".").slice(-2).join("-");
82
58
  const pathParts = urlObj.pathname.split("/").filter(Boolean);
83
- const relevantPathParts = pathParts.slice(-2);
84
- const pathString = relevantPathParts.map((part) => decodeURIComponent(part)).join("-");
85
- let filename = [domainString, pathString].filter(Boolean).join("-");
59
+ const relevantPathParts = pathParts.slice(-2).map(decodeURIComponent).join("-");
60
+ let filename = [domainParts, relevantPathParts].filter(Boolean).join("-");
86
61
  filename = filename.toLowerCase().replace(/[<>:"/\\|?*\x00-\x1F]/g, "").replace(/[\s.]+/g, "-").replace(/^-+|-+$/g, "");
87
62
  return filename || "output";
88
63
  } catch {
89
64
  return "output";
90
65
  }
91
66
  }
92
- const sourcePathToOutputPath = (sourcePath) => {
93
- return isUrl(sourcePath) ? `${urlToFilename(sourcePath)}.md` : changeFileExtension(sourcePath, "md");
94
- };
67
+ const sourcePathToOutputPath = (sourcePath) => isUrl(sourcePath) ? `${urlToFilename(sourcePath)}.md` : changeFileExtension(sourcePath, "md");
95
68
  function getNextAvailableFilePath(filePath) {
96
- const parsedPath = import_node_path.default.parse(filePath);
97
- const directory = parsedPath.dir;
98
- const fullName = parsedPath.base;
99
- const [firstPart, ...restParts] = fullName.split(".");
100
- const restName = restParts.length > 0 ? `.${restParts.join(".")}` : "";
101
- const baseName = firstPart.replace(/_\d+$/, "");
102
- let counter = 1;
103
- let nextFilePath = filePath;
104
- while (import_node_fs.default.existsSync(nextFilePath)) {
105
- const match = firstPart.match(/_(\d+)$/);
106
- if (match) {
107
- counter = Number.parseInt(match[1], 10) + 1;
108
- }
109
- const newName = `${baseName}_${counter}${restName}`;
110
- nextFilePath = import_node_path.default.join(directory, newName);
69
+ const parsed = import_node_path.default.parse(filePath);
70
+ const dir = parsed.dir;
71
+ const ext = parsed.ext;
72
+ const baseName = parsed.name.replace(/_\d+$/, "");
73
+ const match = parsed.name.match(/_(\d+)$/);
74
+ let counter = match ? Number(match[1]) + 1 : 1;
75
+ let newPath = import_node_path.default.join(dir, `${baseName}_${counter}${ext}`);
76
+ while (import_node_fs.default.existsSync(newPath)) {
111
77
  counter++;
78
+ newPath = import_node_path.default.join(dir, `${baseName}_${counter}${ext}`);
112
79
  }
113
- return nextFilePath;
80
+ return newPath;
114
81
  }
115
82
  // Annotate the CommonJS export names for ESM import in node:
116
83
  0 && (module.exports = {
@@ -30,17 +30,18 @@ const PRESET_EXTRACT_HAST = {
30
30
  readability: import_readability.readabilityExtractHast
31
31
  };
32
32
  const DEFAULT_EXTRACT_HAST = ["readability"];
33
- const extractHast = (hast, extractors = DEFAULT_EXTRACT_HAST) => {
33
+ const extractHast = (params, extractors = DEFAULT_EXTRACT_HAST) => {
34
+ const { hast, lang } = params;
34
35
  const _extractors = Array.isArray(extractors) ? extractors : [extractors];
35
36
  const extracted = _extractors.reduce((acc, extractor) => {
36
37
  if (extractor === false) {
37
38
  return acc;
38
39
  }
39
40
  if (typeof extractor === "string" && extractor in PRESET_EXTRACT_HAST) {
40
- return PRESET_EXTRACT_HAST[extractor](acc);
41
+ return PRESET_EXTRACT_HAST[extractor]({ hast: acc, lang });
41
42
  }
42
43
  if (typeof extractor === "function") {
43
- return extractor(acc);
44
+ return extractor({ hast: acc, lang });
44
45
  }
45
46
  throw new Error(`Invalid extractor: ${extractor}`);
46
47
  }, hast) || hast;
@@ -30,35 +30,24 @@ const REGEXPS = {
30
30
  hidden: /hidden|invisible|fallback-image/i,
31
31
  byline: /byline|author|dateline|writtenby|p-author/i,
32
32
  specialUnlikelyCandidates: /frb-|uls-menu|language-link/i,
33
- unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore/i,
33
+ unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|tooltip|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore|avatar/i,
34
34
  okMaybeItsaCandidate: /and|article|body|column|content|main|shadow|code/i
35
35
  };
36
- const BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry", "main"];
37
- const PARAGRAPH_TAGS = ["p", "div", "section", "article", "main", "ul", "ol", "li"];
36
+ const BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry"];
37
+ const PARAGRAPH_TAGS = ["a", "p", "div", "section", "article", "main", "ul", "ol", "li"];
38
38
  const BASE_MINIMAL_LENGTH = {
39
39
  ja: 200,
40
40
  en: 500
41
41
  };
42
- const tryGetLang = (node) => {
43
- if (node.type !== "element") {
44
- return;
45
- }
46
- const element = node;
47
- if (element.tagName !== "html") {
48
- return;
49
- }
50
- const langAttr = element.properties.lang || element.properties["xml:lang"];
51
- if (langAttr) {
52
- return langAttr;
53
- }
42
+ const metadataFilter = (node) => {
43
+ return !(["comment", "doctype"].includes(node.type) || node.type === "element" && ["script", "style", "link", "meta", "noscript", "svg", "title"].includes(node.tagName));
54
44
  };
55
- const metadataFilter = (node) => !["comment", "doctype"].includes(node.type);
56
45
  const universalElementFilter = (node) => {
57
46
  if (node.type !== "element") {
58
47
  return true;
59
48
  }
60
49
  const element = node;
61
- if (["script", "aside"].includes(element.tagName)) {
50
+ if (["aside", "nav"].includes(element.tagName)) {
62
51
  return false;
63
52
  }
64
53
  if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
@@ -89,10 +78,7 @@ const unlikelyElementFilter = (node) => {
89
78
  return true;
90
79
  }
91
80
  const element = node;
92
- if (["body", "article", "main", "section", "a"].includes(element.tagName)) {
93
- return true;
94
- }
95
- if ((0, import_utils.hasAncestors)(element, ["table", "code"], 3)) {
81
+ if (["body", "article", "main", "section", "h1", "h2", "h3", "h4", "h5", "h6", "p"].includes(element.tagName)) {
96
82
  return true;
97
83
  }
98
84
  const match = (0, import_utils.matchString)(element);
@@ -104,55 +90,36 @@ const unlikelyElementFilter = (node) => {
104
90
  }
105
91
  return true;
106
92
  };
107
- const isImageLink = (element) => {
108
- const a = (0, import_hast_util_select.select)("a", element);
109
- const img = (0, import_hast_util_select.select)("img", a);
110
- if (!(a && img)) {
111
- return false;
112
- }
113
- const imgFilename = img.properties.src?.toString().split("/").pop();
114
- const hrefFilename = a.properties.href?.toString().split("/").pop();
115
- return imgFilename === hrefFilename && imgFilename;
116
- };
117
- const removeEmptyFilter = (node) => {
93
+ const removeEmptyFilter = (node, _lang) => {
118
94
  if (node.type !== "element") {
119
95
  return true;
120
96
  }
121
97
  const element = node;
122
- if (!PARAGRAPH_TAGS.includes(element.tagName)) {
98
+ if (PARAGRAPH_TAGS.includes(element.tagName)) {
123
99
  return true;
124
100
  }
125
- if (isImageLink(element)) {
126
- return true;
127
- }
128
- const text = (0, import_hast_util_to_string.toString)(element);
129
- if (text.length < 10) {
101
+ if (element.tagName === "img" && !element.properties.src) {
130
102
  return false;
131
103
  }
132
104
  return true;
133
105
  };
134
- const readabilityExtractHast = (hast) => {
135
- const lang = String((0, import_hast_util_select.select)("html", hast)?.properties.lang || tryGetLang(hast) || "en");
106
+ const readabilityExtractHast = (params) => {
107
+ const { hast, lang = "en" } = params;
136
108
  const body = (0, import_hast_util_select.select)("body", hast) ?? hast;
137
- const bodyText = (0, import_hast_util_to_string.toString)(body);
138
- const baseFilterd = (0, import_unist_util_filter.filter)(body, (node) => {
139
- if (!metadataFilter(node)) {
140
- return false;
141
- }
142
- if (!universalElementFilter(node)) {
143
- return false;
144
- }
145
- return true;
146
- });
147
- if (!baseFilterd) {
148
- return { type: "root", children: [] };
149
- }
150
- const baseFilterdText = (0, import_hast_util_to_string.toString)(baseFilterd);
151
- let [baseTree, baseText] = baseFilterdText.length > bodyText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [body, bodyText];
109
+ const metadataFilteredHast = (0, import_unist_util_filter.filter)(body, (node) => metadataFilter(node));
110
+ const metadataFilteredHastText = metadataFilteredHast && (0, import_hast_util_to_string.toString)(metadataFilteredHast);
111
+ if (!(metadataFilteredHast && metadataFilteredHastText)) {
112
+ return body;
113
+ }
114
+ const baseFilterd = (0, import_unist_util_filter.filter)(metadataFilteredHast, (node) => universalElementFilter(node));
115
+ const baseFilterdText = baseFilterd ? (0, import_hast_util_to_string.toString)(baseFilterd) : "";
116
+ const [baseTree, baseText] = baseFilterdText.length > metadataFilteredHastText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [metadataFilteredHast, metadataFilteredHastText];
152
117
  let minimalLength = lang in BASE_MINIMAL_LENGTH ? BASE_MINIMAL_LENGTH[lang] : 500;
153
118
  if (baseText.length < minimalLength) {
154
119
  minimalLength = Math.max(0, baseText.length - 200);
155
120
  }
121
+ let extractedTree = baseTree;
122
+ let extractedText = baseText;
156
123
  for (const selector of BODY_SELECTORS) {
157
124
  const content = { type: "root", children: (0, import_hast_util_select.selectAll)(selector, baseFilterd) };
158
125
  const contentText = (0, import_hast_util_to_string.toString)(content);
@@ -161,18 +128,18 @@ const readabilityExtractHast = (hast) => {
161
128
  }
162
129
  const links = (0, import_hast_util_select.selectAll)("a", content);
163
130
  const linkText = links.map((link) => (0, import_hast_util_to_string.toString)(link)).join("");
164
- const linkDensity = linkText.length / bodyText.length;
131
+ const linkDensity = linkText.length / contentText.length;
165
132
  if (linkDensity > 0.4) {
166
133
  continue;
167
134
  }
168
135
  if (contentText.length > minimalLength) {
169
- baseTree = content;
170
- baseText = contentText;
136
+ extractedTree = content;
137
+ extractedText = contentText;
171
138
  break;
172
139
  }
173
140
  }
174
- const finalFilteredTree = (0, import_unist_util_filter.filter)(baseTree, (node) => {
175
- if (!removeEmptyFilter(node)) {
141
+ const finalFilteredTree = (0, import_unist_util_filter.filter)(extractedTree, (node) => {
142
+ if (!removeEmptyFilter(node, lang)) {
176
143
  return false;
177
144
  }
178
145
  if (!unlikelyElementFilter(node)) {
@@ -180,7 +147,7 @@ const readabilityExtractHast = (hast) => {
180
147
  }
181
148
  return true;
182
149
  });
183
- const finalTree = (0, import_hast_util_to_string.toString)(finalFilteredTree).length > baseText.length / 3 ? finalFilteredTree : baseTree;
150
+ const finalTree = (0, import_hast_util_to_string.toString)(finalFilteredTree).length > extractedText.length / 3 ? finalFilteredTree : extractedTree;
184
151
  return finalTree;
185
152
  };
186
153
  // Annotate the CommonJS export names for ESM import in node:
@@ -24,7 +24,8 @@ module.exports = __toCommonJS(strongly_exports);
24
24
  var import_hast_util_select = require("hast-util-select");
25
25
  var import_unist_util_filter = require("unist-util-filter");
26
26
  const REMOVE_REGEX = /fallback|icon|header|nav|speechify-ignore|assist|-ad-|banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|assist|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i;
27
- const stronglyExtractHast = (hast) => {
27
+ const stronglyExtractHast = (params) => {
28
+ const { hast } = params;
28
29
  const selectors = ["article", "#article", "#content", "main", "div.container"];
29
30
  let selectedElement = hast;
30
31
  for (const selector of selectors) {
@@ -19,7 +19,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var utils_exports = {};
20
20
  __export(utils_exports, {
21
21
  classnames: () => classnames,
22
- hasAncestors: () => hasAncestors,
23
22
  isStrInclude: () => isStrInclude,
24
23
  matchString: () => matchString
25
24
  });
@@ -37,23 +36,9 @@ const isStrInclude = (value, match) => {
37
36
  }
38
37
  return false;
39
38
  };
40
- const hasAncestors = (element, tagNames, depth = 3) => {
41
- let parent = element.parent;
42
- for (let i = 0; i < depth; i++) {
43
- if (!parent || parent.type !== "element") {
44
- return false;
45
- }
46
- if (tagNames.includes(parent.tagName)) {
47
- return true;
48
- }
49
- parent = parent.parent;
50
- }
51
- return false;
52
- };
53
39
  // Annotate the CommonJS export names for ESM import in node:
54
40
  0 && (module.exports = {
55
41
  classnames,
56
- hasAncestors,
57
42
  isStrInclude,
58
43
  matchString
59
44
  });
@@ -30,12 +30,13 @@ var import_custom_code_handler = require("./mdast-handlers/custom-code-handler")
30
30
  var import_custom_div_handler = require("./mdast-handlers/custom-div-handler");
31
31
  var import_custom_img_handler = require("./mdast-handlers/custom-img-handler");
32
32
  var import_custom_table_handler = require("./mdast-handlers/custom-table-handler");
33
- var import_empty_handler = require("./mdast-handlers/empty-handler");
34
33
  var import_math_handler = require("./mdast-handlers/math-handler");
34
+ var import_hast_utils = require("./utils/hast-utils");
35
35
  const htmlToMdast = (htmlOrHast, options) => {
36
- const { extractors } = options || {};
37
- const hast = typeof htmlOrHast === "string" ? (0, import_hast_util_from_html.fromHtml)(htmlOrHast, { fragment: true }) : htmlOrHast;
38
- const extractedHast = (0, import_extract_hast.extractHast)(hast, extractors);
36
+ const { extractors, url: defaultUrl, lang: defaultLang } = options || {};
37
+ const [lang, hast] = typeof htmlOrHast === "string" ? [defaultLang || (0, import_hast_utils.getLangFromStr)(htmlOrHast), (0, import_hast_util_from_html.fromHtml)(htmlOrHast, { fragment: true })] : [defaultLang || (0, import_hast_utils.getLangFromHast)(htmlOrHast), htmlOrHast];
38
+ const url = defaultUrl || (0, import_hast_utils.getUrlFromHast)(hast);
39
+ const extractedHast = (0, import_extract_hast.extractHast)({ hast, lang, url }, extractors);
39
40
  const mdast = (0, import_hast_util_to_mdast.toMdast)(extractedHast, {
40
41
  handlers: {
41
42
  math: import_math_handler.mathHandler,
@@ -43,8 +44,7 @@ const htmlToMdast = (htmlOrHast, options) => {
43
44
  pre: import_custom_code_handler.customCodeHandler,
44
45
  a: (0, import_custom_a_handler.customAHandler)({ asText: options?.linkAsText }),
45
46
  img: (0, import_custom_img_handler.customImgHandler)({ hideImage: options?.hideImage }),
46
- table: (0, import_custom_table_handler.customTableHandler)({ asText: options?.tableAsText }),
47
- br: import_empty_handler.emptyHandler
47
+ table: (0, import_custom_table_handler.customTableHandler)({ asText: options?.tableAsText })
48
48
  }
49
49
  });
50
50
  const extractedMdast = (0, import_extract_mdast.extractMdast)(mdast);
@@ -21,13 +21,18 @@ __export(link_replacer_exports, {
21
21
  linkReplacer: () => linkReplacer
22
22
  });
23
23
  module.exports = __toCommonJS(link_replacer_exports);
24
- const linkReplacer = (markdown, link) => {
25
- const regex = /(!?\[.*?\]\()(.+?)(\))/g;
26
- return markdown.replace(regex, (match, pre, thisUrl, post) => {
27
- if (thisUrl.startsWith("http") || thisUrl.startsWith("#")) {
24
+ const linkReplacer = (markdown, base) => {
25
+ const regex = /(!?\[.*?\]\()([^)\s]+)(\))/g;
26
+ return markdown.replace(regex, (match, pre, url, post) => {
27
+ if (/^(https?:|#)/.test(url)) {
28
+ return match;
29
+ }
30
+ try {
31
+ const absoluteUrl = new URL(url, base).href;
32
+ return `${pre}${absoluteUrl}${post}`;
33
+ } catch {
28
34
  return match;
29
35
  }
30
- return `${pre}${new URL(thisUrl, link).href}${post}`;
31
36
  });
32
37
  };
33
38
  // Annotate the CommonJS export names for ESM import in node:
@@ -21,15 +21,44 @@ __export(playwright_exports, {
21
21
  loadHtml: () => loadHtml
22
22
  });
23
23
  module.exports = __toCommonJS(playwright_exports);
24
- var import_playwright = require("playwright");
25
- const loadHtml = async (url, context) => {
26
- const _context = context ?? await import_playwright.chromium.launch({ headless: true });
27
- const page = await _context.newPage();
28
- await page.goto(url, { waitUntil: "load" });
24
+ var import_playwright_core = require("playwright-core");
25
+ const SUPER_BYPASS_DEVICE = {
26
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
27
+ viewport: { width: 1920, height: 1080 },
28
+ deviceScaleFactor: 1,
29
+ hasTouch: false,
30
+ isMobile: false,
31
+ javaScriptEnabled: true,
32
+ locale: "en-US",
33
+ timezoneId: "America/New_York"
34
+ };
35
+ const loadHtml = async (url, options) => {
36
+ const { browser, waitUntil, timeout, superBypassMode } = options ?? {};
37
+ const _browser = browser ?? await import_playwright_core.chromium.launch({ headless: true });
38
+ const context = await _browser.newContext(superBypassMode ? SUPER_BYPASS_DEVICE : import_playwright_core.devices["Desktop Chrome"]);
39
+ if (superBypassMode) {
40
+ await context.addInitScript(() => {
41
+ Object.defineProperty(navigator, "webdriver", { get: () => void 0 });
42
+ Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
43
+ Object.defineProperty(navigator, "plugins", { get: () => [1, 2, 3, 4, 5] });
44
+ });
45
+ }
46
+ const page = await context.newPage();
47
+ if (superBypassMode) {
48
+ await page.route("**/*.js", (route) => {
49
+ if (route.request().url().includes("captcha-delivery")) {
50
+ return route.abort();
51
+ }
52
+ return route.continue();
53
+ });
54
+ }
55
+ await page.goto(url, { waitUntil: waitUntil ?? "load", timeout });
29
56
  const html = await page.content();
30
57
  await page.close();
31
- if (!context) {
32
- await _context.close();
58
+ if (browser) {
59
+ await context.close();
60
+ } else {
61
+ await _browser.close();
33
62
  }
34
63
  return html;
35
64
  };