feedscout 1.0.0 → 1.1.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 (50) hide show
  1. package/README.md +2 -1
  2. package/dist/blogrolls/index.cjs +1 -0
  3. package/dist/blogrolls/index.js +1 -0
  4. package/dist/common/discover/utils.cjs +12 -0
  5. package/dist/common/discover/utils.js +12 -0
  6. package/dist/common/locales.cjs +1 -0
  7. package/dist/common/locales.js +1 -0
  8. package/dist/common/types.d.cts +3 -1
  9. package/dist/common/types.d.ts +3 -1
  10. package/dist/common/uris/index.cjs +2 -0
  11. package/dist/common/uris/index.js +2 -0
  12. package/dist/common/uris/platform/index.cjs +12 -0
  13. package/dist/common/uris/platform/index.js +11 -0
  14. package/dist/common/uris/platform/types.d.cts +11 -0
  15. package/dist/common/uris/platform/types.d.ts +11 -0
  16. package/dist/common/utils.cjs +9 -0
  17. package/dist/common/utils.js +7 -1
  18. package/dist/feeds/defaults.cjs +25 -0
  19. package/dist/feeds/defaults.d.cts +3 -1
  20. package/dist/feeds/defaults.d.ts +3 -1
  21. package/dist/feeds/defaults.js +26 -1
  22. package/dist/feeds/index.cjs +1 -0
  23. package/dist/feeds/index.js +2 -1
  24. package/dist/feeds/platform/handlers/blogspot.cjs +15 -0
  25. package/dist/feeds/platform/handlers/blogspot.js +15 -0
  26. package/dist/feeds/platform/handlers/bluesky.cjs +18 -0
  27. package/dist/feeds/platform/handlers/bluesky.js +18 -0
  28. package/dist/feeds/platform/handlers/github.cjs +83 -0
  29. package/dist/feeds/platform/handlers/github.js +83 -0
  30. package/dist/feeds/platform/handlers/gitlab.cjs +19 -0
  31. package/dist/feeds/platform/handlers/gitlab.js +19 -0
  32. package/dist/feeds/platform/handlers/kickstarter.cjs +21 -0
  33. package/dist/feeds/platform/handlers/kickstarter.js +21 -0
  34. package/dist/feeds/platform/handlers/reddit.cjs +44 -0
  35. package/dist/feeds/platform/handlers/reddit.js +44 -0
  36. package/dist/feeds/platform/handlers/soundcloud.cjs +37 -0
  37. package/dist/feeds/platform/handlers/soundcloud.js +37 -0
  38. package/dist/feeds/platform/handlers/substack.cjs +15 -0
  39. package/dist/feeds/platform/handlers/substack.js +15 -0
  40. package/dist/feeds/platform/handlers/tumblr.cjs +15 -0
  41. package/dist/feeds/platform/handlers/tumblr.js +15 -0
  42. package/dist/feeds/platform/handlers/wordpress.cjs +19 -0
  43. package/dist/feeds/platform/handlers/wordpress.js +19 -0
  44. package/dist/feeds/platform/handlers/youtube.cjs +44 -0
  45. package/dist/feeds/platform/handlers/youtube.js +44 -0
  46. package/dist/feeds.cjs +1 -0
  47. package/dist/feeds.d.cts +2 -2
  48. package/dist/feeds.d.ts +2 -2
  49. package/dist/feeds.js +2 -2
  50. package/package.json +16 -15
package/README.md CHANGED
@@ -24,6 +24,7 @@ Finds feeds by scanning links and anchors in HTML content, parsing HTTP headers,
24
24
 
25
25
  ### Discovery Methods
26
26
 
27
+ - **Platform** — Generates feed URLs for YouTube, GitHub, WordPress, and other popular platforms using URL pattern matching.
27
28
  - **HTML** — Scans `<link>` elements with feed MIME types and `<a>` elements matching feed patterns or labels like "RSS", "Subscribe".
28
29
  - **Headers** — Parses HTTP `Link` headers for `rel="alternate"` with feed MIME types per RFC 8288.
29
30
  - **Guess** — Tests common paths (e.g. `/feed`, `/rss.xml`, `/atom.xml`) against the base URL as a fallback.
@@ -32,7 +33,7 @@ Finds feeds by scanning links and anchors in HTML content, parsing HTTP headers,
32
33
 
33
34
  - **Custom extractors** — Override the default parser to extract additional metadata from feeds and blogrolls.
34
35
  - **Configurable methods** — Enable/disable discovery methods or customize their options.
35
- - **Adapter system** — Use native fetch or integrate with Axios, Got, or Ky.
36
+ - **Adapter system** — Use native fetch or easily integrate with Axios, Got, or Ky.
36
37
  - **Concurrency control** — Limit parallel requests during validation.
37
38
  - **Progress tracking** — Monitor discovery progress with callbacks.
38
39
  - **Type-safe** — Full TypeScript support with exported types.
@@ -12,6 +12,7 @@ const discoverBlogrolls = async (input, options) => {
12
12
  extractFn: options.extractFn ?? require_extractors.defaultExtractor,
13
13
  normalizeUrlFn: options.normalizeUrlFn ?? require_utils.normalizeUrl
14
14
  }, {
15
+ platform: { handlers: [] },
15
16
  html: require_defaults.defaultHtmlOptions,
16
17
  headers: require_defaults.defaultHeadersOptions,
17
18
  guess: require_defaults.defaultGuessOptions
@@ -12,6 +12,7 @@ const discoverBlogrolls = async (input, options) => {
12
12
  extractFn: options.extractFn ?? defaultExtractor,
13
13
  normalizeUrlFn: options.normalizeUrlFn ?? normalizeUrl
14
14
  }, {
15
+ platform: { handlers: [] },
15
16
  html: defaultHtmlOptions,
16
17
  headers: defaultHeadersOptions,
17
18
  guess: defaultGuessOptions
@@ -13,6 +13,18 @@ const normalizeInput = async (input, fetchFn) => {
13
13
  const normalizeMethodsConfig = (input, methods, defaults) => {
14
14
  const methodsObj = Array.isArray(methods) ? Object.fromEntries(methods.map((method) => [method, true])) : methods;
15
15
  const methodsConfig = {};
16
+ if (methodsObj.platform) {
17
+ if (!input.url || input.url === "") throw new Error(require_locales.errors.platformMethodRequiresUrl);
18
+ const platformOptions = methodsObj.platform === true ? {} : methodsObj.platform;
19
+ methodsConfig.platform = {
20
+ html: input.content ?? "",
21
+ options: {
22
+ ...defaults.platform,
23
+ ...platformOptions,
24
+ baseUrl: input.url
25
+ }
26
+ };
27
+ }
16
28
  if (methodsObj.html) {
17
29
  if (input.content === void 0) throw new Error(require_locales.errors.htmlMethodRequiresContent);
18
30
  const htmlOptions = methodsObj.html === true ? {} : methodsObj.html;
@@ -13,6 +13,18 @@ const normalizeInput = async (input, fetchFn) => {
13
13
  const normalizeMethodsConfig = (input, methods, defaults) => {
14
14
  const methodsObj = Array.isArray(methods) ? Object.fromEntries(methods.map((method) => [method, true])) : methods;
15
15
  const methodsConfig = {};
16
+ if (methodsObj.platform) {
17
+ if (!input.url || input.url === "") throw new Error(errors.platformMethodRequiresUrl);
18
+ const platformOptions = methodsObj.platform === true ? {} : methodsObj.platform;
19
+ methodsConfig.platform = {
20
+ html: input.content ?? "",
21
+ options: {
22
+ ...defaults.platform,
23
+ ...platformOptions,
24
+ baseUrl: input.url
25
+ }
26
+ };
27
+ }
16
28
  if (methodsObj.html) {
17
29
  if (input.content === void 0) throw new Error(errors.htmlMethodRequiresContent);
18
30
  const htmlOptions = methodsObj.html === true ? {} : methodsObj.html;
@@ -1,6 +1,7 @@
1
1
 
2
2
  //#region src/common/locales.json
3
3
  var errors = {
4
+ "platformMethodRequiresUrl": "Platform method requires url to be provided in input",
4
5
  "htmlMethodRequiresContent": "HTML method requires content to be provided in input",
5
6
  "headersMethodRequiresHeaders": "Headers method requires headers to be provided in input",
6
7
  "guessMethodRequiresUrl": "Guess method requires url to be provided in input"
@@ -1,5 +1,6 @@
1
1
  //#region src/common/locales.json
2
2
  var errors = {
3
+ "platformMethodRequiresUrl": "Platform method requires url to be provided in input",
3
4
  "htmlMethodRequiresContent": "HTML method requires content to be provided in input",
4
5
  "headersMethodRequiresHeaders": "Headers method requires headers to be provided in input",
5
6
  "guessMethodRequiresUrl": "Guess method requires url to be provided in input"
@@ -1,6 +1,7 @@
1
1
  import { GuessMethodOptions } from "./uris/guess/types.cjs";
2
2
  import { HeadersMethodOptions } from "./uris/headers/types.cjs";
3
3
  import { HtmlMethodOptions } from "./uris/html/types.cjs";
4
+ import { PlatformMethodOptions } from "./uris/platform/types.cjs";
4
5
 
5
6
  //#region src/common/types.d.ts
6
7
  type LinkSelector = {
@@ -46,7 +47,8 @@ type DiscoverInputObject = {
46
47
  headers?: Headers;
47
48
  };
48
49
  type DiscoverInput = string | DiscoverInputObject;
49
- type DiscoverMethodsConfig = Array<'html' | 'headers' | 'guess'> | {
50
+ type DiscoverMethodsConfig = Array<'platform' | 'html' | 'headers' | 'guess'> | {
51
+ platform?: true | Partial<PlatformMethodOptions>;
50
52
  html?: true | Partial<Omit<HtmlMethodOptions, 'baseUrl'>>;
51
53
  headers?: true | Partial<Omit<HeadersMethodOptions, 'baseUrl'>>;
52
54
  guess?: true | Partial<Omit<GuessMethodOptions, 'baseUrl'>>;
@@ -1,6 +1,7 @@
1
1
  import { GuessMethodOptions } from "./uris/guess/types.js";
2
2
  import { HeadersMethodOptions } from "./uris/headers/types.js";
3
3
  import { HtmlMethodOptions } from "./uris/html/types.js";
4
+ import { PlatformMethodOptions } from "./uris/platform/types.js";
4
5
 
5
6
  //#region src/common/types.d.ts
6
7
  type LinkSelector = {
@@ -46,7 +47,8 @@ type DiscoverInputObject = {
46
47
  headers?: Headers;
47
48
  };
48
49
  type DiscoverInput = string | DiscoverInputObject;
49
- type DiscoverMethodsConfig = Array<'html' | 'headers' | 'guess'> | {
50
+ type DiscoverMethodsConfig = Array<'platform' | 'html' | 'headers' | 'guess'> | {
51
+ platform?: true | Partial<PlatformMethodOptions>;
50
52
  html?: true | Partial<Omit<HtmlMethodOptions, 'baseUrl'>>;
51
53
  headers?: true | Partial<Omit<HeadersMethodOptions, 'baseUrl'>>;
52
54
  guess?: true | Partial<Omit<GuessMethodOptions, 'baseUrl'>>;
@@ -1,10 +1,12 @@
1
1
  const require_index = require('./guess/index.cjs');
2
2
  const require_index$1 = require('./headers/index.cjs');
3
3
  const require_index$2 = require('./html/index.cjs');
4
+ const require_index$3 = require('./platform/index.cjs');
4
5
 
5
6
  //#region src/common/uris/index.ts
6
7
  const discoverUris = (config) => {
7
8
  const uris = /* @__PURE__ */ new Set();
9
+ if (config.platform) for (const uri of require_index$3.discoverUrisFromPlatform(config.platform.html, config.platform.options)) uris.add(uri);
8
10
  if (config.html) for (const uri of require_index$2.discoverUrisFromHtml(config.html.html, config.html.options)) uris.add(uri);
9
11
  if (config.headers) for (const uri of require_index$1.discoverUrisFromHeaders(config.headers.headers, config.headers.options)) uris.add(uri);
10
12
  if (config.guess) for (const uri of require_index.discoverUrisFromGuess(config.guess.options)) uris.add(uri);
@@ -1,10 +1,12 @@
1
1
  import { discoverUrisFromGuess } from "./guess/index.js";
2
2
  import { discoverUrisFromHeaders } from "./headers/index.js";
3
3
  import { discoverUrisFromHtml } from "./html/index.js";
4
+ import { discoverUrisFromPlatform } from "./platform/index.js";
4
5
 
5
6
  //#region src/common/uris/index.ts
6
7
  const discoverUris = (config) => {
7
8
  const uris = /* @__PURE__ */ new Set();
9
+ if (config.platform) for (const uri of discoverUrisFromPlatform(config.platform.html, config.platform.options)) uris.add(uri);
8
10
  if (config.html) for (const uri of discoverUrisFromHtml(config.html.html, config.html.options)) uris.add(uri);
9
11
  if (config.headers) for (const uri of discoverUrisFromHeaders(config.headers.headers, config.headers.options)) uris.add(uri);
10
12
  if (config.guess) for (const uri of discoverUrisFromGuess(config.guess.options)) uris.add(uri);
@@ -0,0 +1,12 @@
1
+
2
+ //#region src/common/uris/platform/index.ts
3
+ const discoverUrisFromPlatform = (html, options) => {
4
+ const { baseUrl, handlers } = options;
5
+ for (const handler of handlers) try {
6
+ if (handler.match(baseUrl)) return handler.resolve(baseUrl, html);
7
+ } catch {}
8
+ return [];
9
+ };
10
+
11
+ //#endregion
12
+ exports.discoverUrisFromPlatform = discoverUrisFromPlatform;
@@ -0,0 +1,11 @@
1
+ //#region src/common/uris/platform/index.ts
2
+ const discoverUrisFromPlatform = (html, options) => {
3
+ const { baseUrl, handlers } = options;
4
+ for (const handler of handlers) try {
5
+ if (handler.match(baseUrl)) return handler.resolve(baseUrl, html);
6
+ } catch {}
7
+ return [];
8
+ };
9
+
10
+ //#endregion
11
+ export { discoverUrisFromPlatform };
@@ -0,0 +1,11 @@
1
+ //#region src/common/uris/platform/types.d.ts
2
+ type PlatformHandler = {
3
+ match: (url: string) => boolean;
4
+ resolve: (url: string, content?: string) => Array<string>;
5
+ };
6
+ type PlatformMethodOptions = {
7
+ baseUrl: string;
8
+ handlers: Array<PlatformHandler>;
9
+ };
10
+ //#endregion
11
+ export { PlatformMethodOptions };
@@ -0,0 +1,11 @@
1
+ //#region src/common/uris/platform/types.d.ts
2
+ type PlatformHandler = {
3
+ match: (url: string) => boolean;
4
+ resolve: (url: string, content?: string) => Array<string>;
5
+ };
6
+ type PlatformMethodOptions = {
7
+ baseUrl: string;
8
+ handlers: Array<PlatformHandler>;
9
+ };
10
+ //#endregion
11
+ export { PlatformMethodOptions };
@@ -3,6 +3,12 @@
3
3
  const normalizeMimeType = (type) => {
4
4
  return type.split(";")[0].trim().toLowerCase();
5
5
  };
6
+ const isSubdomainOf = (url, domain) => {
7
+ return new URL(url).hostname.toLowerCase().endsWith(`.${domain}`);
8
+ };
9
+ const isHostOf = (url, hosts) => {
10
+ return isAnyOf(new URL(url).hostname, hosts);
11
+ };
6
12
  const includesAnyOf = (value, patterns, parser) => {
7
13
  const parsedValue = parser ? parser(value) : value?.toLowerCase();
8
14
  return patterns.map((pattern) => pattern.toLowerCase()).some((pattern) => parsedValue?.includes(pattern));
@@ -52,6 +58,9 @@ const processConcurrently = async (items, processFn, options) => {
52
58
  //#endregion
53
59
  exports.endsWithAnyOf = endsWithAnyOf;
54
60
  exports.includesAnyOf = includesAnyOf;
61
+ exports.isAnyOf = isAnyOf;
62
+ exports.isHostOf = isHostOf;
63
+ exports.isSubdomainOf = isSubdomainOf;
55
64
  exports.matchesAnyOfLinkSelectors = matchesAnyOfLinkSelectors;
56
65
  exports.normalizeUrl = normalizeUrl;
57
66
  exports.processConcurrently = processConcurrently;
@@ -2,6 +2,12 @@
2
2
  const normalizeMimeType = (type) => {
3
3
  return type.split(";")[0].trim().toLowerCase();
4
4
  };
5
+ const isSubdomainOf = (url, domain) => {
6
+ return new URL(url).hostname.toLowerCase().endsWith(`.${domain}`);
7
+ };
8
+ const isHostOf = (url, hosts) => {
9
+ return isAnyOf(new URL(url).hostname, hosts);
10
+ };
5
11
  const includesAnyOf = (value, patterns, parser) => {
6
12
  const parsedValue = parser ? parser(value) : value?.toLowerCase();
7
13
  return patterns.map((pattern) => pattern.toLowerCase()).some((pattern) => parsedValue?.includes(pattern));
@@ -49,4 +55,4 @@ const processConcurrently = async (items, processFn, options) => {
49
55
  };
50
56
 
51
57
  //#endregion
52
- export { endsWithAnyOf, includesAnyOf, matchesAnyOfLinkSelectors, normalizeUrl, processConcurrently };
58
+ export { endsWithAnyOf, includesAnyOf, isAnyOf, isHostOf, isSubdomainOf, matchesAnyOfLinkSelectors, normalizeUrl, processConcurrently };
@@ -1,3 +1,14 @@
1
+ const require_blogspot = require('./platform/handlers/blogspot.cjs');
2
+ const require_bluesky = require('./platform/handlers/bluesky.cjs');
3
+ const require_github = require('./platform/handlers/github.cjs');
4
+ const require_gitlab = require('./platform/handlers/gitlab.cjs');
5
+ const require_kickstarter = require('./platform/handlers/kickstarter.cjs');
6
+ const require_reddit = require('./platform/handlers/reddit.cjs');
7
+ const require_soundcloud = require('./platform/handlers/soundcloud.cjs');
8
+ const require_substack = require('./platform/handlers/substack.cjs');
9
+ const require_tumblr = require('./platform/handlers/tumblr.cjs');
10
+ const require_wordpress = require('./platform/handlers/wordpress.cjs');
11
+ const require_youtube = require('./platform/handlers/youtube.cjs');
1
12
 
2
13
  //#region src/feeds/defaults.ts
3
14
  const mimeTypes = [
@@ -75,12 +86,26 @@ const defaultHtmlOptions = {
75
86
  };
76
87
  const defaultHeadersOptions = { linkSelectors };
77
88
  const defaultGuessOptions = { uris: urisBalanced };
89
+ const defaultPlatformOptions = { handlers: [
90
+ require_blogspot.blogspotHandler,
91
+ require_bluesky.blueskyHandler,
92
+ require_github.githubHandler,
93
+ require_gitlab.gitlabHandler,
94
+ require_kickstarter.kickstarterHandler,
95
+ require_reddit.redditHandler,
96
+ require_soundcloud.soundcloudHandler,
97
+ require_substack.substackHandler,
98
+ require_tumblr.tumblrHandler,
99
+ require_wordpress.wordpressHandler,
100
+ require_youtube.youtubeHandler
101
+ ] };
78
102
 
79
103
  //#endregion
80
104
  exports.anchorLabels = anchorLabels;
81
105
  exports.defaultGuessOptions = defaultGuessOptions;
82
106
  exports.defaultHeadersOptions = defaultHeadersOptions;
83
107
  exports.defaultHtmlOptions = defaultHtmlOptions;
108
+ exports.defaultPlatformOptions = defaultPlatformOptions;
84
109
  exports.ignoredUris = ignoredUris;
85
110
  exports.linkSelectors = linkSelectors;
86
111
  exports.mimeTypes = mimeTypes;
@@ -1,6 +1,7 @@
1
1
  import { GuessMethodOptions } from "../common/uris/guess/types.cjs";
2
2
  import { HeadersMethodOptions } from "../common/uris/headers/types.cjs";
3
3
  import { HtmlMethodOptions } from "../common/uris/html/types.cjs";
4
+ import { PlatformMethodOptions } from "../common/uris/platform/types.cjs";
4
5
  import { LinkSelector } from "../common/types.cjs";
5
6
 
6
7
  //#region src/feeds/defaults.d.ts
@@ -14,5 +15,6 @@ declare const linkSelectors: Array<LinkSelector>;
14
15
  declare const defaultHtmlOptions: Omit<HtmlMethodOptions, 'baseUrl'>;
15
16
  declare const defaultHeadersOptions: Omit<HeadersMethodOptions, 'baseUrl'>;
16
17
  declare const defaultGuessOptions: Omit<GuessMethodOptions, 'baseUrl'>;
18
+ declare const defaultPlatformOptions: Omit<PlatformMethodOptions, 'baseUrl'>;
17
19
  //#endregion
18
- export { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
20
+ export { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
@@ -1,6 +1,7 @@
1
1
  import { GuessMethodOptions } from "../common/uris/guess/types.js";
2
2
  import { HeadersMethodOptions } from "../common/uris/headers/types.js";
3
3
  import { HtmlMethodOptions } from "../common/uris/html/types.js";
4
+ import { PlatformMethodOptions } from "../common/uris/platform/types.js";
4
5
  import { LinkSelector } from "../common/types.js";
5
6
 
6
7
  //#region src/feeds/defaults.d.ts
@@ -14,5 +15,6 @@ declare const linkSelectors: Array<LinkSelector>;
14
15
  declare const defaultHtmlOptions: Omit<HtmlMethodOptions, 'baseUrl'>;
15
16
  declare const defaultHeadersOptions: Omit<HeadersMethodOptions, 'baseUrl'>;
16
17
  declare const defaultGuessOptions: Omit<GuessMethodOptions, 'baseUrl'>;
18
+ declare const defaultPlatformOptions: Omit<PlatformMethodOptions, 'baseUrl'>;
17
19
  //#endregion
18
- export { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
20
+ export { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
@@ -1,3 +1,15 @@
1
+ import { blogspotHandler } from "./platform/handlers/blogspot.js";
2
+ import { blueskyHandler } from "./platform/handlers/bluesky.js";
3
+ import { githubHandler } from "./platform/handlers/github.js";
4
+ import { gitlabHandler } from "./platform/handlers/gitlab.js";
5
+ import { kickstarterHandler } from "./platform/handlers/kickstarter.js";
6
+ import { redditHandler } from "./platform/handlers/reddit.js";
7
+ import { soundcloudHandler } from "./platform/handlers/soundcloud.js";
8
+ import { substackHandler } from "./platform/handlers/substack.js";
9
+ import { tumblrHandler } from "./platform/handlers/tumblr.js";
10
+ import { wordpressHandler } from "./platform/handlers/wordpress.js";
11
+ import { youtubeHandler } from "./platform/handlers/youtube.js";
12
+
1
13
  //#region src/feeds/defaults.ts
2
14
  const mimeTypes = [
3
15
  "application/rss+xml",
@@ -74,6 +86,19 @@ const defaultHtmlOptions = {
74
86
  };
75
87
  const defaultHeadersOptions = { linkSelectors };
76
88
  const defaultGuessOptions = { uris: urisBalanced };
89
+ const defaultPlatformOptions = { handlers: [
90
+ blogspotHandler,
91
+ blueskyHandler,
92
+ githubHandler,
93
+ gitlabHandler,
94
+ kickstarterHandler,
95
+ redditHandler,
96
+ soundcloudHandler,
97
+ substackHandler,
98
+ tumblrHandler,
99
+ wordpressHandler,
100
+ youtubeHandler
101
+ ] };
77
102
 
78
103
  //#endregion
79
- export { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
104
+ export { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
@@ -12,6 +12,7 @@ const discoverFeeds = async (input, options) => {
12
12
  extractFn: options.extractFn ?? require_extractors.defaultExtractor,
13
13
  normalizeUrlFn: options.normalizeUrlFn ?? require_utils.normalizeUrl
14
14
  }, {
15
+ platform: require_defaults.defaultPlatformOptions,
15
16
  html: require_defaults.defaultHtmlOptions,
16
17
  headers: require_defaults.defaultHeadersOptions,
17
18
  guess: require_defaults.defaultGuessOptions
@@ -1,7 +1,7 @@
1
1
  import { createNativeFetchAdapter } from "../common/discover/adapters.js";
2
2
  import { normalizeUrl } from "../common/utils.js";
3
3
  import { discover } from "../common/discover/index.js";
4
- import { defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions } from "./defaults.js";
4
+ import { defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions } from "./defaults.js";
5
5
  import { defaultExtractor } from "./extractors.js";
6
6
 
7
7
  //#region src/feeds/index.ts
@@ -12,6 +12,7 @@ const discoverFeeds = async (input, options) => {
12
12
  extractFn: options.extractFn ?? defaultExtractor,
13
13
  normalizeUrlFn: options.normalizeUrlFn ?? normalizeUrl
14
14
  }, {
15
+ platform: defaultPlatformOptions,
15
16
  html: defaultHtmlOptions,
16
17
  headers: defaultHeadersOptions,
17
18
  guess: defaultGuessOptions
@@ -0,0 +1,15 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/blogspot.ts
4
+ const blogspotHandler = {
5
+ match: (url) => {
6
+ return require_utils.isSubdomainOf(url, "blogspot.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [`${origin}/feeds/posts/default`, `${origin}/feeds/posts/default?alt=rss`];
11
+ }
12
+ };
13
+
14
+ //#endregion
15
+ exports.blogspotHandler = blogspotHandler;
@@ -0,0 +1,15 @@
1
+ import { isSubdomainOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/blogspot.ts
4
+ const blogspotHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "blogspot.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [`${origin}/feeds/posts/default`, `${origin}/feeds/posts/default?alt=rss`];
11
+ }
12
+ };
13
+
14
+ //#endregion
15
+ export { blogspotHandler };
@@ -0,0 +1,18 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/bluesky.ts
4
+ const hosts = ["bsky.app"];
5
+ const blueskyHandler = {
6
+ match: (url) => {
7
+ return require_utils.isHostOf(url, hosts);
8
+ },
9
+ resolve: (url) => {
10
+ const { pathname } = new URL(url);
11
+ const handle = pathname.match(/^\/profile\/([^/]+)/)?.[1];
12
+ if (!handle) return [];
13
+ return [`https://bsky.app/profile/${handle}/rss`];
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ exports.blueskyHandler = blueskyHandler;
@@ -0,0 +1,18 @@
1
+ import { isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/bluesky.ts
4
+ const hosts = ["bsky.app"];
5
+ const blueskyHandler = {
6
+ match: (url) => {
7
+ return isHostOf(url, hosts);
8
+ },
9
+ resolve: (url) => {
10
+ const { pathname } = new URL(url);
11
+ const handle = pathname.match(/^\/profile\/([^/]+)/)?.[1];
12
+ if (!handle) return [];
13
+ return [`https://bsky.app/profile/${handle}/rss`];
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ export { blueskyHandler };
@@ -0,0 +1,83 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/github.ts
4
+ const hosts = ["github.com", "www.github.com"];
5
+ const excludedPaths = [
6
+ "about",
7
+ "account",
8
+ "apps",
9
+ "blog",
10
+ "careers",
11
+ "codespaces",
12
+ "collections",
13
+ "contact",
14
+ "copilot",
15
+ "customer-stories",
16
+ "dashboard",
17
+ "education",
18
+ "enterprise",
19
+ "events",
20
+ "explore",
21
+ "features",
22
+ "feed",
23
+ "home",
24
+ "issues",
25
+ "join",
26
+ "login",
27
+ "marketplace",
28
+ "new",
29
+ "nonprofit",
30
+ "notifications",
31
+ "organizations",
32
+ "orgs",
33
+ "password_reset",
34
+ "premium-support",
35
+ "pricing",
36
+ "pulls",
37
+ "readme",
38
+ "resources",
39
+ "search",
40
+ "security",
41
+ "sessions",
42
+ "settings",
43
+ "site",
44
+ "sponsors",
45
+ "stars",
46
+ "team",
47
+ "topics",
48
+ "trending",
49
+ "watching"
50
+ ];
51
+ const githubHandler = {
52
+ match: (url) => {
53
+ return require_utils.isHostOf(url, hosts);
54
+ },
55
+ resolve: (url) => {
56
+ const { pathname } = new URL(url);
57
+ const uris = [];
58
+ const userMatch = pathname.match(/^\/([^/]+)\/?$/);
59
+ if (userMatch?.[1] && !require_utils.isAnyOf(userMatch[1], excludedPaths)) {
60
+ const user = userMatch[1];
61
+ uris.push(`https://github.com/${user}.atom`);
62
+ return uris;
63
+ }
64
+ const repoMatch = pathname.match(/^\/([^/]+)\/([^/]+)/);
65
+ const owner = repoMatch?.[1];
66
+ const repo = repoMatch?.[2];
67
+ if (!owner || !repo || require_utils.isAnyOf(owner, excludedPaths)) return [];
68
+ uris.push(`https://github.com/${owner}/${repo}/releases.atom`);
69
+ uris.push(`https://github.com/${owner}/${repo}/commits.atom`);
70
+ uris.push(`https://github.com/${owner}/${repo}/tags.atom`);
71
+ if (pathname.includes("/wiki")) uris.push(`https://github.com/${owner}/${repo}/wiki.atom`);
72
+ if (pathname.includes("/discussions")) uris.push(`https://github.com/${owner}/${repo}/discussions.atom`);
73
+ const branchMatch = pathname.match(/^\/[^/]+\/[^/]+\/tree\/([^/]+)/);
74
+ if (branchMatch?.[1]) {
75
+ const branch = branchMatch[1];
76
+ uris.push(`https://github.com/${owner}/${repo}/commits/${branch}.atom`);
77
+ }
78
+ return uris;
79
+ }
80
+ };
81
+
82
+ //#endregion
83
+ exports.githubHandler = githubHandler;
@@ -0,0 +1,83 @@
1
+ import { isAnyOf, isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/github.ts
4
+ const hosts = ["github.com", "www.github.com"];
5
+ const excludedPaths = [
6
+ "about",
7
+ "account",
8
+ "apps",
9
+ "blog",
10
+ "careers",
11
+ "codespaces",
12
+ "collections",
13
+ "contact",
14
+ "copilot",
15
+ "customer-stories",
16
+ "dashboard",
17
+ "education",
18
+ "enterprise",
19
+ "events",
20
+ "explore",
21
+ "features",
22
+ "feed",
23
+ "home",
24
+ "issues",
25
+ "join",
26
+ "login",
27
+ "marketplace",
28
+ "new",
29
+ "nonprofit",
30
+ "notifications",
31
+ "organizations",
32
+ "orgs",
33
+ "password_reset",
34
+ "premium-support",
35
+ "pricing",
36
+ "pulls",
37
+ "readme",
38
+ "resources",
39
+ "search",
40
+ "security",
41
+ "sessions",
42
+ "settings",
43
+ "site",
44
+ "sponsors",
45
+ "stars",
46
+ "team",
47
+ "topics",
48
+ "trending",
49
+ "watching"
50
+ ];
51
+ const githubHandler = {
52
+ match: (url) => {
53
+ return isHostOf(url, hosts);
54
+ },
55
+ resolve: (url) => {
56
+ const { pathname } = new URL(url);
57
+ const uris = [];
58
+ const userMatch = pathname.match(/^\/([^/]+)\/?$/);
59
+ if (userMatch?.[1] && !isAnyOf(userMatch[1], excludedPaths)) {
60
+ const user = userMatch[1];
61
+ uris.push(`https://github.com/${user}.atom`);
62
+ return uris;
63
+ }
64
+ const repoMatch = pathname.match(/^\/([^/]+)\/([^/]+)/);
65
+ const owner = repoMatch?.[1];
66
+ const repo = repoMatch?.[2];
67
+ if (!owner || !repo || isAnyOf(owner, excludedPaths)) return [];
68
+ uris.push(`https://github.com/${owner}/${repo}/releases.atom`);
69
+ uris.push(`https://github.com/${owner}/${repo}/commits.atom`);
70
+ uris.push(`https://github.com/${owner}/${repo}/tags.atom`);
71
+ if (pathname.includes("/wiki")) uris.push(`https://github.com/${owner}/${repo}/wiki.atom`);
72
+ if (pathname.includes("/discussions")) uris.push(`https://github.com/${owner}/${repo}/discussions.atom`);
73
+ const branchMatch = pathname.match(/^\/[^/]+\/[^/]+\/tree\/([^/]+)/);
74
+ if (branchMatch?.[1]) {
75
+ const branch = branchMatch[1];
76
+ uris.push(`https://github.com/${owner}/${repo}/commits/${branch}.atom`);
77
+ }
78
+ return uris;
79
+ }
80
+ };
81
+
82
+ //#endregion
83
+ export { githubHandler };
@@ -0,0 +1,19 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/gitlab.ts
4
+ const hosts = ["gitlab.com", "www.gitlab.com"];
5
+ const gitlabHandler = {
6
+ match: (url) => {
7
+ return require_utils.isHostOf(url, hosts);
8
+ },
9
+ resolve: (url) => {
10
+ const { origin, pathname } = new URL(url);
11
+ const pathSegments = pathname.split("/").filter(Boolean);
12
+ if (pathSegments.length === 1) return [`${origin}/${pathSegments[0]}.atom`];
13
+ if (pathSegments.length >= 2) return [`${origin}/${pathSegments[0]}/${pathSegments[1]}.atom`];
14
+ return [];
15
+ }
16
+ };
17
+
18
+ //#endregion
19
+ exports.gitlabHandler = gitlabHandler;
@@ -0,0 +1,19 @@
1
+ import { isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/gitlab.ts
4
+ const hosts = ["gitlab.com", "www.gitlab.com"];
5
+ const gitlabHandler = {
6
+ match: (url) => {
7
+ return isHostOf(url, hosts);
8
+ },
9
+ resolve: (url) => {
10
+ const { origin, pathname } = new URL(url);
11
+ const pathSegments = pathname.split("/").filter(Boolean);
12
+ if (pathSegments.length === 1) return [`${origin}/${pathSegments[0]}.atom`];
13
+ if (pathSegments.length >= 2) return [`${origin}/${pathSegments[0]}/${pathSegments[1]}.atom`];
14
+ return [];
15
+ }
16
+ };
17
+
18
+ //#endregion
19
+ export { gitlabHandler };
@@ -0,0 +1,21 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/kickstarter.ts
4
+ const hosts = ["kickstarter.com", "www.kickstarter.com"];
5
+ const kickstarterHandler = {
6
+ match: (url) => {
7
+ if (!require_utils.isHostOf(url, hosts)) return false;
8
+ const { pathname } = new URL(url);
9
+ const pathSegments = pathname.split("/").filter(Boolean);
10
+ return pathSegments.length >= 3 && pathSegments[0] === "projects";
11
+ },
12
+ resolve: (url) => {
13
+ const { origin, pathname } = new URL(url);
14
+ const pathSegments = pathname.split("/").filter(Boolean);
15
+ if (pathSegments.length >= 3 && pathSegments[0] === "projects") return [`${origin}/projects/${pathSegments[1]}/${pathSegments[2]}/posts.atom`];
16
+ return [];
17
+ }
18
+ };
19
+
20
+ //#endregion
21
+ exports.kickstarterHandler = kickstarterHandler;
@@ -0,0 +1,21 @@
1
+ import { isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/kickstarter.ts
4
+ const hosts = ["kickstarter.com", "www.kickstarter.com"];
5
+ const kickstarterHandler = {
6
+ match: (url) => {
7
+ if (!isHostOf(url, hosts)) return false;
8
+ const { pathname } = new URL(url);
9
+ const pathSegments = pathname.split("/").filter(Boolean);
10
+ return pathSegments.length >= 3 && pathSegments[0] === "projects";
11
+ },
12
+ resolve: (url) => {
13
+ const { origin, pathname } = new URL(url);
14
+ const pathSegments = pathname.split("/").filter(Boolean);
15
+ if (pathSegments.length >= 3 && pathSegments[0] === "projects") return [`${origin}/projects/${pathSegments[1]}/${pathSegments[2]}/posts.atom`];
16
+ return [];
17
+ }
18
+ };
19
+
20
+ //#endregion
21
+ export { kickstarterHandler };
@@ -0,0 +1,44 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/reddit.ts
4
+ const hosts = [
5
+ "reddit.com",
6
+ "www.reddit.com",
7
+ "old.reddit.com",
8
+ "new.reddit.com"
9
+ ];
10
+ const sortOptions = [
11
+ "hot",
12
+ "new",
13
+ "rising",
14
+ "controversial",
15
+ "top"
16
+ ];
17
+ const redditHandler = {
18
+ match: (url) => {
19
+ return require_utils.isHostOf(url, hosts);
20
+ },
21
+ resolve: (url) => {
22
+ const { pathname } = new URL(url);
23
+ const commentsMatch = pathname.match(/^\/r\/([^/]+)\/comments\/([^/]+)/);
24
+ if (commentsMatch?.[1] && commentsMatch?.[2]) return [`https://www.reddit.com/r/${commentsMatch[1]}/comments/${commentsMatch[2]}/.rss`];
25
+ const subredditMatch = pathname.match(/^\/r\/([^/]+)(?:\/([^/]+))?/);
26
+ if (subredditMatch?.[1]) {
27
+ const subreddit = subredditMatch[1];
28
+ const sort = subredditMatch[2];
29
+ const uris = [];
30
+ if (sort && require_utils.isAnyOf(sort, sortOptions)) uris.push(`https://www.reddit.com/r/${subreddit}/${sort}/.rss`);
31
+ else uris.push(`https://www.reddit.com/r/${subreddit}/.rss`);
32
+ uris.push(`https://www.reddit.com/r/${subreddit}/comments/.rss`);
33
+ return uris;
34
+ }
35
+ const userMatch = pathname.match(/^\/(u|user)\/([^/]+)/);
36
+ if (userMatch?.[2]) return [`https://www.reddit.com/user/${userMatch[2]}/.rss`];
37
+ const domainMatch = pathname.match(/^\/domain\/([^/]+)/);
38
+ if (domainMatch?.[1]) return [`https://www.reddit.com/domain/${domainMatch[1]}/.rss`];
39
+ return [];
40
+ }
41
+ };
42
+
43
+ //#endregion
44
+ exports.redditHandler = redditHandler;
@@ -0,0 +1,44 @@
1
+ import { isAnyOf, isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/reddit.ts
4
+ const hosts = [
5
+ "reddit.com",
6
+ "www.reddit.com",
7
+ "old.reddit.com",
8
+ "new.reddit.com"
9
+ ];
10
+ const sortOptions = [
11
+ "hot",
12
+ "new",
13
+ "rising",
14
+ "controversial",
15
+ "top"
16
+ ];
17
+ const redditHandler = {
18
+ match: (url) => {
19
+ return isHostOf(url, hosts);
20
+ },
21
+ resolve: (url) => {
22
+ const { pathname } = new URL(url);
23
+ const commentsMatch = pathname.match(/^\/r\/([^/]+)\/comments\/([^/]+)/);
24
+ if (commentsMatch?.[1] && commentsMatch?.[2]) return [`https://www.reddit.com/r/${commentsMatch[1]}/comments/${commentsMatch[2]}/.rss`];
25
+ const subredditMatch = pathname.match(/^\/r\/([^/]+)(?:\/([^/]+))?/);
26
+ if (subredditMatch?.[1]) {
27
+ const subreddit = subredditMatch[1];
28
+ const sort = subredditMatch[2];
29
+ const uris = [];
30
+ if (sort && isAnyOf(sort, sortOptions)) uris.push(`https://www.reddit.com/r/${subreddit}/${sort}/.rss`);
31
+ else uris.push(`https://www.reddit.com/r/${subreddit}/.rss`);
32
+ uris.push(`https://www.reddit.com/r/${subreddit}/comments/.rss`);
33
+ return uris;
34
+ }
35
+ const userMatch = pathname.match(/^\/(u|user)\/([^/]+)/);
36
+ if (userMatch?.[2]) return [`https://www.reddit.com/user/${userMatch[2]}/.rss`];
37
+ const domainMatch = pathname.match(/^\/domain\/([^/]+)/);
38
+ if (domainMatch?.[1]) return [`https://www.reddit.com/domain/${domainMatch[1]}/.rss`];
39
+ return [];
40
+ }
41
+ };
42
+
43
+ //#endregion
44
+ export { redditHandler };
@@ -0,0 +1,37 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/soundcloud.ts
4
+ const hosts = [
5
+ "soundcloud.com",
6
+ "www.soundcloud.com",
7
+ "m.soundcloud.com"
8
+ ];
9
+ const excludedPaths = [
10
+ "discover",
11
+ "stream",
12
+ "search",
13
+ "upload",
14
+ "you",
15
+ "settings",
16
+ "messages"
17
+ ];
18
+ const extractUserIdFromContent = (content) => {
19
+ return content.match(/soundcloud:\/\/users:(\d+)/)?.[1];
20
+ };
21
+ const soundcloudHandler = {
22
+ match: (url) => {
23
+ if (!require_utils.isHostOf(url, hosts)) return false;
24
+ const { pathname } = new URL(url);
25
+ const pathSegments = pathname.split("/").filter(Boolean);
26
+ return pathSegments.length >= 1 && !excludedPaths.includes(pathSegments[0]);
27
+ },
28
+ resolve: (_url, content) => {
29
+ if (!content) return [];
30
+ const userId = extractUserIdFromContent(content);
31
+ if (!userId) return [];
32
+ return [`https://feeds.soundcloud.com/users/soundcloud:users:${userId}/sounds.rss`];
33
+ }
34
+ };
35
+
36
+ //#endregion
37
+ exports.soundcloudHandler = soundcloudHandler;
@@ -0,0 +1,37 @@
1
+ import { isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/soundcloud.ts
4
+ const hosts = [
5
+ "soundcloud.com",
6
+ "www.soundcloud.com",
7
+ "m.soundcloud.com"
8
+ ];
9
+ const excludedPaths = [
10
+ "discover",
11
+ "stream",
12
+ "search",
13
+ "upload",
14
+ "you",
15
+ "settings",
16
+ "messages"
17
+ ];
18
+ const extractUserIdFromContent = (content) => {
19
+ return content.match(/soundcloud:\/\/users:(\d+)/)?.[1];
20
+ };
21
+ const soundcloudHandler = {
22
+ match: (url) => {
23
+ if (!isHostOf(url, hosts)) return false;
24
+ const { pathname } = new URL(url);
25
+ const pathSegments = pathname.split("/").filter(Boolean);
26
+ return pathSegments.length >= 1 && !excludedPaths.includes(pathSegments[0]);
27
+ },
28
+ resolve: (_url, content) => {
29
+ if (!content) return [];
30
+ const userId = extractUserIdFromContent(content);
31
+ if (!userId) return [];
32
+ return [`https://feeds.soundcloud.com/users/soundcloud:users:${userId}/sounds.rss`];
33
+ }
34
+ };
35
+
36
+ //#endregion
37
+ export { soundcloudHandler };
@@ -0,0 +1,15 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/substack.ts
4
+ const substackHandler = {
5
+ match: (url) => {
6
+ return require_utils.isSubdomainOf(url, "substack.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [`${origin}/feed`];
11
+ }
12
+ };
13
+
14
+ //#endregion
15
+ exports.substackHandler = substackHandler;
@@ -0,0 +1,15 @@
1
+ import { isSubdomainOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/substack.ts
4
+ const substackHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "substack.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [`${origin}/feed`];
11
+ }
12
+ };
13
+
14
+ //#endregion
15
+ export { substackHandler };
@@ -0,0 +1,15 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/tumblr.ts
4
+ const tumblrHandler = {
5
+ match: (url) => {
6
+ return require_utils.isSubdomainOf(url, "tumblr.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [`${origin}/rss`];
11
+ }
12
+ };
13
+
14
+ //#endregion
15
+ exports.tumblrHandler = tumblrHandler;
@@ -0,0 +1,15 @@
1
+ import { isSubdomainOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/tumblr.ts
4
+ const tumblrHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "tumblr.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [`${origin}/rss`];
11
+ }
12
+ };
13
+
14
+ //#endregion
15
+ export { tumblrHandler };
@@ -0,0 +1,19 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/wordpress.ts
4
+ const wordpressHandler = {
5
+ match: (url) => {
6
+ return require_utils.isSubdomainOf(url, "wordpress.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [
11
+ `${origin}/feed/`,
12
+ `${origin}/feed/atom/`,
13
+ `${origin}/comments/feed/`
14
+ ];
15
+ }
16
+ };
17
+
18
+ //#endregion
19
+ exports.wordpressHandler = wordpressHandler;
@@ -0,0 +1,19 @@
1
+ import { isSubdomainOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/wordpress.ts
4
+ const wordpressHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "wordpress.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin } = new URL(url);
10
+ return [
11
+ `${origin}/feed/`,
12
+ `${origin}/feed/atom/`,
13
+ `${origin}/comments/feed/`
14
+ ];
15
+ }
16
+ };
17
+
18
+ //#endregion
19
+ export { wordpressHandler };
@@ -0,0 +1,44 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/youtube.ts
4
+ const hosts = [
5
+ "youtube.com",
6
+ "www.youtube.com",
7
+ "m.youtube.com"
8
+ ];
9
+ const extractChannelIdFromContent = (content) => {
10
+ return content.match(/"channelId":"(UC[a-zA-Z0-9_-]+)"/)?.[1];
11
+ };
12
+ const getVideosOnlyPlaylistId = (channelId) => {
13
+ return channelId.replace(/^UC/, "UULF");
14
+ };
15
+ const youtubeHandler = {
16
+ match: (url) => {
17
+ return require_utils.isHostOf(url, hosts);
18
+ },
19
+ resolve: (url, content) => {
20
+ const parsedUrl = new URL(url);
21
+ const uris = [];
22
+ const channelMatch = parsedUrl.pathname.match(/^\/channel\/(UC[a-zA-Z0-9_-]+)/);
23
+ if (channelMatch?.[1]) {
24
+ const channelId = channelMatch[1];
25
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
26
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
27
+ }
28
+ const playlistId = parsedUrl.searchParams.get("list");
29
+ if (playlistId) uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`);
30
+ if (uris.length === 0 && content) {
31
+ if (parsedUrl.pathname.match(/^\/@([^/]+)/) || parsedUrl.pathname.match(/^\/user\/([^/]+)/) || parsedUrl.pathname.match(/^\/c\/([^/]+)/)) {
32
+ const channelId = extractChannelIdFromContent(content);
33
+ if (channelId) {
34
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
35
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
36
+ }
37
+ }
38
+ }
39
+ return uris;
40
+ }
41
+ };
42
+
43
+ //#endregion
44
+ exports.youtubeHandler = youtubeHandler;
@@ -0,0 +1,44 @@
1
+ import { isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/youtube.ts
4
+ const hosts = [
5
+ "youtube.com",
6
+ "www.youtube.com",
7
+ "m.youtube.com"
8
+ ];
9
+ const extractChannelIdFromContent = (content) => {
10
+ return content.match(/"channelId":"(UC[a-zA-Z0-9_-]+)"/)?.[1];
11
+ };
12
+ const getVideosOnlyPlaylistId = (channelId) => {
13
+ return channelId.replace(/^UC/, "UULF");
14
+ };
15
+ const youtubeHandler = {
16
+ match: (url) => {
17
+ return isHostOf(url, hosts);
18
+ },
19
+ resolve: (url, content) => {
20
+ const parsedUrl = new URL(url);
21
+ const uris = [];
22
+ const channelMatch = parsedUrl.pathname.match(/^\/channel\/(UC[a-zA-Z0-9_-]+)/);
23
+ if (channelMatch?.[1]) {
24
+ const channelId = channelMatch[1];
25
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
26
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
27
+ }
28
+ const playlistId = parsedUrl.searchParams.get("list");
29
+ if (playlistId) uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`);
30
+ if (uris.length === 0 && content) {
31
+ if (parsedUrl.pathname.match(/^\/@([^/]+)/) || parsedUrl.pathname.match(/^\/user\/([^/]+)/) || parsedUrl.pathname.match(/^\/c\/([^/]+)/)) {
32
+ const channelId = extractChannelIdFromContent(content);
33
+ if (channelId) {
34
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
35
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
36
+ }
37
+ }
38
+ }
39
+ return uris;
40
+ }
41
+ };
42
+
43
+ //#endregion
44
+ export { youtubeHandler };
package/dist/feeds.cjs CHANGED
@@ -6,6 +6,7 @@ exports.defaultExtractor = require_extractors.defaultExtractor;
6
6
  exports.defaultGuessOptions = require_defaults.defaultGuessOptions;
7
7
  exports.defaultHeadersOptions = require_defaults.defaultHeadersOptions;
8
8
  exports.defaultHtmlOptions = require_defaults.defaultHtmlOptions;
9
+ exports.defaultPlatformOptions = require_defaults.defaultPlatformOptions;
9
10
  exports.ignoredUris = require_defaults.ignoredUris;
10
11
  exports.linkSelectors = require_defaults.linkSelectors;
11
12
  exports.mimeTypes = require_defaults.mimeTypes;
package/dist/feeds.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal } from "./feeds/defaults.cjs";
1
+ import { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal } from "./feeds/defaults.cjs";
2
2
  import { FeedResult } from "./feeds/types.cjs";
3
3
  import { defaultExtractor } from "./feeds/extractors.cjs";
4
- export { FeedResult, anchorLabels, defaultExtractor, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
4
+ export { FeedResult, anchorLabels, defaultExtractor, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
package/dist/feeds.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal } from "./feeds/defaults.js";
1
+ import { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal } from "./feeds/defaults.js";
2
2
  import { FeedResult } from "./feeds/types.js";
3
3
  import { defaultExtractor } from "./feeds/extractors.js";
4
- export { FeedResult, anchorLabels, defaultExtractor, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
4
+ export { FeedResult, anchorLabels, defaultExtractor, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
package/dist/feeds.js CHANGED
@@ -1,4 +1,4 @@
1
- import { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal } from "./feeds/defaults.js";
1
+ import { anchorLabels, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal } from "./feeds/defaults.js";
2
2
  import { defaultExtractor } from "./feeds/extractors.js";
3
3
 
4
- export { anchorLabels, defaultExtractor, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
4
+ export { anchorLabels, defaultExtractor, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions, ignoredUris, linkSelectors, mimeTypes, urisBalanced, urisComprehensive, urisMinimal };
package/package.json CHANGED
@@ -1,6 +1,17 @@
1
1
  {
2
2
  "name": "feedscout",
3
3
  "description": "Advanced feed autodiscovery for JavaScript. Collect feed information from any webpage using multiple discovery methods.",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/macieklamberski/feedscout.git"
7
+ },
8
+ "homepage": "https://feedscout.dev",
9
+ "bugs": {
10
+ "url": "https://github.com/macieklamberski/feedscout/issues"
11
+ },
12
+ "license": "MIT",
13
+ "author": "Maciej Lamberski",
14
+ "sideEffects": false,
4
15
  "keywords": [
5
16
  "rss",
6
17
  "rss-autodiscovery",
@@ -12,20 +23,7 @@
12
23
  "rss-discovery",
13
24
  "feed-locator"
14
25
  ],
15
- "repository": {
16
- "type": "git",
17
- "url": "https://github.com/macieklamberski/feedscout.git"
18
- },
19
- "homepage": "https://github.com/macieklamberski/feedscout",
20
- "bugs": {
21
- "url": "https://github.com/macieklamberski/feedscout/issues"
22
- },
23
- "author": "Maciej Lamberski",
24
- "license": "MIT",
25
26
  "type": "module",
26
- "files": [
27
- "dist"
28
- ],
29
27
  "exports": {
30
28
  ".": {
31
29
  "import": {
@@ -89,6 +87,9 @@
89
87
  },
90
88
  "./package.json": "./package.json"
91
89
  },
90
+ "files": [
91
+ "dist"
92
+ ],
92
93
  "scripts": {
93
94
  "prepare": "lefthook install",
94
95
  "build": "tsdown src/exports/index.ts src/exports/feeds.ts src/exports/blogrolls.ts src/exports/hubs.ts src/exports/adapters.ts src/exports/methods.ts --format cjs,esm --dts --clean --unbundle --no-fixed-extension",
@@ -103,8 +104,8 @@
103
104
  "devDependencies": {
104
105
  "@types/bun": "^1.3.4",
105
106
  "kvalita": "1.8.0",
106
- "tsdown": "^0.17.1",
107
+ "tsdown": "^0.17.2",
107
108
  "vitepress": "^1.6.4"
108
109
  },
109
- "version": "1.0.0"
110
+ "version": "1.1.0"
110
111
  }