feedscout 1.0.0 → 1.2.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 (98) hide show
  1. package/README.md +10 -7
  2. package/dist/blogrolls/index.cjs +9 -3
  3. package/dist/blogrolls/index.d.cts +1 -1
  4. package/dist/blogrolls/index.d.ts +1 -1
  5. package/dist/blogrolls/index.js +9 -3
  6. package/dist/common/discover/utils.cjs +26 -0
  7. package/dist/common/discover/utils.js +26 -1
  8. package/dist/common/locales.cjs +1 -0
  9. package/dist/common/locales.js +1 -0
  10. package/dist/common/types.d.cts +7 -5
  11. package/dist/common/types.d.ts +7 -5
  12. package/dist/common/uris/headers/index.cjs +6 -3
  13. package/dist/common/uris/headers/index.js +6 -3
  14. package/dist/common/uris/index.cjs +2 -0
  15. package/dist/common/uris/index.js +2 -0
  16. package/dist/common/uris/platform/index.cjs +12 -0
  17. package/dist/common/uris/platform/index.js +11 -0
  18. package/dist/common/uris/platform/types.d.cts +11 -0
  19. package/dist/common/uris/platform/types.d.ts +11 -0
  20. package/dist/common/utils.cjs +10 -0
  21. package/dist/common/utils.d.cts +9 -0
  22. package/dist/common/utils.d.ts +9 -0
  23. package/dist/common/utils.js +7 -1
  24. package/dist/feeds/defaults.cjs +43 -0
  25. package/dist/feeds/defaults.d.cts +3 -1
  26. package/dist/feeds/defaults.d.ts +3 -1
  27. package/dist/feeds/defaults.js +44 -1
  28. package/dist/feeds/index.cjs +10 -3
  29. package/dist/feeds/index.d.cts +1 -1
  30. package/dist/feeds/index.d.ts +1 -1
  31. package/dist/feeds/index.js +11 -4
  32. package/dist/feeds/platform/handlers/behance.cjs +45 -0
  33. package/dist/feeds/platform/handlers/behance.js +45 -0
  34. package/dist/feeds/platform/handlers/blogspot.cjs +24 -0
  35. package/dist/feeds/platform/handlers/blogspot.js +23 -0
  36. package/dist/feeds/platform/handlers/bluesky.cjs +18 -0
  37. package/dist/feeds/platform/handlers/bluesky.js +18 -0
  38. package/dist/feeds/platform/handlers/dailymotion.cjs +66 -0
  39. package/dist/feeds/platform/handlers/dailymotion.js +66 -0
  40. package/dist/feeds/platform/handlers/deviantart.cjs +50 -0
  41. package/dist/feeds/platform/handlers/deviantart.js +50 -0
  42. package/dist/feeds/platform/handlers/devto.cjs +44 -0
  43. package/dist/feeds/platform/handlers/devto.js +44 -0
  44. package/dist/feeds/platform/handlers/github.cjs +89 -0
  45. package/dist/feeds/platform/handlers/github.js +89 -0
  46. package/dist/feeds/platform/handlers/githubGist.cjs +42 -0
  47. package/dist/feeds/platform/handlers/githubGist.js +42 -0
  48. package/dist/feeds/platform/handlers/gitlab.cjs +53 -0
  49. package/dist/feeds/platform/handlers/gitlab.js +53 -0
  50. package/dist/feeds/platform/handlers/kickstarter.cjs +18 -0
  51. package/dist/feeds/platform/handlers/kickstarter.js +18 -0
  52. package/dist/feeds/platform/handlers/lobsters.cjs +34 -0
  53. package/dist/feeds/platform/handlers/lobsters.js +34 -0
  54. package/dist/feeds/platform/handlers/medium.cjs +47 -0
  55. package/dist/feeds/platform/handlers/medium.js +47 -0
  56. package/dist/feeds/platform/handlers/pinterest.cjs +48 -0
  57. package/dist/feeds/platform/handlers/pinterest.js +48 -0
  58. package/dist/feeds/platform/handlers/producthunt.cjs +22 -0
  59. package/dist/feeds/platform/handlers/producthunt.js +22 -0
  60. package/dist/feeds/platform/handlers/reddit.cjs +47 -0
  61. package/dist/feeds/platform/handlers/reddit.js +47 -0
  62. package/dist/feeds/platform/handlers/soundcloud.cjs +37 -0
  63. package/dist/feeds/platform/handlers/soundcloud.js +37 -0
  64. package/dist/feeds/platform/handlers/substack.cjs +15 -0
  65. package/dist/feeds/platform/handlers/substack.js +15 -0
  66. package/dist/feeds/platform/handlers/tumblr.cjs +18 -0
  67. package/dist/feeds/platform/handlers/tumblr.js +18 -0
  68. package/dist/feeds/platform/handlers/wordpress.cjs +30 -0
  69. package/dist/feeds/platform/handlers/wordpress.js +30 -0
  70. package/dist/feeds/platform/handlers/youtube.cjs +56 -0
  71. package/dist/feeds/platform/handlers/youtube.js +56 -0
  72. package/dist/feeds.cjs +1 -0
  73. package/dist/feeds.d.cts +2 -2
  74. package/dist/feeds.d.ts +2 -2
  75. package/dist/feeds.js +2 -2
  76. package/dist/hubs/discover/index.cjs +7 -6
  77. package/dist/hubs/discover/index.js +5 -4
  78. package/dist/hubs/discover/types.d.cts +2 -1
  79. package/dist/hubs/discover/types.d.ts +2 -1
  80. package/dist/hubs/headers/index.cjs +3 -3
  81. package/dist/hubs/headers/index.js +3 -3
  82. package/dist/hubs/html/index.cjs +3 -3
  83. package/dist/hubs/html/index.js +3 -3
  84. package/dist/index.d.cts +2 -2
  85. package/dist/index.d.ts +2 -2
  86. package/dist/utils.cjs +8 -0
  87. package/dist/utils.d.cts +2 -0
  88. package/dist/utils.d.ts +2 -0
  89. package/dist/utils.js +3 -0
  90. package/package.json +32 -32
  91. package/dist/adapters.cjs +0 -6
  92. package/dist/adapters.d.cts +0 -2
  93. package/dist/adapters.d.ts +0 -2
  94. package/dist/adapters.js +0 -3
  95. package/dist/common/discover/adapters.cjs +0 -76
  96. package/dist/common/discover/adapters.d.cts +0 -10
  97. package/dist/common/discover/adapters.d.ts +0 -10
  98. package/dist/common/discover/adapters.js +0 -72
@@ -0,0 +1,22 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/producthunt.ts
4
+ const hosts = ["producthunt.com", "www.producthunt.com"];
5
+ const topicPathRegex = /^\/topics\/([a-zA-Z0-9_-]+)/;
6
+ const categoryPathRegex = /^\/categories\/([a-zA-Z0-9_-]+)/;
7
+ const producthuntHandler = {
8
+ match: (url) => {
9
+ return require_utils.isHostOf(url, hosts);
10
+ },
11
+ resolve: (url) => {
12
+ const { pathname } = new URL(url);
13
+ const topicMatch = pathname.match(topicPathRegex);
14
+ if (topicMatch?.[1]) return [`https://www.producthunt.com/feed?topic=${topicMatch[1]}`];
15
+ const categoryMatch = pathname.match(categoryPathRegex);
16
+ if (categoryMatch?.[1]) return [`https://www.producthunt.com/feed?category=${categoryMatch[1]}`];
17
+ return ["https://www.producthunt.com/feed"];
18
+ }
19
+ };
20
+
21
+ //#endregion
22
+ exports.producthuntHandler = producthuntHandler;
@@ -0,0 +1,22 @@
1
+ import { isHostOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/producthunt.ts
4
+ const hosts = ["producthunt.com", "www.producthunt.com"];
5
+ const topicPathRegex = /^\/topics\/([a-zA-Z0-9_-]+)/;
6
+ const categoryPathRegex = /^\/categories\/([a-zA-Z0-9_-]+)/;
7
+ const producthuntHandler = {
8
+ match: (url) => {
9
+ return isHostOf(url, hosts);
10
+ },
11
+ resolve: (url) => {
12
+ const { pathname } = new URL(url);
13
+ const topicMatch = pathname.match(topicPathRegex);
14
+ if (topicMatch?.[1]) return [`https://www.producthunt.com/feed?topic=${topicMatch[1]}`];
15
+ const categoryMatch = pathname.match(categoryPathRegex);
16
+ if (categoryMatch?.[1]) return [`https://www.producthunt.com/feed?category=${categoryMatch[1]}`];
17
+ return ["https://www.producthunt.com/feed"];
18
+ }
19
+ };
20
+
21
+ //#endregion
22
+ export { producthuntHandler };
@@ -0,0 +1,47 @@
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
+ if (pathname.split("/").filter(Boolean).length === 0) return ["https://www.reddit.com/.rss"];
24
+ const commentsMatch = pathname.match(/^\/r\/([^/]+)\/comments\/([^/]+)/);
25
+ if (commentsMatch?.[1] && commentsMatch?.[2]) return [`https://www.reddit.com/r/${commentsMatch[1]}/comments/${commentsMatch[2]}/.rss`];
26
+ const subredditMatch = pathname.match(/^\/r\/([^/]+)(?:\/([^/]+))?/);
27
+ if (subredditMatch?.[1]) {
28
+ const subreddit = subredditMatch[1];
29
+ const sort = subredditMatch[2];
30
+ const uris = [];
31
+ if (sort && require_utils.isAnyOf(sort, sortOptions)) uris.push(`https://www.reddit.com/r/${subreddit}/${sort}/.rss`);
32
+ else uris.push(`https://www.reddit.com/r/${subreddit}/.rss`);
33
+ uris.push(`https://www.reddit.com/r/${subreddit}/comments/.rss`);
34
+ return uris;
35
+ }
36
+ const multiredditMatch = pathname.match(/^\/user\/([^/]+)\/m\/([^/]+)/);
37
+ if (multiredditMatch?.[1] && multiredditMatch?.[2]) return [`https://www.reddit.com/user/${multiredditMatch[1]}/m/${multiredditMatch[2]}/.rss`];
38
+ const userMatch = pathname.match(/^\/(u|user)\/([^/]+)/);
39
+ if (userMatch?.[2]) return [`https://www.reddit.com/user/${userMatch[2]}/.rss`];
40
+ const domainMatch = pathname.match(/^\/domain\/([^/]+)/);
41
+ if (domainMatch?.[1]) return [`https://www.reddit.com/domain/${domainMatch[1]}/.rss`];
42
+ return [];
43
+ }
44
+ };
45
+
46
+ //#endregion
47
+ exports.redditHandler = redditHandler;
@@ -0,0 +1,47 @@
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
+ if (pathname.split("/").filter(Boolean).length === 0) return ["https://www.reddit.com/.rss"];
24
+ const commentsMatch = pathname.match(/^\/r\/([^/]+)\/comments\/([^/]+)/);
25
+ if (commentsMatch?.[1] && commentsMatch?.[2]) return [`https://www.reddit.com/r/${commentsMatch[1]}/comments/${commentsMatch[2]}/.rss`];
26
+ const subredditMatch = pathname.match(/^\/r\/([^/]+)(?:\/([^/]+))?/);
27
+ if (subredditMatch?.[1]) {
28
+ const subreddit = subredditMatch[1];
29
+ const sort = subredditMatch[2];
30
+ const uris = [];
31
+ if (sort && isAnyOf(sort, sortOptions)) uris.push(`https://www.reddit.com/r/${subreddit}/${sort}/.rss`);
32
+ else uris.push(`https://www.reddit.com/r/${subreddit}/.rss`);
33
+ uris.push(`https://www.reddit.com/r/${subreddit}/comments/.rss`);
34
+ return uris;
35
+ }
36
+ const multiredditMatch = pathname.match(/^\/user\/([^/]+)\/m\/([^/]+)/);
37
+ if (multiredditMatch?.[1] && multiredditMatch?.[2]) return [`https://www.reddit.com/user/${multiredditMatch[1]}/m/${multiredditMatch[2]}/.rss`];
38
+ const userMatch = pathname.match(/^\/(u|user)\/([^/]+)/);
39
+ if (userMatch?.[2]) return [`https://www.reddit.com/user/${userMatch[2]}/.rss`];
40
+ const domainMatch = pathname.match(/^\/domain\/([^/]+)/);
41
+ if (domainMatch?.[1]) return [`https://www.reddit.com/domain/${domainMatch[1]}/.rss`];
42
+ return [];
43
+ }
44
+ };
45
+
46
+ //#endregion
47
+ 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 && !require_utils.isAnyOf(pathSegments[0], excludedPaths);
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 { isAnyOf, 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 && !isAnyOf(pathSegments[0], excludedPaths);
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,18 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/tumblr.ts
4
+ const tagPathRegex = /^\/tagged\/([^/]+)/;
5
+ const tumblrHandler = {
6
+ match: (url) => {
7
+ return require_utils.isSubdomainOf(url, "tumblr.com");
8
+ },
9
+ resolve: (url) => {
10
+ const { origin, pathname } = new URL(url);
11
+ const tagMatch = pathname.match(tagPathRegex);
12
+ if (tagMatch?.[1]) return [`${origin}/tagged/${tagMatch[1]}/rss`];
13
+ return [`${origin}/rss`];
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ exports.tumblrHandler = tumblrHandler;
@@ -0,0 +1,18 @@
1
+ import { isSubdomainOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/tumblr.ts
4
+ const tagPathRegex = /^\/tagged\/([^/]+)/;
5
+ const tumblrHandler = {
6
+ match: (url) => {
7
+ return isSubdomainOf(url, "tumblr.com");
8
+ },
9
+ resolve: (url) => {
10
+ const { origin, pathname } = new URL(url);
11
+ const tagMatch = pathname.match(tagPathRegex);
12
+ if (tagMatch?.[1]) return [`${origin}/tagged/${tagMatch[1]}/rss`];
13
+ return [`${origin}/rss`];
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ export { tumblrHandler };
@@ -0,0 +1,30 @@
1
+ const require_utils = require('../../../common/utils.cjs');
2
+
3
+ //#region src/feeds/platform/handlers/wordpress.ts
4
+ const categoryPathRegex = /^\/category\/([^/]+)/;
5
+ const tagPathRegex = /^\/tag\/([^/]+)/;
6
+ const authorPathRegex = /^\/author\/([^/]+)/;
7
+ const wordpressHandler = {
8
+ match: (url) => {
9
+ return require_utils.isSubdomainOf(url, "wordpress.com");
10
+ },
11
+ resolve: (url) => {
12
+ const { origin, pathname } = new URL(url);
13
+ const uris = [];
14
+ const categoryMatch = pathname.match(categoryPathRegex);
15
+ if (categoryMatch?.[1]) uris.push(`${origin}/category/${categoryMatch[1]}/feed/`);
16
+ const tagMatch = pathname.match(tagPathRegex);
17
+ if (tagMatch?.[1]) uris.push(`${origin}/tag/${tagMatch[1]}/feed/`);
18
+ const authorMatch = pathname.match(authorPathRegex);
19
+ if (authorMatch?.[1]) uris.push(`${origin}/author/${authorMatch[1]}/feed/`);
20
+ uris.push(`${origin}/feed/`);
21
+ uris.push(`${origin}/feed/rss2/`);
22
+ uris.push(`${origin}/feed/rdf/`);
23
+ uris.push(`${origin}/feed/atom/`);
24
+ uris.push(`${origin}/comments/feed/`);
25
+ return uris;
26
+ }
27
+ };
28
+
29
+ //#endregion
30
+ exports.wordpressHandler = wordpressHandler;
@@ -0,0 +1,30 @@
1
+ import { isSubdomainOf } from "../../../common/utils.js";
2
+
3
+ //#region src/feeds/platform/handlers/wordpress.ts
4
+ const categoryPathRegex = /^\/category\/([^/]+)/;
5
+ const tagPathRegex = /^\/tag\/([^/]+)/;
6
+ const authorPathRegex = /^\/author\/([^/]+)/;
7
+ const wordpressHandler = {
8
+ match: (url) => {
9
+ return isSubdomainOf(url, "wordpress.com");
10
+ },
11
+ resolve: (url) => {
12
+ const { origin, pathname } = new URL(url);
13
+ const uris = [];
14
+ const categoryMatch = pathname.match(categoryPathRegex);
15
+ if (categoryMatch?.[1]) uris.push(`${origin}/category/${categoryMatch[1]}/feed/`);
16
+ const tagMatch = pathname.match(tagPathRegex);
17
+ if (tagMatch?.[1]) uris.push(`${origin}/tag/${tagMatch[1]}/feed/`);
18
+ const authorMatch = pathname.match(authorPathRegex);
19
+ if (authorMatch?.[1]) uris.push(`${origin}/author/${authorMatch[1]}/feed/`);
20
+ uris.push(`${origin}/feed/`);
21
+ uris.push(`${origin}/feed/rss2/`);
22
+ uris.push(`${origin}/feed/rdf/`);
23
+ uris.push(`${origin}/feed/atom/`);
24
+ uris.push(`${origin}/comments/feed/`);
25
+ return uris;
26
+ }
27
+ };
28
+
29
+ //#endregion
30
+ export { wordpressHandler };
@@ -0,0 +1,56 @@
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
+ "youtu.be",
9
+ "www.youtu.be"
10
+ ];
11
+ const channelIdRegex = /"channelId":"(UC[a-zA-Z0-9_-]+)"/;
12
+ const channelPathRegex = /^\/channel\/(UC[a-zA-Z0-9_-]+)/;
13
+ const handlePathRegex = /^\/@([^/]+)/;
14
+ const userPathRegex = /^\/user\/([^/]+)/;
15
+ const customPathRegex = /^\/c\/([^/]+)/;
16
+ const extractChannelIdFromContent = (content) => {
17
+ return content.match(channelIdRegex)?.[1];
18
+ };
19
+ const getVideosOnlyPlaylistId = (channelId) => {
20
+ return channelId.replace(/^UC/, "UULF");
21
+ };
22
+ const getShortsOnlyPlaylistId = (channelId) => {
23
+ return channelId.replace(/^UC/, "UUSH");
24
+ };
25
+ const youtubeHandler = {
26
+ match: (url) => {
27
+ return require_utils.isHostOf(url, hosts);
28
+ },
29
+ resolve: (url, content) => {
30
+ const parsedUrl = new URL(url);
31
+ const uris = [];
32
+ const channelMatch = parsedUrl.pathname.match(channelPathRegex);
33
+ if (channelMatch?.[1]) {
34
+ const channelId = channelMatch[1];
35
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
36
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
37
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getShortsOnlyPlaylistId(channelId)}`);
38
+ }
39
+ const playlistId = parsedUrl.searchParams.get("list");
40
+ if (playlistId) uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`);
41
+ if (uris.length === 0 && content) {
42
+ if (parsedUrl.searchParams.has("v") || parsedUrl.hostname.includes("youtu.be") && parsedUrl.pathname.length > 1 || parsedUrl.pathname.match(handlePathRegex) || parsedUrl.pathname.match(userPathRegex) || parsedUrl.pathname.match(customPathRegex)) {
43
+ const channelId = extractChannelIdFromContent(content);
44
+ if (channelId) {
45
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
46
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
47
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getShortsOnlyPlaylistId(channelId)}`);
48
+ }
49
+ }
50
+ }
51
+ return uris;
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ exports.youtubeHandler = youtubeHandler;
@@ -0,0 +1,56 @@
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
+ "youtu.be",
9
+ "www.youtu.be"
10
+ ];
11
+ const channelIdRegex = /"channelId":"(UC[a-zA-Z0-9_-]+)"/;
12
+ const channelPathRegex = /^\/channel\/(UC[a-zA-Z0-9_-]+)/;
13
+ const handlePathRegex = /^\/@([^/]+)/;
14
+ const userPathRegex = /^\/user\/([^/]+)/;
15
+ const customPathRegex = /^\/c\/([^/]+)/;
16
+ const extractChannelIdFromContent = (content) => {
17
+ return content.match(channelIdRegex)?.[1];
18
+ };
19
+ const getVideosOnlyPlaylistId = (channelId) => {
20
+ return channelId.replace(/^UC/, "UULF");
21
+ };
22
+ const getShortsOnlyPlaylistId = (channelId) => {
23
+ return channelId.replace(/^UC/, "UUSH");
24
+ };
25
+ const youtubeHandler = {
26
+ match: (url) => {
27
+ return isHostOf(url, hosts);
28
+ },
29
+ resolve: (url, content) => {
30
+ const parsedUrl = new URL(url);
31
+ const uris = [];
32
+ const channelMatch = parsedUrl.pathname.match(channelPathRegex);
33
+ if (channelMatch?.[1]) {
34
+ const channelId = channelMatch[1];
35
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
36
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
37
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getShortsOnlyPlaylistId(channelId)}`);
38
+ }
39
+ const playlistId = parsedUrl.searchParams.get("list");
40
+ if (playlistId) uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${playlistId}`);
41
+ if (uris.length === 0 && content) {
42
+ if (parsedUrl.searchParams.has("v") || parsedUrl.hostname.includes("youtu.be") && parsedUrl.pathname.length > 1 || parsedUrl.pathname.match(handlePathRegex) || parsedUrl.pathname.match(userPathRegex) || parsedUrl.pathname.match(customPathRegex)) {
43
+ const channelId = extractChannelIdFromContent(content);
44
+ if (channelId) {
45
+ uris.push(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`);
46
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getVideosOnlyPlaylistId(channelId)}`);
47
+ uris.push(`https://www.youtube.com/feeds/videos.xml?playlist_id=${getShortsOnlyPlaylistId(channelId)}`);
48
+ }
49
+ }
50
+ }
51
+ return uris;
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ 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 };
@@ -1,8 +1,9 @@
1
- const require_adapters = require('../../common/discover/adapters.cjs');
1
+ const require_utils = require('../../common/utils.cjs');
2
+ const require_utils$1 = require('../../common/discover/utils.cjs');
2
3
  const require_index = require('../feed/index.cjs');
3
4
  const require_index$1 = require('../headers/index.cjs');
4
5
  const require_index$2 = require('../html/index.cjs');
5
- const require_utils = require('./utils.cjs');
6
+ const require_utils$2 = require('./utils.cjs');
6
7
 
7
8
  //#region src/hubs/discover/index.ts
8
9
  const discoverHubs = async (input, options = {}) => {
@@ -10,11 +11,11 @@ const discoverHubs = async (input, options = {}) => {
10
11
  "headers",
11
12
  "feed",
12
13
  "html"
13
- ], fetchFn = require_adapters.createNativeFetchAdapter() } = options;
14
- const normalizedInput = await require_utils.normalizeInput(input, fetchFn);
14
+ ], fetchFn = require_utils$1.defaultFetchFn, normalizeUrlFn = require_utils.normalizeUrl } = options;
15
+ const normalizedInput = await require_utils$2.normalizeInput(input, fetchFn);
15
16
  const results = [];
16
17
  if (methods.includes("headers") && normalizedInput.headers) {
17
- const headerHubs = require_index$1.discoverHubsFromHeaders(normalizedInput.headers, normalizedInput.url);
18
+ const headerHubs = require_index$1.discoverHubsFromHeaders(normalizedInput.headers, normalizedInput.url, normalizeUrlFn);
18
19
  results.push(...headerHubs);
19
20
  }
20
21
  if (methods.includes("feed") && normalizedInput.content) {
@@ -22,7 +23,7 @@ const discoverHubs = async (input, options = {}) => {
22
23
  results.push(...feedHubs);
23
24
  }
24
25
  if (methods.includes("html") && normalizedInput.content) {
25
- const htmlHubs = require_index$2.discoverHubsFromHtml(normalizedInput.content, normalizedInput.url);
26
+ const htmlHubs = require_index$2.discoverHubsFromHtml(normalizedInput.content, normalizedInput.url, normalizeUrlFn);
26
27
  results.push(...htmlHubs);
27
28
  }
28
29
  return results;
@@ -1,4 +1,5 @@
1
- import { createNativeFetchAdapter } from "../../common/discover/adapters.js";
1
+ import { normalizeUrl } from "../../common/utils.js";
2
+ import { defaultFetchFn } from "../../common/discover/utils.js";
2
3
  import { discoverHubsFromFeed } from "../feed/index.js";
3
4
  import { discoverHubsFromHeaders } from "../headers/index.js";
4
5
  import { discoverHubsFromHtml } from "../html/index.js";
@@ -10,11 +11,11 @@ const discoverHubs = async (input, options = {}) => {
10
11
  "headers",
11
12
  "feed",
12
13
  "html"
13
- ], fetchFn = createNativeFetchAdapter() } = options;
14
+ ], fetchFn = defaultFetchFn, normalizeUrlFn = normalizeUrl } = options;
14
15
  const normalizedInput = await normalizeInput(input, fetchFn);
15
16
  const results = [];
16
17
  if (methods.includes("headers") && normalizedInput.headers) {
17
- const headerHubs = discoverHubsFromHeaders(normalizedInput.headers, normalizedInput.url);
18
+ const headerHubs = discoverHubsFromHeaders(normalizedInput.headers, normalizedInput.url, normalizeUrlFn);
18
19
  results.push(...headerHubs);
19
20
  }
20
21
  if (methods.includes("feed") && normalizedInput.content) {
@@ -22,7 +23,7 @@ const discoverHubs = async (input, options = {}) => {
22
23
  results.push(...feedHubs);
23
24
  }
24
25
  if (methods.includes("html") && normalizedInput.content) {
25
- const htmlHubs = discoverHubsFromHtml(normalizedInput.content, normalizedInput.url);
26
+ const htmlHubs = discoverHubsFromHtml(normalizedInput.content, normalizedInput.url, normalizeUrlFn);
26
27
  results.push(...htmlHubs);
27
28
  }
28
29
  return results;
@@ -1,4 +1,4 @@
1
- import { DiscoverFetchFn } from "../../common/types.cjs";
1
+ import { DiscoverFetchFn, DiscoverNormalizeUrlFn } from "../../common/types.cjs";
2
2
 
3
3
  //#region src/hubs/discover/types.d.ts
4
4
  type HubResult = {
@@ -9,6 +9,7 @@ type DiscoverHubsMethodsConfig = Array<'headers' | 'html' | 'feed'>;
9
9
  type DiscoverHubsOptions = {
10
10
  methods?: DiscoverHubsMethodsConfig;
11
11
  fetchFn?: DiscoverFetchFn;
12
+ normalizeUrlFn?: DiscoverNormalizeUrlFn;
12
13
  };
13
14
  //#endregion
14
15
  export { DiscoverHubsMethodsConfig, DiscoverHubsOptions, HubResult };
@@ -1,4 +1,4 @@
1
- import { DiscoverFetchFn } from "../../common/types.js";
1
+ import { DiscoverFetchFn, DiscoverNormalizeUrlFn } from "../../common/types.js";
2
2
 
3
3
  //#region src/hubs/discover/types.d.ts
4
4
  type HubResult = {
@@ -9,6 +9,7 @@ type DiscoverHubsMethodsConfig = Array<'headers' | 'html' | 'feed'>;
9
9
  type DiscoverHubsOptions = {
10
10
  methods?: DiscoverHubsMethodsConfig;
11
11
  fetchFn?: DiscoverFetchFn;
12
+ normalizeUrlFn?: DiscoverNormalizeUrlFn;
12
13
  };
13
14
  //#endregion
14
15
  export { DiscoverHubsMethodsConfig, DiscoverHubsOptions, HubResult };
@@ -4,13 +4,13 @@ const require_index = require('../../common/uris/headers/index.cjs');
4
4
  //#region src/hubs/headers/index.ts
5
5
  const hubSelector = [{ rel: "hub" }];
6
6
  const selfSelector = [{ rel: "self" }];
7
- const discoverHubsFromHeaders = (headers, baseUrl) => {
7
+ const discoverHubsFromHeaders = (headers, baseUrl, normalizeUrlFn = require_utils.normalizeUrl) => {
8
8
  const hubUris = require_index.discoverUrisFromHeaders(headers, { linkSelectors: hubSelector });
9
9
  if (hubUris.length === 0) return [];
10
10
  const selfUris = require_index.discoverUrisFromHeaders(headers, { linkSelectors: selfSelector });
11
- const topic = selfUris[0] ? require_utils.normalizeUrl(selfUris[0], baseUrl) : baseUrl;
11
+ const topic = selfUris[0] ? normalizeUrlFn(selfUris[0], baseUrl) : baseUrl;
12
12
  return hubUris.map((hub) => ({
13
- hub: require_utils.normalizeUrl(hub, baseUrl),
13
+ hub: normalizeUrlFn(hub, baseUrl),
14
14
  topic
15
15
  }));
16
16
  };
@@ -4,13 +4,13 @@ import { discoverUrisFromHeaders } from "../../common/uris/headers/index.js";
4
4
  //#region src/hubs/headers/index.ts
5
5
  const hubSelector = [{ rel: "hub" }];
6
6
  const selfSelector = [{ rel: "self" }];
7
- const discoverHubsFromHeaders = (headers, baseUrl) => {
7
+ const discoverHubsFromHeaders = (headers, baseUrl, normalizeUrlFn = normalizeUrl) => {
8
8
  const hubUris = discoverUrisFromHeaders(headers, { linkSelectors: hubSelector });
9
9
  if (hubUris.length === 0) return [];
10
10
  const selfUris = discoverUrisFromHeaders(headers, { linkSelectors: selfSelector });
11
- const topic = selfUris[0] ? normalizeUrl(selfUris[0], baseUrl) : baseUrl;
11
+ const topic = selfUris[0] ? normalizeUrlFn(selfUris[0], baseUrl) : baseUrl;
12
12
  return hubUris.map((hub) => ({
13
- hub: normalizeUrl(hub, baseUrl),
13
+ hub: normalizeUrlFn(hub, baseUrl),
14
14
  topic
15
15
  }));
16
16
  };