@waynehartman/recipe-scraper
Version:
Extract recipe data from the web effortlessly
477 lines (468 loc) • 25.7 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// src/cli.ts
var import_commander = require("commander");
// package.json
var package_default = {
name: "@waynehartman/recipe-scraper",
version: "0.0.9",
description: "Extract recipe data from the web effortlessly",
publishConfig: {
access: "public"
},
license: "MIT",
homepage: "https://github.com/WayneHartman-com/recipe-scraper",
repository: {
type: "git",
url: "git+https://github.com/WayneHartman-com/recipe-scraper.git"
},
bugs: {
url: "https://github.com/WayneHartman-com/recipe-scraper/issues"
},
keywords: [
"recipe",
"food-recipe",
"scraper",
"json-ld",
"jsonld",
"micro-data",
"microdata"
],
exports: {
require: "./dist/index.js",
import: "./dist/index.mjs"
},
main: "./dist/index.js",
module: "./dist/index.mjs",
types: "./dist/index.d.ts",
bin: "./dist/cli.js",
files: [
"dist"
],
scripts: {
"build-fast": "tsup src/index.ts --format cjs,esm && tsup src/cli.ts",
prepare: "pnpm run build-fast --dts-resolve",
build: "pnpm run build-fast --dts-resolve",
prepublishOnly: "pnpm run build",
test: "jest"
},
devDependencies: {
"@antfu/eslint-config": "^0.39.8",
"@types/jest": "^29.5.14",
"@types/node": "^20.17.14",
eslint: "^8.57.1",
jest: "^29.7.0",
"lint-staged": "^13.3.0",
"simple-git-hooks": "^2.11.1",
"ts-jest": "^29.2.5",
tsup: "7.0.0",
typescript: "5.1.3"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
},
dependencies: {
axios: "^1.7.9",
cheerio: "1.0.0-rc.12",
commander: "^11.1.0",
"iso8601-duration": "^2.1.2",
jsonschema: "^1.5.0",
"microdata-node": "^2.0.0",
"schema-dts": "^1.1.2"
}
};
// src/index.ts
var cheerio = __toESM(require("cheerio"));
var import_axios = __toESM(require("axios"));
var import_jsonschema = require("jsonschema");
var import_microdata_node = __toESM(require("microdata-node"));
// src/schema.json
var schema_default = {
$schema: "http://json-schema.org/draft-07/schema#",
description: "A recipe scraped from the web",
type: "object",
required: ["name", "recipeIngredients"],
properties: {
name: {
type: "string",
minLength: 1
},
recipeIngredients: {
type: "array",
minItems: 1,
items: { type: "string" }
}
}
};
// src/utils.ts
var MATCH_HTML_TAGS = /<[^>]*>?/gm;
var MATCH_LINE_BREAK = /(\r\n|\n|\r)/gm;
var MATCH_MULTI_SPACE = / |\s\s+/gm;
function isValidHttpUrl(string) {
let url;
try {
url = new URL(string);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
// src/propertyTransforrmer.ts
var import_iso8601_duration = require("iso8601-duration");
var htmlCodes = { "	": " ", " ": "\n", " ": " ", "!": "!", "#": "#", "$": "$", "%": "%", "&": "&", "'": "'", "(": "(", ")": ")", "*": "*", "+": "+", ",": ",", "-": "-", ".": ".", "/": "/", ":": ":", ";": ";", "<": "<", "=": "=", ">": ">", "?": "?", "@": "@", "[": "[", "\": "\\", "]": "]", "^": "^", "_": "_", "`": "`", "{": "{", "|": "|", "}": "}", "~": "~", "€": "\u20AC", "‚": "\u201A", "ƒ": "\u0192", "„": "\u201E", "…": "\u2026", "†": "\u2020", "‡": "\u2021", "ˆ": "\u02C6", "‰": "\u2030", "Š": "\u0160", "‹": "\u2039", "Œ": "\u0152", "Ž": "\u017D", "‘": "\u2018", "’": "\u2019", "“": "\u201C", "”": "\u201D", "•": "\u2022", "–": "\u2013", "—": "\u2014", "˜": "\u02DC", "™": "\u2122", "š": "\u0161", "›": "\u203A", "œ": "\u0153", "ž": "\u017E", "Ÿ": "\u0178", " ": " ", "¡": "\xA1", "¢": "\xA2", "£": "\xA3", "¤": "\xA4", "¥": "\xA5", "¦": "\xA6", "§": "\xA7", "¨": "\xA8", "©": "\xA9", "ª": "\xAA", "«": "\xAB", "¬": "\xAC", "­": "-", "®": "\xAE", "¯": "\xAF", "°": "\xB0", "±": "\xB1", "²": "\xB2", "³": "\xB3", "´": "\xB4", "µ": "\xB5", "¶": "\xB6", "·": "\xB7", "¸": "\xB8", "¹": "\xB9", "º": "\xBA", "»": "\xBB", "¼": "\xBC", "½": "\xBD", "¾": "\xBE", "¿": "\xBF", "À": "\xC0", "Á": "\xC1", "Â": "\xC2", "Ã": "\xC3", "Ä": "\xC4", "Å": "\xC5", "Æ": "\xC6", "Ç": "\xC7", "È": "\xC8", "É": "\xC9", "Ê": "\xCA", "Ë": "\xCB", "Ì": "\xCC", "Í": "\xCD", "Î": "\xCE", "Ï": "\xCF", "Ð": "\xD0", "Ñ": "\xD1", "Ò": "\xD2", "Ó": "\xD3", "Ô": "\xD4", "Õ": "\xD5", "Ö": "\xD6", "×": "\xD7", "Ø": "\xD8", "Ù": "\xD9", "Ú": "\xDA", "Û": "\xDB", "Ü": "\xDC", "Ý": "\xDD", "Þ": "\xDE", "ß": "\xDF", "à": "\xE0", "á": "\xE1", "â": "\xE2", "ã": "\xE3", "ä": "\xE4", "å": "\xE5", "æ": "\xE6", "ç": "\xE7", "è": "\xE8", "é": "\xE9", "ê": "\xEA", "ë": "\xEB", "ì": "\xEC", "í": "\xED", "î": "\xEE", "ï": "\xEF", "ð": "\xF0", "ñ": "\xF1", "ò": "\xF2", "ó": "\xF3", "ô": "\xF4", "õ": "\xF5", "ö": "\xF6", "÷": "\xF7", "ø": "\xF8", "ù": "\xF9", "ú": "\xFA", "û": "\xFB", "ü": "\xFC", "ý": "\xFD", "þ": "\xFE", "ÿ": "\xFF", "€": "\u20AC", "™": "\u2122", "←": "\u2190", "↑": "\u2191", "→": "\u2192", "↓": "\u2193", "↔": "\u2194", "↵": "\u21B5", "∀": "\u2200", "∂": "\u2202", "∃": "\u2203", "∅": "\u2205", "∇": "\u2207", "∈": "\u2208", "∉": "\u2209", "∋": "\u220B", "∏": "\u220F", "∑": "\u2211", "−": "\u2212", "∗": "\u2217", "√": "\u221A", "∝": "\u221D", "∞": "\u221E", "∠": "\u2220", "∧": "\u2227", "∨": "\u2228", "∩": "\u2229", "∪": "\u222A", "∫": "\u222B", "∴": "\u2234", "∼": "\u223C", "≅": "\u2245", "≈": "\u2248", "≠": "\u2260", "≡": "\u2261", "≤": "\u2264", "≥": "\u2265", "⊂": "\u2282", "⊃": "\u2283", "⊄": "\u2284", "⊆": "\u2286", "⊇": "\u2287", "⊕": "\u2295", "⊗": "\u2297", "⊥": "\u22A5", "⋅": "\u22C5", "⌈": "\u2308", "⌉": "\u2309", "⌊": "\u230A", "⌋": "\u230B", "◊": "\u25CA", "♠": "\u2660", "♣": "\u2663", "♥": "\u2665", "♦": "\u2666", "Á": "\xC1", "á": "\xE1", "Ă": "\u0102", "ă": "\u0103", "Â": "\xC2", "â": "\xE2", "´": "\xB4", "Æ": "\xC6", "æ": "\xE6", "À": "\xC0", "à": "\xE0", "Α": "\u0391", "α": "\u03B1", "Ā": "\u0100", "ā": "\u0101", "&": "&", "∧": "\u2227", "∠": "\u2220", "Ą": "\u0104", "ą": "\u0105", "Å": "\xC5", "å": "\xE5", "≈": "\u2248", "Ã": "\xC3", "ã": "\xE3", "Ä": "\xC4", "ä": "\xE4", "„": "\u201E", "Β": "\u0392", "β": "\u03B2", "¦": "\xA6", "•": "\u2022", "Ć": "\u0106", "ć": "\u0107", "∩": "\u2229", "Č": "\u010C", "č": "\u010D", "Ç": "\xC7", "ç": "\xE7", "Ĉ": "\u0108", "ĉ": "\u0109", "Ċ": "\u010A", "ċ": "\u010B", "¸": "\xB8", "¢": "\xA2", "Χ": "\u03A7", "χ": "\u03C7", "ˆ": "\u02C6", "♣": "\u2663", "≅": "\u2245", "©": "\xA9", "↵": "\u21B5", "∪": "\u222A", "¤": "\xA4", "†": "\u2020", "‡": "\u2021", "↓": "\u2193", "Ď": "\u010E", "ď": "\u010F", "°": "\xB0", "Δ": "\u0394", "δ": "\u03B4", "♦": "\u2666", "÷": "\xF7", "˙": "\xB7", "Đ": "\u0110", "đ": "\u0111", "É": "\xC9", "é": "\xE9", "&Ebreve;": "\u0114", "&ebreve;": "\u0115", "Ě": "\u011A", "ě": "\u011B", "Ê": "\xCA", "ê": "\xEA", "Ė": "\u0116", "ė": "\u0117", "È": "\xC8", "è": "\xE8", "Ē": "\u0112", "ē": "\u0113", "∅": "\u2205", " ": "\u2003", "Ŋ": "\u014A", "ŋ": "\u014B", " ": "\u2002", "Ę": "\u0118", "ę": "\u0119", "Ε": "\u0395", "ε": "\u03B5", "≡": "\u2261", "Η": "\u0397", "η": "\u03B7", "Ð": "\xD0", "ð": "\xF0", "Ë": "\xCB", "ë": "\xEB", "€": "\u20AC", "∃": "\u2203", "ƒ": "\u0192", "∀": "\u2200", "½": "\xBD", "¼": "\xBC", "¾": "\xBE", "Γ": "\u0393", "γ": "\u03B3", "Ğ": "\u011E", "ğ": "\u011F", "Ģ": "\u0122", "&gcedil;": "\u0123", "Ĝ": "\u011C", "ĝ": "\u011D", "Ġ": "\u0120", "ġ": "\u0121", "≥": "\u2265", ">": ">", "↔": "\u2194", "Ĥ": "\u0124", "ĥ": "\u0125", "♥": "\u2665", "…": "\u2026", "Ħ": "\u0126", "ħ": "\u0127", "Í": "\xCD", "í": "\xED", "&Ibreve;": "\u012C", "&ibreve;": "\u012D", "Î": "\xCE", "î": "\xEE", "İ": "\u0130", "¡": "\xA1", "Ì": "\xCC", "ì": "\xEC", "IJ": "\u0132", "ij": "\u0133", "Ī": "\u012A", "ī": "\u012B", "ı": "\u0131", "∞": "\u221E", "∫": "\u222B", "Į": "\u012E", "į": "\u012F", "Ι": "\u0399", "ι": "\u03B9", "¿": "\xBF", "∈": "\u2208", "Ĩ": "\u0128", "ĩ": "\u0129", "Ï": "\xCF", "ï": "\xEF", "Ĵ": "\u0134", "ĵ": "\u0135", "Κ": "\u039A", "κ": "\u03BA", "Ķ": "\u0136", "ķ": "\u0137", "ĸ": "\u0138", "Ĺ": "\u0139", "ĺ": "\u013A", "Λ": "\u039B", "λ": "\u03BB", "«": "\xAB", "←": "\u2190", "Ľ": "\u013D", "ľ": "\u013E", "Ļ": "\u013B", "ļ": "\u013C", "⌈": "\u2308", "“": "\u201C", "≤": "\u2264", "⌊": "\u230A", "Ŀ": "\u013F", "ŀ": "\u0140", "∗": "\u2217", "◊": "\u25CA", "‎": "\u200E", "‹": "\u2039", "‘": "\u2018", "Ł": "\u0141", "ł": "\u0142", "<": "<", "¯": "\xAF", "—": "\u2014", "µ": "\xB5", "−": "\u2212", "Μ": "\u039C", "μ": "\u03BC", "∇": "\u2207", "Ń": "\u0143", "ń": "\u0144", "ʼn": "\u0149", " ": " ", "Ň": "\u0147", "ň": "\u0148", "Ņ": "\u0145", "ņ": "\u0146", "–": "\u2013", "≠": "\u2260", "
": "\n", "∋": "\u220B", "¬": "\xAC", "∉": "\u2209", "⊄": "\u2284", "Ñ": "\xD1", "ñ": "\xF1", "Ν": "\u039D", "ν": "\u03BD", "Ó": "\xD3", "ó": "\xF3", "&Obreve;": "\u014E", "&obreve;": "\u014F", "Ô": "\xD4", "ô": "\xF4", "Ő": "\u0150", "ő": "\u0151", "Œ": "\u0152", "œ": "\u0153", "Ò": "\xD2", "ò": "\xF2", "‾": "\u203E", "Ō": "\u014C", "ō": "\u014D", "Ω": "\u03A9", "ω": "\u03C9", "Ο": "\u039F", "ο": "\u03BF", "⊕": "\u2295", "∨": "\u2228", "ª": "\xAA", "º": "\xBA", "Ø": "\xD8", "ø": "\xF8", "Õ": "\xD5", "õ": "\xF5", "⊗": "\u2297", "Ö": "\xD6", "ö": "\xF6", "¶": "\xB6", "∂": "\u2202", "‰": "\u2030", "⊥": "\u22A5", "Φ": "\u03A6", "φ": "\u03C6", "Π": "\u03A0", "π": "\u03C0", "ϖ": "\u03D6", "±": "\xB1", "£": "\xA3", "′": "\u2032", "″": "\u2033", "∏": "\u220F", "∝": "\u221D", "Ψ": "\u03A8", "ψ": "\u03C8", """: '"', "Ŕ": "\u0154", "ŕ": "\u0155", "√": "\u221A", "»": "\xBB", "→": "\u2192", "Ř": "\u0158", "ř": "\u0159", "Ŗ": "\u0156", "ŗ": "\u0157", "⌉": "\u2309", "”": "\u201D", "®": "\xAE", "⌋": "\u230B", "Ρ": "\u03A1", "ρ": "\u03C1", "‏": "\u200F", "›": "\u203A", "’": "\u2019", "Ś": "\u015A", "ś": "\u015B", "‚": "\u201A", "Š": "\u0160", "š": "\u0161", "Ş": "\u015E", "ş": "\u015F", "Ŝ": "\u015C", "ŝ": "\u015D", "⋅": "\u22C5", "§": "\xA7", "­": "\xAD", "Σ": "\u03A3", "σ": "\u03C3", "ς": "\u03C2", "∼": "\u223C", "♠": "\u2660", "⊂": "\u2282", "⊆": "\u2286", "∑": "\u2211", "⊃": "\u2283", "¹": "\xB9", "²": "\xB2", "³": "\xB3", "⊇": "\u2287", "ß": "\xDF", "	": " ", "Τ": "\u03A4", "τ": "\u03C4", "Ť": "\u0164", "ť": "\u0165", "Ţ": "\u0162", "ţ": "\u0163", "∴": "\u2234", "Θ": "\u0398", "θ": "\u03B8", "ϑ": "\u03D1", " ": "\u2009", "Þ": "\xDE", "þ": "\xFE", "˜": "\u02DC", "×": "\xD7", "™": "\u2122", "Ŧ": "\u0166", "ŧ": "\u0167", "Ú": "\xDA", "ú": "\xFA", "↑": "\u2191", "Ŭ": "\u016C", "ŭ": "\u016D", "Û": "\xDB", "û": "\xFB", "Ű": "\u0170", "ű": "\u0171", "Ù": "\xD9", "ù": "\xF9", "Ū": "\u016A", "ū": "\u016B", "¨": "\xA8", "Ų": "\u0172", "ų": "\u0173", "ϒ": "\u03D2", "Υ": "\u03A5", "υ": "\u03C5", "Ů": "\u016E", "ů": "\u016F", "Ũ": "\u0168", "ũ": "\u0169", "Ü": "\xDC", "ü": "\xFC", "Ŵ": "\u0174", "ŵ": "\u0175", "Ξ": "\u039E", "ξ": "\u03BE", "Ý": "\xDD", "ý": "\xFD", "Ŷ": "\u0176", "ŷ": "\u0177", "¥": "\xA5", "ÿ": "\xFF", "Ÿ": "\u0178", "Ζ": "\u0396", "ζ": "\u03B6", "‍": "\u200D", "‌": "\u200C" };
function transformImage(value) {
if (typeof value === "string")
return [value];
if (value.url)
return [value.url];
if (Array.isArray(value)) {
return value.map((item) => {
if (typeof item === "string") {
return item;
}
if (typeof item === "object") {
if (item.url) {
return item.url;
}
}
return item;
}).filter((item) => item !== void 0 && item !== "");
}
return value;
}
function transformToList(value) {
if (typeof value === "string") {
if (value.includes(","))
return value.split(",").map((item) => item.trim());
return [value];
}
if (Array.isArray(value)) {
const array = value;
const filtered = array.filter((item) => {
return typeof item === "string";
});
return filtered;
}
return value;
}
function transformToString(value) {
if (typeof value === "string")
return cleanHtmlCodes(value);
if (Array.isArray(value)) {
const array = value;
if (array.length > 0) {
const filtered = array.filter((item) => {
return typeof item === "string";
});
if (filtered.length > 0) {
return cleanHtmlCodes(filtered[0]);
}
}
}
if (typeof value === "number") {
return `${value}`;
}
return value;
}
function transformToAuthor(value) {
if (!value) {
return void 0;
}
if (typeof value === "string")
return cleanHtmlCodes(value);
if (value.name)
return cleanHtmlCodes(value.name);
if (Array.isArray(value)) {
const array = value;
if (array.length > 0) {
const first = array[0];
if (typeof first === "string") {
return first;
}
if (first.name) {
return cleanHtmlCodes(first.name);
}
}
} else if (typeof value === "object") {
if (value.name) {
return cleanHtmlCodes(value.name);
}
}
return void 0;
}
function transformISOToString(dateObj) {
let date = "";
if (dateObj.days)
date += dateObj.days > 1 ? `${dateObj.days} days ` : `${dateObj.days} day `;
if (dateObj.hours)
date += dateObj.hours > 1 ? `${dateObj.hours} hours ` : `${dateObj.hours} hour `;
if (dateObj.minutes)
date += dateObj.minutes > 1 ? `${dateObj.minutes} minutes ` : `${dateObj.minutes} minute `;
if (dateObj.seconds)
date += dateObj.seconds > 1 ? `${dateObj.seconds} seconds ` : `${dateObj.seconds} second `;
const final = date.trim();
if (final.length === 0) {
return void 0;
}
return final;
}
function transformToTime(value) {
if (!value) {
return null;
}
const time = transformToString(value);
if (!time) {
return null;
}
try {
const parsedISODuration = (0, import_iso8601_duration.parse)(time);
if (parsedISODuration)
return transformISOToString(parsedISODuration);
} catch {
}
return time;
}
function cleanString(str) {
let cleaned = str.replace(MATCH_HTML_TAGS, "").replace(MATCH_LINE_BREAK, " ").replace(MATCH_MULTI_SPACE, " ").trim();
if (cleaned.startsWith(", ")) {
cleaned = cleaned.slice(1).trim();
}
return cleanHtmlCodes(cleaned);
}
function transformToCleanString(value) {
return cleanString(transformToString(value));
}
function transformInstructions(value) {
if (typeof value === "string") {
const cleanedValue = cleanString(value);
if (cleanedValue.includes(".,"))
return cleanedValue.split(".,").map((item) => item.trim());
return [cleanedValue];
}
if (Array.isArray(value)) {
const firstItem = value[0];
if (typeof firstItem === "string")
return value.map((item) => cleanString(item)).filter((item) => item !== void 0);
if (typeof firstItem === "object" && !Array.isArray(firstItem)) {
const itemList = firstItem.itemListElement;
if (itemList && Array.isArray(itemList)) {
return itemList.map((item) => {
if (item.text)
return cleanString(item.text);
return void 0;
}).filter((item) => item !== void 0);
}
}
return value.map((item) => {
if (item.text)
return cleanString(item.text);
return void 0;
}).filter((item) => item !== void 0);
}
}
function cleanIngredientAmounts(line) {
let cleaned = line.replace(/¼/g, "1/4").replace(/½/g, "1/2").replace(/¾/g, "3/4").replace(/⅔/g, "2/3").replace(MATCH_HTML_TAGS, "").replace(MATCH_MULTI_SPACE, " ").trim();
if (cleaned.startsWith(", ")) {
cleaned = cleaned.slice(1).trim();
}
if (cleaned.startsWith("unit ")) {
cleaned = cleaned.slice(4).trim();
}
return cleaned;
}
function transformIngredients(value) {
if (value && typeof value[0] === "string")
return value.map((item) => cleanIngredientAmounts(item));
const mappedItems = [];
Object.entries(value).forEach(([, item]) => {
if (item.properties) {
const { name: name2, amount } = item.properties;
if (name2 || amount) {
const _name = name2 && name2[0];
const _amount = amount && amount[0];
const singleLine = _amount ? `${_amount} ${_name}` : _name;
mappedItems.push(cleanIngredientAmounts(singleLine));
}
}
});
if (mappedItems.length)
return mappedItems;
return [];
}
function cleanHtmlCodes(str) {
if (!str || typeof str !== "string") {
return str;
}
const regex = new RegExp(Object.keys(htmlCodes).join("|"), "g");
return str.replace(regex, (match) => htmlCodes[match]);
}
var propertyTransformerMap = {
name: transformToString,
author: transformToAuthor,
image: transformImage,
description: transformToCleanString,
cookTime: transformToTime,
prepTime: transformToTime,
totalTime: transformToTime,
recipeYield: transformToString,
recipeIngredients: transformIngredients,
recipeInstructions: transformInstructions,
recipeCategories: transformToList,
recipeCuisines: transformToList,
keywords: transformToList
};
var propertyTransforrmer_default = propertyTransformerMap;
// src/index.ts
var DEFAULT_OPTIONS = {
maxRedirects: 5,
timeout: 1e4,
lang: "en-US,en;q=0.9",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
};
function consolidateRecipeProperties(recipe) {
return {
url: recipe.url,
author: recipe.author,
name: recipe.name,
image: recipe.image || recipe.thumbnailUrl,
description: recipe.description,
cookTime: recipe.cookTime,
prepTime: recipe.prepTime,
totalTime: recipe.totalTime,
recipeYield: recipe.recipeYield,
recipeIngredients: recipe.recipeIngredient,
recipeInstructions: recipe.recipeInstructions,
recipeCategories: recipe.recipeCategory,
recipeCuisines: recipe.recipeCuisine,
keywords: recipe.keywords
};
}
function prettifyRecipe(recipe, url) {
const transformedRecipe = {};
const consolidatedRecipe = consolidateRecipeProperties(recipe);
transformedRecipe.url = recipe.url?.toString() || (isValidHttpUrl(url) ? url : void 0);
Object.entries(consolidatedRecipe).forEach(([key, value]) => {
const propertyTransformer = propertyTransforrmer_default[key];
if (value && propertyTransformer)
transformedRecipe[key] = propertyTransformer(value);
});
return transformedRecipe;
}
async function getRecipeData(input, inputOptions = {}) {
let siteUrl, html, recipe;
if (typeof input === "object") {
inputOptions = input;
siteUrl = input;
} else {
siteUrl = input;
}
const options = { ...DEFAULT_OPTIONS, ...inputOptions };
if (!isValidHttpUrl(siteUrl) && !options.html)
throw new Error("Url must start with http:// or https://");
try {
const response = await import_axios.default.get(siteUrl, {
responseType: "text",
headers: {
"User-Agent": options.userAgent,
"Accept-Language": options.lang,
"Accept-Encoding": "gzip, deflate, br"
},
timeout: options.timeout,
maxRedirects: options.maxRedirects
});
html = response.data;
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
if (options.html)
html = options.html;
else
throw new Error(message);
}
try {
const $ = cheerio.load(html);
const tags = $('script[type="application/ld+json"]');
if (tags.length > 0) {
for (let i = 0; i < tags.length; i++) {
const tag = tags[i];
const textContent = $(tag).text();
if (textContent) {
const data = JSON.parse(textContent);
if (data["@graph"] && Array.isArray(data["@graph"])) {
data["@graph"].forEach((g) => {
if (g["@type"] === "Recipe") {
recipe = g;
}
});
}
if (data["@type"] === "Recipe")
recipe = data;
if (Array.isArray(data["@type"]) && data["@type"].includes("Recipe"))
recipe = data;
if (Array.isArray(data))
recipe = data.find((obj) => obj["@type"] && obj["@type"].includes("Recipe"));
} else {
throw new Error("Something went wrong while scraping");
}
}
} else {
throw new Error("Trying search for microdata next");
}
} catch {
const data = import_microdata_node.default.toJson(html);
if (!data || !data.items || !data.items[0])
throw new Error("HTML tags provided has no valid recipe schema");
const recipeData = Object.values(data.items).find((item) => item.type[0].includes("Recipe"));
if (!recipeData?.properties)
throw new Error("Recipe not found on page");
recipe = recipeData.properties;
}
if (!recipe) {
throw new Error("Recipe not found on page");
}
const prettifiedRecipe = prettifyRecipe(recipe, siteUrl);
if (prettifiedRecipe !== void 0) {
const response = (0, import_jsonschema.validate)({ name: prettifiedRecipe.name, recipeIngredients: prettifiedRecipe.recipeIngredients }, schema_default);
if (!response.valid)
throw new Error("Recipe is not valid");
return prettifiedRecipe;
}
}
// src/cli.ts
var { name, description, version } = package_default;
import_commander.program.name(`npx ${name}`).version(version).allowExcessArguments(false).arguments("<url>").description(description, { url: "food recipe url" }).action(async (url) => {
try {
console.log(await getRecipeData(url));
} catch (err) {
console.error(err.message);
process.exit(1);
}
});
import_commander.program.parseAsync().catch((e) => {
console.error(e.message);
process.exit(1);
});