scrapex 0.5.3 → 1.0.0-beta.1

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 (47) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +551 -145
  3. package/dist/enhancer-ByjRD-t5.mjs +769 -0
  4. package/dist/enhancer-ByjRD-t5.mjs.map +1 -0
  5. package/dist/enhancer-j0xqKDJm.cjs +847 -0
  6. package/dist/enhancer-j0xqKDJm.cjs.map +1 -0
  7. package/dist/index-CDgcRnig.d.cts +268 -0
  8. package/dist/index-CDgcRnig.d.cts.map +1 -0
  9. package/dist/index-piS5wtki.d.mts +268 -0
  10. package/dist/index-piS5wtki.d.mts.map +1 -0
  11. package/dist/index.cjs +2007 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.cts +580 -0
  14. package/dist/index.d.cts.map +1 -0
  15. package/dist/index.d.mts +580 -0
  16. package/dist/index.d.mts.map +1 -0
  17. package/dist/index.mjs +1956 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/llm/index.cjs +334 -0
  20. package/dist/llm/index.cjs.map +1 -0
  21. package/dist/llm/index.d.cts +258 -0
  22. package/dist/llm/index.d.cts.map +1 -0
  23. package/dist/llm/index.d.mts +258 -0
  24. package/dist/llm/index.d.mts.map +1 -0
  25. package/dist/llm/index.mjs +317 -0
  26. package/dist/llm/index.mjs.map +1 -0
  27. package/dist/parsers/index.cjs +11 -0
  28. package/dist/parsers/index.d.cts +2 -0
  29. package/dist/parsers/index.d.mts +2 -0
  30. package/dist/parsers/index.mjs +3 -0
  31. package/dist/parsers-Bneuws8x.cjs +569 -0
  32. package/dist/parsers-Bneuws8x.cjs.map +1 -0
  33. package/dist/parsers-CwkYnyWY.mjs +482 -0
  34. package/dist/parsers-CwkYnyWY.mjs.map +1 -0
  35. package/dist/types-CadAXrme.d.mts +674 -0
  36. package/dist/types-CadAXrme.d.mts.map +1 -0
  37. package/dist/types-DPEtPihB.d.cts +674 -0
  38. package/dist/types-DPEtPihB.d.cts.map +1 -0
  39. package/package.json +79 -100
  40. package/dist/index.d.ts +0 -45
  41. package/dist/index.js +0 -8
  42. package/dist/scrapex.cjs.development.js +0 -1130
  43. package/dist/scrapex.cjs.development.js.map +0 -1
  44. package/dist/scrapex.cjs.production.min.js +0 -2
  45. package/dist/scrapex.cjs.production.min.js.map +0 -1
  46. package/dist/scrapex.esm.js +0 -1122
  47. package/dist/scrapex.esm.js.map +0 -1
@@ -0,0 +1,482 @@
1
+ import * as cheerio from "cheerio";
2
+ import { fromMarkdown } from "mdast-util-from-markdown";
3
+ import { toString } from "mdast-util-to-string";
4
+ import { visit } from "unist-util-visit";
5
+
6
+ //#region src/parsers/github.ts
7
+ /**
8
+ * GitHub-specific utilities for parsing repositories.
9
+ */
10
+ /**
11
+ * Check if a URL is a GitHub repository
12
+ */
13
+ function isGitHubRepo(url) {
14
+ return /^https?:\/\/(www\.)?github\.com\/[^/]+\/[^/]+\/?$/.test(url);
15
+ }
16
+ /**
17
+ * Extract GitHub repo info from URL
18
+ */
19
+ function parseGitHubUrl(url) {
20
+ const match = url.match(/github\.com\/([^/]+)\/([^/]+)/);
21
+ if (!match || !match[1] || !match[2]) return null;
22
+ return {
23
+ owner: match[1],
24
+ repo: match[2].replace(/\.git$/, "")
25
+ };
26
+ }
27
+ /**
28
+ * Convert a GitHub repo URL to raw content URL
29
+ */
30
+ function toRawUrl(url, branch = "main", file = "README.md") {
31
+ const info = parseGitHubUrl(url);
32
+ if (!info) return url;
33
+ return `https://raw.githubusercontent.com/${info.owner}/${info.repo}/${branch}/${file}`;
34
+ }
35
+ /**
36
+ * Fetch GitHub API metadata for a repository
37
+ * Note: This is a placeholder - actual implementation would need GitHub API access
38
+ */
39
+ async function fetchRepoMeta(owner, repo, _token) {
40
+ return {
41
+ repoOwner: owner,
42
+ repoName: repo
43
+ };
44
+ }
45
+ /**
46
+ * Group links by their category/section
47
+ */
48
+ function groupByCategory(links) {
49
+ const groups = /* @__PURE__ */ new Map();
50
+ for (const link of links) {
51
+ const category = link.context || "Uncategorized";
52
+ const existing = groups.get(category) || [];
53
+ existing.push(link);
54
+ groups.set(category, existing);
55
+ }
56
+ return groups;
57
+ }
58
+
59
+ //#endregion
60
+ //#region src/parsers/markdown.ts
61
+ /**
62
+ * Generic Markdown parser.
63
+ * Extracts structure, links, and code blocks from markdown content.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const parser = new MarkdownParser();
68
+ * const result = parser.parse(markdownContent);
69
+ * console.log(result.data.sections);
70
+ * console.log(result.data.links);
71
+ * ```
72
+ */
73
+ var MarkdownParser = class {
74
+ name = "markdown";
75
+ canParse(content) {
76
+ return content.includes("# ") || content.includes("## ") || content.includes("- [") || content.includes("* [") || content.includes("```");
77
+ }
78
+ parse(content) {
79
+ const tree = fromMarkdown(content);
80
+ const sections = [];
81
+ const allLinks = [];
82
+ const codeBlocks = [];
83
+ let frontmatter;
84
+ if (content.startsWith("---")) {
85
+ const endIndex = content.indexOf("---", 3);
86
+ if (endIndex !== -1) {
87
+ const frontmatterContent = content.slice(3, endIndex).trim();
88
+ frontmatter = this.parseFrontmatter(frontmatterContent);
89
+ }
90
+ }
91
+ let currentSection = null;
92
+ visit(tree, (node) => {
93
+ if (node.type === "heading") {
94
+ const heading = node;
95
+ const title = toString(heading);
96
+ if (currentSection) sections.push(currentSection);
97
+ currentSection = {
98
+ level: heading.depth,
99
+ title,
100
+ content: "",
101
+ links: []
102
+ };
103
+ }
104
+ if (node.type === "link") {
105
+ const link = node;
106
+ const text = toString(link);
107
+ const linkData = {
108
+ url: link.url,
109
+ text,
110
+ title: link.title ?? void 0,
111
+ context: currentSection?.title
112
+ };
113
+ allLinks.push(linkData);
114
+ if (currentSection) currentSection.links.push(linkData);
115
+ }
116
+ if (node.type === "code") {
117
+ const code = node;
118
+ codeBlocks.push({
119
+ language: code.lang ?? void 0,
120
+ code: code.value,
121
+ meta: code.meta ?? void 0
122
+ });
123
+ }
124
+ if (currentSection && node.type === "paragraph") {
125
+ const text = toString(node);
126
+ currentSection.content += (currentSection.content ? "\n\n" : "") + text;
127
+ }
128
+ });
129
+ if (currentSection) sections.push(currentSection);
130
+ return { data: {
131
+ title: frontmatter?.title ?? sections.find((s) => s.level === 1)?.title,
132
+ description: frontmatter?.description ?? this.extractDescription(tree),
133
+ sections,
134
+ links: allLinks,
135
+ codeBlocks,
136
+ frontmatter
137
+ } };
138
+ }
139
+ parseFrontmatter(content) {
140
+ const result = {};
141
+ const lines = content.split("\n");
142
+ for (const line of lines) {
143
+ const colonIndex = line.indexOf(":");
144
+ if (colonIndex > 0) {
145
+ const key = line.slice(0, colonIndex).trim();
146
+ let value = line.slice(colonIndex + 1).trim();
147
+ if (value === "true") value = true;
148
+ else if (value === "false") value = false;
149
+ else if (/^-?\d+(\.\d+)?$/.test(value)) value = Number(value);
150
+ else if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
151
+ else if (value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
152
+ result[key] = value;
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+ extractDescription(tree) {
158
+ for (const node of tree.children) {
159
+ if (node.type === "heading") break;
160
+ if (node.type === "paragraph") return toString(node);
161
+ }
162
+ }
163
+ };
164
+ /**
165
+ * Extract links from a list-based markdown structure (like awesome lists)
166
+ */
167
+ function extractListLinks(markdown) {
168
+ const tree = fromMarkdown(markdown);
169
+ const links = [];
170
+ let currentHeading = "";
171
+ visit(tree, (node) => {
172
+ if (node.type === "heading") currentHeading = toString(node);
173
+ if (node.type === "listItem") visit(node, "link", (linkNode) => {
174
+ links.push({
175
+ url: linkNode.url,
176
+ text: toString(linkNode),
177
+ title: linkNode.title ?? void 0,
178
+ context: currentHeading || void 0
179
+ });
180
+ });
181
+ });
182
+ return links;
183
+ }
184
+ /**
185
+ * Parse markdown into sections by heading level
186
+ */
187
+ function parseByHeadings(markdown, minLevel = 2) {
188
+ return new MarkdownParser().parse(markdown).data.sections.filter((s) => s.level >= minLevel);
189
+ }
190
+
191
+ //#endregion
192
+ //#region src/parsers/rss.ts
193
+ /**
194
+ * RSS/Atom feed parser.
195
+ * Supports RSS 2.0, RSS 1.0 (RDF), and Atom 1.0 formats.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * import { RSSParser } from 'scrapex/parsers';
200
+ *
201
+ * const parser = new RSSParser();
202
+ * const result = parser.parse(feedXml, 'https://example.com/feed.xml');
203
+ *
204
+ * console.log(result.data.title);
205
+ * console.log(result.data.items);
206
+ * ```
207
+ */
208
+ var RSSParser = class {
209
+ name = "rss";
210
+ customFields;
211
+ constructor(options) {
212
+ this.customFields = options?.customFields || {};
213
+ }
214
+ canParse(content) {
215
+ const lower = content.toLowerCase();
216
+ return lower.includes("<rss") || lower.includes("<feed") || lower.includes("<rdf:rdf");
217
+ }
218
+ parse(content, url) {
219
+ const $ = cheerio.load(content, { xml: true });
220
+ if ($("feed").length > 0) return this.parseAtom($, url);
221
+ else if ($("rdf\\:RDF, RDF").length > 0) return this.parseRSS1($, url);
222
+ else return this.parseRSS2($, url);
223
+ }
224
+ parseRSS2($, baseUrl) {
225
+ const channel = $("channel");
226
+ const feedLink = channel.find("> link").text();
227
+ const resolveBase = baseUrl || feedLink;
228
+ const items = $("item").map((_, el) => {
229
+ const $item = $(el);
230
+ const itemLink = $item.find("link").text();
231
+ const guid = $item.find("guid").text();
232
+ const pubDate = $item.find("pubDate").text();
233
+ const resolvedLink = this.resolveLink(itemLink, guid, resolveBase);
234
+ return {
235
+ id: guid || itemLink,
236
+ title: $item.find("title").text(),
237
+ link: resolvedLink,
238
+ description: this.parseText($item.find("description")),
239
+ content: this.parseContentEncoded($item.find("content\\:encoded")),
240
+ author: $item.find("author").text() || $item.find("dc\\:creator").text() || void 0,
241
+ publishedAt: this.parseDate(pubDate),
242
+ rawPublishedAt: pubDate || void 0,
243
+ categories: this.parseCategories($item.find("category").map((_$1, c) => $(c).text()).get()),
244
+ enclosure: this.parseEnclosure($item.find("enclosure"), resolveBase),
245
+ customFields: this.extractCustomFields($item)
246
+ };
247
+ }).get();
248
+ return {
249
+ data: {
250
+ format: "rss2",
251
+ title: channel.find("> title").text(),
252
+ description: channel.find("> description").text() || void 0,
253
+ link: this.resolveUrl(feedLink, resolveBase),
254
+ language: channel.find("> language").text() || void 0,
255
+ lastBuildDate: this.parseDate(channel.find("> lastBuildDate").text()),
256
+ copyright: channel.find("> copyright").text() || void 0,
257
+ items,
258
+ customFields: this.extractCustomFields(channel)
259
+ },
260
+ meta: {
261
+ generator: channel.find("> generator").text() || void 0,
262
+ ttl: this.parseNumber(channel.find("> ttl").text()),
263
+ image: this.parseImage(channel.find("> image"), resolveBase),
264
+ categories: this.parseCategories(channel.find("> category").map((_, c) => $(c).text()).get())
265
+ }
266
+ };
267
+ }
268
+ parseAtom($, baseUrl) {
269
+ const feed = $("feed");
270
+ const feedLink = feed.find("> link[rel=\"alternate\"], > link:not([rel])").attr("href") || "";
271
+ const nextLink = feed.find("> link[rel=\"next\"]").attr("href");
272
+ const resolveBase = baseUrl || feedLink;
273
+ const items = $("entry").map((_, el) => {
274
+ const $entry = $(el);
275
+ const entryLink = $entry.find("link[rel=\"alternate\"], link:not([rel])").attr("href") || "";
276
+ const entryId = $entry.find("id").text();
277
+ const published = $entry.find("published").text();
278
+ const updated = $entry.find("updated").text();
279
+ const resolvedLink = this.resolveLink(entryLink, entryId, resolveBase);
280
+ return {
281
+ id: entryId,
282
+ title: $entry.find("title").text(),
283
+ link: resolvedLink,
284
+ description: this.parseText($entry.find("summary")),
285
+ content: this.parseText($entry.find("content")),
286
+ author: $entry.find("author name").text() || void 0,
287
+ publishedAt: this.parseDate(published),
288
+ rawPublishedAt: published || updated || void 0,
289
+ updatedAt: this.parseDate(updated),
290
+ categories: this.parseCategories($entry.find("category").map((_$1, c) => $(c).attr("term")).get()),
291
+ customFields: this.extractCustomFields($entry)
292
+ };
293
+ }).get();
294
+ return {
295
+ data: {
296
+ format: "atom",
297
+ title: feed.find("> title").text(),
298
+ description: feed.find("> subtitle").text() || void 0,
299
+ link: this.resolveUrl(feedLink, resolveBase),
300
+ next: nextLink ? this.resolveUrl(nextLink, resolveBase) : void 0,
301
+ language: feed.attr("xml:lang") || void 0,
302
+ lastBuildDate: this.parseDate(feed.find("> updated").text()),
303
+ copyright: feed.find("> rights").text() || void 0,
304
+ items,
305
+ customFields: this.extractCustomFields(feed)
306
+ },
307
+ meta: {
308
+ generator: feed.find("> generator").text() || void 0,
309
+ image: this.parseAtomImage(feed, resolveBase),
310
+ categories: this.parseCategories(feed.find("> category").map((_, c) => $(c).attr("term")).get())
311
+ }
312
+ };
313
+ }
314
+ parseRSS1($, baseUrl) {
315
+ const channel = $("channel");
316
+ const feedLink = channel.find("link").text();
317
+ const resolveBase = baseUrl || feedLink;
318
+ const items = $("item").map((_, el) => {
319
+ const $item = $(el);
320
+ const itemLink = $item.find("link").text();
321
+ const rdfAbout = $item.attr("rdf:about") || "";
322
+ const dcDate = $item.find("dc\\:date").text();
323
+ const resolvedLink = this.resolveLink(itemLink, rdfAbout, resolveBase);
324
+ const dcSubjects = $item.find("dc\\:subject").map((_$1, s) => $(s).text()).get();
325
+ return {
326
+ id: rdfAbout || itemLink,
327
+ title: $item.find("title").text(),
328
+ link: resolvedLink,
329
+ description: this.parseText($item.find("description")),
330
+ content: this.parseContentEncoded($item.find("content\\:encoded")),
331
+ author: $item.find("dc\\:creator").text() || void 0,
332
+ publishedAt: this.parseDate(dcDate),
333
+ rawPublishedAt: dcDate || void 0,
334
+ categories: this.parseCategories(dcSubjects),
335
+ customFields: this.extractCustomFields($item)
336
+ };
337
+ }).get();
338
+ const rdfImage = $("image");
339
+ const imageUrl = rdfImage.find("url").text() || rdfImage.attr("rdf:resource");
340
+ return {
341
+ data: {
342
+ format: "rss1",
343
+ title: channel.find("title").text(),
344
+ description: channel.find("description").text() || void 0,
345
+ link: this.resolveUrl(feedLink, resolveBase),
346
+ language: channel.find("dc\\:language").text() || void 0,
347
+ lastBuildDate: this.parseDate(channel.find("dc\\:date").text()),
348
+ copyright: channel.find("dc\\:rights").text() || void 0,
349
+ items,
350
+ customFields: this.extractCustomFields(channel)
351
+ },
352
+ meta: {
353
+ generator: channel.find("admin\\:generatorAgent").attr("rdf:resource") || void 0,
354
+ image: imageUrl ? {
355
+ url: this.resolveUrl(imageUrl, resolveBase),
356
+ title: rdfImage.find("title").text() || void 0,
357
+ link: this.resolveUrl(rdfImage.find("link").text(), resolveBase) || void 0
358
+ } : void 0,
359
+ categories: this.parseCategories(channel.find("dc\\:subject").map((_, s) => $(s).text()).get())
360
+ }
361
+ };
362
+ }
363
+ /**
364
+ * Extract custom fields from an element using configured selectors.
365
+ */
366
+ extractCustomFields($el) {
367
+ if (Object.keys(this.customFields).length === 0) return void 0;
368
+ const fields = {};
369
+ for (const [key, selector] of Object.entries(this.customFields)) {
370
+ const value = $el.find(selector).text().trim();
371
+ if (value) fields[key] = value;
372
+ }
373
+ return Object.keys(fields).length > 0 ? fields : void 0;
374
+ }
375
+ /**
376
+ * Parse date string to ISO 8601 format.
377
+ * Returns undefined if parsing fails (never returns raw strings).
378
+ */
379
+ parseDate(dateStr) {
380
+ if (!dateStr?.trim()) return void 0;
381
+ try {
382
+ const date = new Date(dateStr);
383
+ if (Number.isNaN(date.getTime())) return;
384
+ return date.toISOString();
385
+ } catch {
386
+ return;
387
+ }
388
+ }
389
+ /**
390
+ * Parse element text content, returning undefined if empty.
391
+ */
392
+ parseText($el) {
393
+ return $el.text().trim() || void 0;
394
+ }
395
+ /**
396
+ * Parse content:encoded, stripping HTML tags from CDATA content.
397
+ */
398
+ parseContentEncoded($el) {
399
+ const raw = $el.text().trim();
400
+ if (!raw) return void 0;
401
+ return raw.replace(/<[^>]+>/g, "").trim() || void 0;
402
+ }
403
+ /**
404
+ * Parse categories/tags, filtering out empty strings.
405
+ */
406
+ parseCategories(categories) {
407
+ return categories.map((c) => c?.trim()).filter((c) => !!c && c.length > 0);
408
+ }
409
+ /**
410
+ * Resolve a URL against a base URL.
411
+ * Only allows https scheme; all other schemes are rejected.
412
+ */
413
+ resolveUrl(url, base) {
414
+ if (!url?.trim()) return "";
415
+ try {
416
+ const resolved = base ? new URL(url, base) : new URL(url);
417
+ return resolved.protocol === "https:" ? resolved.href : "";
418
+ } catch {
419
+ return "";
420
+ }
421
+ }
422
+ /**
423
+ * Resolve link with fallback to id/guid if primary link is empty and id is a URL.
424
+ * Only allows https scheme; all other schemes are rejected.
425
+ */
426
+ resolveLink(primaryLink, fallbackId, base) {
427
+ if (primaryLink?.trim()) return this.resolveUrl(primaryLink, base);
428
+ if (fallbackId?.trim()) try {
429
+ const resolvedId = new URL(fallbackId);
430
+ return resolvedId.protocol === "https:" ? resolvedId.href : "";
431
+ } catch {
432
+ return this.resolveUrl(fallbackId, base);
433
+ }
434
+ return "";
435
+ }
436
+ /**
437
+ * Parse enclosure element with URL resolution.
438
+ */
439
+ parseEnclosure($enc, baseUrl) {
440
+ const url = $enc.attr("url");
441
+ if (!url) return void 0;
442
+ return {
443
+ url: this.resolveUrl(url, baseUrl),
444
+ type: $enc.attr("type") || void 0,
445
+ length: this.parseNumber($enc.attr("length"))
446
+ };
447
+ }
448
+ /**
449
+ * Parse RSS image element with URL resolution.
450
+ */
451
+ parseImage($img, baseUrl) {
452
+ const url = $img.find("url").text();
453
+ if (!url) return void 0;
454
+ return {
455
+ url: this.resolveUrl(url, baseUrl),
456
+ title: $img.find("title").text() || void 0,
457
+ link: this.resolveUrl($img.find("link").text(), baseUrl) || void 0
458
+ };
459
+ }
460
+ /**
461
+ * Parse Atom logo/icon element with URL resolution.
462
+ */
463
+ parseAtomImage($feed, baseUrl) {
464
+ const logo = $feed.find("> logo").text();
465
+ const icon = $feed.find("> icon").text();
466
+ const url = logo || icon;
467
+ if (!url) return void 0;
468
+ return { url: this.resolveUrl(url, baseUrl) };
469
+ }
470
+ /**
471
+ * Parse a string to number, returning undefined if invalid.
472
+ */
473
+ parseNumber(value) {
474
+ if (!value) return void 0;
475
+ const num = parseInt(value, 10);
476
+ return Number.isNaN(num) ? void 0 : num;
477
+ }
478
+ };
479
+
480
+ //#endregion
481
+ export { fetchRepoMeta as a, parseGitHubUrl as c, parseByHeadings as i, toRawUrl as l, MarkdownParser as n, groupByCategory as o, extractListLinks as r, isGitHubRepo as s, RSSParser as t };
482
+ //# sourceMappingURL=parsers-CwkYnyWY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsers-CwkYnyWY.mjs","names":["sections: MarkdownSection[]","allLinks: MarkdownLink[]","codeBlocks: CodeBlock[]","frontmatter: Record<string, unknown> | undefined","currentSection: MarkdownSection | null","mdastToString","linkData: MarkdownLink","result: Record<string, unknown>","value: string | boolean | number","links: MarkdownLink[]","items: FeedItem[]","fields: Record<string, string>"],"sources":["../src/parsers/github.ts","../src/parsers/markdown.ts","../src/parsers/rss.ts"],"sourcesContent":["import type { GitHubMeta, MarkdownLink } from './types.js';\n\n/**\n * GitHub-specific utilities for parsing repositories.\n */\n\n/**\n * Check if a URL is a GitHub repository\n */\nexport function isGitHubRepo(url: string): boolean {\n return /^https?:\\/\\/(www\\.)?github\\.com\\/[^/]+\\/[^/]+\\/?$/.test(url);\n}\n\n/**\n * Extract GitHub repo info from URL\n */\nexport function parseGitHubUrl(url: string): { owner: string; repo: string } | null {\n const match = url.match(/github\\.com\\/([^/]+)\\/([^/]+)/);\n if (!match || !match[1] || !match[2]) return null;\n return {\n owner: match[1],\n repo: match[2].replace(/\\.git$/, ''),\n };\n}\n\n/**\n * Convert a GitHub repo URL to raw content URL\n */\nexport function toRawUrl(url: string, branch = 'main', file = 'README.md'): string {\n const info = parseGitHubUrl(url);\n if (!info) return url;\n return `https://raw.githubusercontent.com/${info.owner}/${info.repo}/${branch}/${file}`;\n}\n\n/**\n * Fetch GitHub API metadata for a repository\n * Note: This is a placeholder - actual implementation would need GitHub API access\n */\nexport async function fetchRepoMeta(\n owner: string,\n repo: string,\n _token?: string\n): Promise<GitHubMeta> {\n // This would make actual API calls in a full implementation\n // For now, return basic info\n return {\n repoOwner: owner,\n repoName: repo,\n };\n}\n\n/**\n * Group links by their category/section\n */\nexport function groupByCategory(links: MarkdownLink[]): Map<string, MarkdownLink[]> {\n const groups = new Map<string, MarkdownLink[]>();\n\n for (const link of links) {\n const category = link.context || 'Uncategorized';\n const existing = groups.get(category) || [];\n existing.push(link);\n groups.set(category, existing);\n }\n\n return groups;\n}\n","import type { Code, Heading, Link, ListItem, Root } from 'mdast';\nimport { fromMarkdown } from 'mdast-util-from-markdown';\nimport { toString as mdastToString } from 'mdast-util-to-string';\nimport { visit } from 'unist-util-visit';\nimport type {\n CodeBlock,\n MarkdownLink,\n MarkdownSection,\n ParsedMarkdown,\n ParserResult,\n SourceParser,\n} from './types.js';\n\n/**\n * Generic Markdown parser.\n * Extracts structure, links, and code blocks from markdown content.\n *\n * @example\n * ```ts\n * const parser = new MarkdownParser();\n * const result = parser.parse(markdownContent);\n * console.log(result.data.sections);\n * console.log(result.data.links);\n * ```\n */\nexport class MarkdownParser implements SourceParser<ParsedMarkdown> {\n readonly name = 'markdown';\n\n canParse(content: string): boolean {\n // Check for common markdown patterns\n return (\n content.includes('# ') ||\n content.includes('## ') ||\n content.includes('- [') ||\n content.includes('* [') ||\n content.includes('```')\n );\n }\n\n parse(content: string): ParserResult<ParsedMarkdown> {\n const tree = fromMarkdown(content);\n const sections: MarkdownSection[] = [];\n const allLinks: MarkdownLink[] = [];\n const codeBlocks: CodeBlock[] = [];\n let frontmatter: Record<string, unknown> | undefined;\n\n // Extract frontmatter if present\n if (content.startsWith('---')) {\n const endIndex = content.indexOf('---', 3);\n if (endIndex !== -1) {\n const frontmatterContent = content.slice(3, endIndex).trim();\n frontmatter = this.parseFrontmatter(frontmatterContent);\n }\n }\n\n // Track current section\n let currentSection: MarkdownSection | null = null;\n\n // Process the AST\n visit(tree, (node) => {\n // Handle headings\n if (node.type === 'heading') {\n const heading = node as Heading;\n const title = mdastToString(heading);\n\n // Finalize previous section\n if (currentSection) {\n sections.push(currentSection);\n }\n\n currentSection = {\n level: heading.depth,\n title,\n content: '',\n links: [],\n };\n }\n\n // Handle links\n if (node.type === 'link') {\n const link = node as Link;\n const text = mdastToString(link);\n const linkData: MarkdownLink = {\n url: link.url,\n text,\n title: link.title ?? undefined,\n context: currentSection?.title,\n };\n\n allLinks.push(linkData);\n if (currentSection) {\n currentSection.links.push(linkData);\n }\n }\n\n // Handle code blocks\n if (node.type === 'code') {\n const code = node as Code;\n codeBlocks.push({\n language: code.lang ?? undefined,\n code: code.value,\n meta: code.meta ?? undefined,\n });\n }\n\n // Accumulate content for current section\n if (currentSection && node.type === 'paragraph') {\n const text = mdastToString(node);\n currentSection.content += (currentSection.content ? '\\n\\n' : '') + text;\n }\n });\n\n // Finalize last section\n if (currentSection) {\n sections.push(currentSection);\n }\n\n // Extract title from first h1 or frontmatter\n const title = (frontmatter?.title as string) ?? sections.find((s) => s.level === 1)?.title;\n\n // Extract description from frontmatter or first paragraph before any heading\n const description = (frontmatter?.description as string) ?? this.extractDescription(tree);\n\n return {\n data: {\n title,\n description,\n sections,\n links: allLinks,\n codeBlocks,\n frontmatter,\n },\n };\n }\n\n private parseFrontmatter(content: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n const lines = content.split('\\n');\n\n for (const line of lines) {\n const colonIndex = line.indexOf(':');\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n let value: string | boolean | number = line.slice(colonIndex + 1).trim();\n\n // Parse simple types\n if (value === 'true') value = true;\n else if (value === 'false') value = false;\n else if (/^-?\\d+(\\.\\d+)?$/.test(value)) value = Number(value);\n else if (value.startsWith('\"') && value.endsWith('\"')) value = value.slice(1, -1);\n else if (value.startsWith(\"'\") && value.endsWith(\"'\")) value = value.slice(1, -1);\n\n result[key] = value;\n }\n }\n\n return result;\n }\n\n private extractDescription(tree: Root): string | undefined {\n // Find first paragraph before any heading\n for (const node of tree.children) {\n if (node.type === 'heading') break;\n if (node.type === 'paragraph') {\n return mdastToString(node);\n }\n }\n return undefined;\n }\n}\n\n/**\n * Extract links from a list-based markdown structure (like awesome lists)\n */\nexport function extractListLinks(markdown: string): MarkdownLink[] {\n const tree = fromMarkdown(markdown);\n const links: MarkdownLink[] = [];\n let currentHeading = '';\n\n visit(tree, (node) => {\n if (node.type === 'heading') {\n currentHeading = mdastToString(node as Heading);\n }\n\n if (node.type === 'listItem') {\n const listItem = node as ListItem;\n\n // Find links in this list item\n visit(listItem, 'link', (linkNode: Link) => {\n links.push({\n url: linkNode.url,\n text: mdastToString(linkNode),\n title: linkNode.title ?? undefined,\n context: currentHeading || undefined,\n });\n });\n }\n });\n\n return links;\n}\n\n/**\n * Parse markdown into sections by heading level\n */\nexport function parseByHeadings(markdown: string, minLevel = 2): MarkdownSection[] {\n const parser = new MarkdownParser();\n const result = parser.parse(markdown);\n return result.data.sections.filter((s) => s.level >= minLevel);\n}\n","import * as cheerio from 'cheerio';\nimport type { Element } from 'domhandler';\nimport type {\n FeedEnclosure,\n FeedItem,\n FeedMeta,\n ParsedFeed,\n ParserResult,\n SourceParser,\n} from './types.js';\n\nexport interface RSSParserOptions {\n /**\n * Map of custom field names to CSS selectors or XML tag names.\n * Useful for extracting podcast/media namespace fields.\n * @example { duration: \"itunes\\:duration\", rating: \"media\\:rating\" }\n */\n customFields?: Record<string, string>;\n}\n\n/**\n * RSS/Atom feed parser.\n * Supports RSS 2.0, RSS 1.0 (RDF), and Atom 1.0 formats.\n *\n * @example\n * ```ts\n * import { RSSParser } from 'scrapex/parsers';\n *\n * const parser = new RSSParser();\n * const result = parser.parse(feedXml, 'https://example.com/feed.xml');\n *\n * console.log(result.data.title);\n * console.log(result.data.items);\n * ```\n */\nexport class RSSParser implements SourceParser<ParsedFeed, FeedMeta> {\n readonly name = 'rss';\n private customFields: Record<string, string>;\n\n constructor(options?: RSSParserOptions) {\n this.customFields = options?.customFields || {};\n }\n\n canParse(content: string): boolean {\n const lower = content.toLowerCase();\n return lower.includes('<rss') || lower.includes('<feed') || lower.includes('<rdf:rdf');\n }\n\n parse(content: string, url?: string): ParserResult<ParsedFeed, FeedMeta> {\n // Cheerio's xml: true mode disables HTML entities and structure fixes,\n // effectively preventing many XSS/injection vectors during parsing.\n const $ = cheerio.load(content, { xml: true });\n\n // Detect format and parse\n if ($('feed').length > 0) {\n return this.parseAtom($, url);\n } else if ($('rdf\\\\:RDF, RDF').length > 0) {\n return this.parseRSS1($, url);\n } else {\n return this.parseRSS2($, url);\n }\n }\n\n private parseRSS2($: cheerio.CheerioAPI, baseUrl?: string): ParserResult<ParsedFeed, FeedMeta> {\n const channel = $('channel');\n const feedLink = channel.find('> link').text();\n const resolveBase = baseUrl || feedLink;\n\n const items: FeedItem[] = $('item')\n .map((_, el) => {\n const $item = $(el);\n const itemLink = $item.find('link').text();\n const guid = $item.find('guid').text();\n const pubDate = $item.find('pubDate').text();\n\n // Resolve link with fallback to guid if it's a URL\n const resolvedLink = this.resolveLink(itemLink, guid, resolveBase);\n\n return {\n id: guid || itemLink,\n title: $item.find('title').text(),\n link: resolvedLink,\n description: this.parseText($item.find('description')),\n content: this.parseContentEncoded($item.find('content\\\\:encoded')),\n author: $item.find('author').text() || $item.find('dc\\\\:creator').text() || undefined,\n publishedAt: this.parseDate(pubDate),\n rawPublishedAt: pubDate || undefined,\n categories: this.parseCategories(\n $item\n .find('category')\n .map((_, c) => $(c).text())\n .get()\n ),\n enclosure: this.parseEnclosure($item.find('enclosure'), resolveBase),\n customFields: this.extractCustomFields($item),\n };\n })\n .get();\n\n return {\n data: {\n format: 'rss2',\n title: channel.find('> title').text(),\n description: channel.find('> description').text() || undefined,\n link: this.resolveUrl(feedLink, resolveBase),\n language: channel.find('> language').text() || undefined,\n lastBuildDate: this.parseDate(channel.find('> lastBuildDate').text()),\n copyright: channel.find('> copyright').text() || undefined,\n items,\n customFields: this.extractCustomFields(channel),\n },\n meta: {\n generator: channel.find('> generator').text() || undefined,\n ttl: this.parseNumber(channel.find('> ttl').text()),\n image: this.parseImage(channel.find('> image'), resolveBase),\n categories: this.parseCategories(\n channel\n .find('> category')\n .map((_, c) => $(c).text())\n .get()\n ),\n },\n };\n }\n\n private parseAtom($: cheerio.CheerioAPI, baseUrl?: string): ParserResult<ParsedFeed, FeedMeta> {\n const feed = $('feed');\n const feedLink = feed.find('> link[rel=\"alternate\"], > link:not([rel])').attr('href') || '';\n const nextLink = feed.find('> link[rel=\"next\"]').attr('href');\n const resolveBase = baseUrl || feedLink;\n\n const items: FeedItem[] = $('entry')\n .map((_, el) => {\n const $entry = $(el);\n const entryLink = $entry.find('link[rel=\"alternate\"], link:not([rel])').attr('href') || '';\n const entryId = $entry.find('id').text();\n const published = $entry.find('published').text();\n const updated = $entry.find('updated').text();\n\n // Resolve link with fallback to id if it's a URL\n const resolvedLink = this.resolveLink(entryLink, entryId, resolveBase);\n\n return {\n id: entryId,\n title: $entry.find('title').text(),\n link: resolvedLink,\n description: this.parseText($entry.find('summary')),\n content: this.parseText($entry.find('content')),\n author: $entry.find('author name').text() || undefined,\n publishedAt: this.parseDate(published),\n rawPublishedAt: published || updated || undefined,\n updatedAt: this.parseDate(updated),\n categories: this.parseCategories(\n $entry\n .find('category')\n .map((_, c) => $(c).attr('term'))\n .get()\n ),\n customFields: this.extractCustomFields($entry),\n };\n })\n .get();\n\n return {\n data: {\n format: 'atom',\n title: feed.find('> title').text(),\n description: feed.find('> subtitle').text() || undefined,\n link: this.resolveUrl(feedLink, resolveBase),\n next: nextLink ? this.resolveUrl(nextLink, resolveBase) : undefined,\n language: feed.attr('xml:lang') || undefined,\n lastBuildDate: this.parseDate(feed.find('> updated').text()),\n copyright: feed.find('> rights').text() || undefined,\n items,\n customFields: this.extractCustomFields(feed),\n },\n meta: {\n generator: feed.find('> generator').text() || undefined,\n image: this.parseAtomImage(feed, resolveBase),\n categories: this.parseCategories(\n feed\n .find('> category')\n .map((_, c) => $(c).attr('term'))\n .get()\n ),\n },\n };\n }\n\n private parseRSS1($: cheerio.CheerioAPI, baseUrl?: string): ParserResult<ParsedFeed, FeedMeta> {\n const channel = $('channel');\n const feedLink = channel.find('link').text();\n const resolveBase = baseUrl || feedLink;\n\n // RSS 1.0 items are siblings of channel, not children\n const items: FeedItem[] = $('item')\n .map((_, el) => {\n const $item = $(el);\n const itemLink = $item.find('link').text();\n const rdfAbout = $item.attr('rdf:about') || '';\n const dcDate = $item.find('dc\\\\:date').text();\n\n // Resolve link with fallback to rdf:about\n const resolvedLink = this.resolveLink(itemLink, rdfAbout, resolveBase);\n\n // Extract dc:subject as categories (RSS 1.0 uses Dublin Core)\n const dcSubjects = $item\n .find('dc\\\\:subject')\n .map((_, s) => $(s).text())\n .get();\n\n return {\n id: rdfAbout || itemLink,\n title: $item.find('title').text(),\n link: resolvedLink,\n description: this.parseText($item.find('description')),\n content: this.parseContentEncoded($item.find('content\\\\:encoded')),\n author: $item.find('dc\\\\:creator').text() || undefined,\n publishedAt: this.parseDate(dcDate),\n rawPublishedAt: dcDate || undefined,\n categories: this.parseCategories(dcSubjects),\n customFields: this.extractCustomFields($item),\n };\n })\n .get();\n\n // Parse RDF image element (sibling of channel)\n const rdfImage = $('image');\n const imageUrl = rdfImage.find('url').text() || rdfImage.attr('rdf:resource');\n\n return {\n data: {\n format: 'rss1',\n title: channel.find('title').text(),\n description: channel.find('description').text() || undefined,\n link: this.resolveUrl(feedLink, resolveBase),\n language: channel.find('dc\\\\:language').text() || undefined,\n lastBuildDate: this.parseDate(channel.find('dc\\\\:date').text()),\n copyright: channel.find('dc\\\\:rights').text() || undefined,\n items,\n customFields: this.extractCustomFields(channel),\n },\n meta: {\n generator: channel.find('admin\\\\:generatorAgent').attr('rdf:resource') || undefined,\n image: imageUrl\n ? {\n url: this.resolveUrl(imageUrl, resolveBase),\n title: rdfImage.find('title').text() || undefined,\n link: this.resolveUrl(rdfImage.find('link').text(), resolveBase) || undefined,\n }\n : undefined,\n categories: this.parseCategories(\n channel\n .find('dc\\\\:subject')\n .map((_, s) => $(s).text())\n .get()\n ),\n },\n };\n }\n\n /**\n * Extract custom fields from an element using configured selectors.\n */\n private extractCustomFields($el: cheerio.Cheerio<Element>): Record<string, string> | undefined {\n if (Object.keys(this.customFields).length === 0) return undefined;\n\n const fields: Record<string, string> = {};\n for (const [key, selector] of Object.entries(this.customFields)) {\n const value = $el.find(selector).text().trim();\n if (value) {\n fields[key] = value;\n }\n }\n\n return Object.keys(fields).length > 0 ? fields : undefined;\n }\n\n /**\n * Parse date string to ISO 8601 format.\n * Returns undefined if parsing fails (never returns raw strings).\n */\n private parseDate(dateStr: string): string | undefined {\n if (!dateStr?.trim()) return undefined;\n\n try {\n const date = new Date(dateStr);\n // Check for invalid date\n if (Number.isNaN(date.getTime())) {\n return undefined;\n }\n return date.toISOString();\n } catch {\n return undefined;\n }\n }\n\n /**\n * Parse element text content, returning undefined if empty.\n */\n private parseText($el: cheerio.Cheerio<Element>): string | undefined {\n const text = $el.text().trim();\n return text || undefined;\n }\n\n /**\n * Parse content:encoded, stripping HTML tags from CDATA content.\n */\n private parseContentEncoded($el: cheerio.Cheerio<Element>): string | undefined {\n const raw = $el.text().trim();\n if (!raw) return undefined;\n return raw.replace(/<[^>]+>/g, '').trim() || undefined;\n }\n\n /**\n * Parse categories/tags, filtering out empty strings.\n */\n private parseCategories(categories: (string | undefined)[]): string[] {\n return categories.map((c) => c?.trim()).filter((c): c is string => !!c && c.length > 0);\n }\n\n /**\n * Resolve a URL against a base URL.\n * Only allows https scheme; all other schemes are rejected.\n */\n private resolveUrl(url: string, base?: string): string {\n if (!url?.trim()) return '';\n\n try {\n const resolved = base ? new URL(url, base) : new URL(url);\n return resolved.protocol === 'https:' ? resolved.href : '';\n } catch {\n return '';\n }\n }\n\n /**\n * Resolve link with fallback to id/guid if primary link is empty and id is a URL.\n * Only allows https scheme; all other schemes are rejected.\n */\n private resolveLink(primaryLink: string, fallbackId: string, base?: string): string {\n // Try primary link first\n if (primaryLink?.trim()) {\n return this.resolveUrl(primaryLink, base);\n }\n\n // Fallback to id if it looks like a URL\n if (fallbackId?.trim()) {\n try {\n const resolvedId = new URL(fallbackId);\n return resolvedId.protocol === 'https:' ? resolvedId.href : '';\n } catch {\n // Not a valid URL, try resolving against base\n return this.resolveUrl(fallbackId, base);\n }\n }\n\n return '';\n }\n\n /**\n * Parse enclosure element with URL resolution.\n */\n private parseEnclosure(\n $enc: cheerio.Cheerio<Element>,\n baseUrl?: string\n ): FeedEnclosure | undefined {\n const url = $enc.attr('url');\n if (!url) return undefined;\n\n return {\n url: this.resolveUrl(url, baseUrl),\n type: $enc.attr('type') || undefined,\n length: this.parseNumber($enc.attr('length')),\n };\n }\n\n /**\n * Parse RSS image element with URL resolution.\n */\n private parseImage(\n $img: cheerio.Cheerio<Element>,\n baseUrl?: string\n ): FeedMeta['image'] | undefined {\n const url = $img.find('url').text();\n if (!url) return undefined;\n\n return {\n url: this.resolveUrl(url, baseUrl),\n title: $img.find('title').text() || undefined,\n link: this.resolveUrl($img.find('link').text(), baseUrl) || undefined,\n };\n }\n\n /**\n * Parse Atom logo/icon element with URL resolution.\n */\n private parseAtomImage(\n $feed: cheerio.Cheerio<Element>,\n baseUrl?: string\n ): FeedMeta['image'] | undefined {\n const logo = $feed.find('> logo').text();\n const icon = $feed.find('> icon').text();\n const url = logo || icon;\n\n if (!url) return undefined;\n\n return {\n url: this.resolveUrl(url, baseUrl),\n };\n }\n\n /**\n * Parse a string to number, returning undefined if invalid.\n */\n private parseNumber(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const num = parseInt(value, 10);\n return Number.isNaN(num) ? undefined : num;\n }\n}\n"],"mappings":"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"}