feedscout 1.6.2 → 1.7.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 (92) hide show
  1. package/README.md +14 -0
  2. package/dist/blogrolls/extractors.cjs +1 -1
  3. package/dist/blogrolls/extractors.js +1 -1
  4. package/dist/blogrolls/index.cjs +3 -2
  5. package/dist/blogrolls/index.js +5 -4
  6. package/dist/common/discover/index.cjs +21 -9
  7. package/dist/common/discover/index.js +21 -9
  8. package/dist/common/discover/utils.cjs +44 -18
  9. package/dist/common/discover/utils.d.cts +8 -0
  10. package/dist/common/discover/utils.d.ts +8 -0
  11. package/dist/common/discover/utils.js +43 -19
  12. package/dist/common/locales.cjs +3 -1
  13. package/dist/common/locales.js +3 -1
  14. package/dist/common/types.d.cts +6 -4
  15. package/dist/common/types.d.ts +6 -4
  16. package/dist/common/uris/guess/utils.cjs +3 -2
  17. package/dist/common/uris/guess/utils.js +3 -2
  18. package/dist/common/uris/headers/index.cjs +2 -1
  19. package/dist/common/uris/headers/index.js +2 -1
  20. package/dist/common/utils.cjs +15 -4
  21. package/dist/common/utils.js +14 -4
  22. package/dist/favicons/extractors.cjs +16 -4
  23. package/dist/favicons/extractors.js +16 -4
  24. package/dist/favicons/index.cjs +3 -2
  25. package/dist/favicons/index.js +5 -4
  26. package/dist/favicons/platform/handlers/bluesky.cjs +3 -3
  27. package/dist/favicons/platform/handlers/bluesky.js +3 -3
  28. package/dist/favicons/platform/handlers/mastodon.cjs +7 -5
  29. package/dist/favicons/platform/handlers/mastodon.js +7 -5
  30. package/dist/favicons/platform/handlers/reddit.cjs +5 -6
  31. package/dist/favicons/platform/handlers/reddit.js +5 -6
  32. package/dist/favicons/utils.cjs +10 -0
  33. package/dist/favicons/utils.js +9 -0
  34. package/dist/feeds/defaults.cjs +2 -0
  35. package/dist/feeds/defaults.js +2 -0
  36. package/dist/feeds/extractors.cjs +10 -8
  37. package/dist/feeds/extractors.js +10 -8
  38. package/dist/feeds/index.cjs +2 -2
  39. package/dist/feeds/index.js +3 -3
  40. package/dist/feeds/platform/handlers/blogspot.cjs +2 -1
  41. package/dist/feeds/platform/handlers/blogspot.js +2 -1
  42. package/dist/feeds/platform/handlers/bluesky.cjs +2 -1
  43. package/dist/feeds/platform/handlers/bluesky.js +2 -1
  44. package/dist/feeds/platform/handlers/csdn.cjs +2 -1
  45. package/dist/feeds/platform/handlers/csdn.js +2 -1
  46. package/dist/feeds/platform/handlers/deviantart.cjs +8 -4
  47. package/dist/feeds/platform/handlers/deviantart.js +8 -4
  48. package/dist/feeds/platform/handlers/douban.cjs +4 -2
  49. package/dist/feeds/platform/handlers/douban.js +4 -2
  50. package/dist/feeds/platform/handlers/github.cjs +12 -6
  51. package/dist/feeds/platform/handlers/github.js +12 -6
  52. package/dist/feeds/platform/handlers/githubGist.cjs +6 -3
  53. package/dist/feeds/platform/handlers/githubGist.js +6 -3
  54. package/dist/feeds/platform/handlers/gitlab.cjs +15 -2
  55. package/dist/feeds/platform/handlers/gitlab.js +16 -3
  56. package/dist/feeds/platform/handlers/lemmy.cjs +46 -0
  57. package/dist/feeds/platform/handlers/lemmy.js +46 -0
  58. package/dist/feeds/platform/handlers/mastodon.cjs +5 -3
  59. package/dist/feeds/platform/handlers/mastodon.js +5 -3
  60. package/dist/feeds/platform/handlers/medium.cjs +10 -5
  61. package/dist/feeds/platform/handlers/medium.js +10 -5
  62. package/dist/feeds/platform/handlers/reddit.cjs +10 -5
  63. package/dist/feeds/platform/handlers/reddit.js +10 -5
  64. package/dist/feeds/platform/handlers/soundcloud.cjs +3 -2
  65. package/dist/feeds/platform/handlers/soundcloud.js +3 -2
  66. package/dist/feeds/platform/handlers/stackExchange.cjs +6 -3
  67. package/dist/feeds/platform/handlers/stackExchange.js +6 -3
  68. package/dist/feeds/platform/handlers/steam.cjs +4 -2
  69. package/dist/feeds/platform/handlers/steam.js +4 -2
  70. package/dist/feeds/platform/handlers/v2ex.cjs +4 -2
  71. package/dist/feeds/platform/handlers/v2ex.js +4 -2
  72. package/dist/feeds/platform/handlers/vimeo.cjs +3 -2
  73. package/dist/feeds/platform/handlers/vimeo.js +3 -2
  74. package/dist/feeds/platform/handlers/ximalaya.cjs +2 -1
  75. package/dist/feeds/platform/handlers/ximalaya.js +2 -1
  76. package/dist/feeds/platform/handlers/youtube.cjs +10 -9
  77. package/dist/feeds/platform/handlers/youtube.js +10 -9
  78. package/dist/hubs/discover/index.cjs +4 -4
  79. package/dist/hubs/discover/index.js +5 -5
  80. package/dist/hubs/discover/types.d.cts +2 -2
  81. package/dist/hubs/discover/types.d.ts +2 -2
  82. package/dist/hubs/feed/index.cjs +7 -5
  83. package/dist/hubs/feed/index.js +7 -5
  84. package/dist/hubs/headers/index.cjs +3 -3
  85. package/dist/hubs/headers/index.js +4 -4
  86. package/dist/hubs/html/index.cjs +3 -3
  87. package/dist/hubs/html/index.js +4 -4
  88. package/dist/index.cjs +3 -0
  89. package/dist/index.d.cts +3 -2
  90. package/dist/index.d.ts +3 -2
  91. package/dist/index.js +2 -1
  92. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  import { composeHint, isHostOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/csdn.ts
3
+ const userRegex = /^\/([^/]+)/;
3
4
  const hosts = ["blog.csdn.net"];
4
5
  const csdnHandler = {
5
6
  match: (url) => {
@@ -7,7 +8,7 @@ const csdnHandler = {
7
8
  },
8
9
  resolve: (url) => {
9
10
  const { pathname } = new URL(url);
10
- const username = pathname.match(/^\/([^/]+)/)?.[1];
11
+ const username = pathname.match(userRegex)?.[1];
11
12
  if (!username) return [];
12
13
  return [{
13
14
  uri: [`https://rss.csdn.net/${username}/rss/map`, `https://blog.csdn.net/${username}/rss/list`],
@@ -1,5 +1,9 @@
1
1
  const require_utils = require("../../../common/utils.cjs");
2
2
  //#region src/feeds/platform/handlers/deviantart.ts
3
+ const tagRegex = /^\/tag\/([^/]+)/;
4
+ const favouritesRegex = /^\/([a-zA-Z0-9_-]+)\/favourites\/?$/;
5
+ const folderRegex = /^\/([a-zA-Z0-9_-]+)\/gallery\/(\d+)(?:\/|$)/;
6
+ const profileRegex = /^\/([a-zA-Z0-9_-]+)(?:\/gallery(?:\/all)?)?(?:\/|$)/;
3
7
  const hosts = ["deviantart.com", "www.deviantart.com"];
4
8
  const feedBaseUrl = "https://backend.deviantart.com/rss.xml";
5
9
  const excludedPaths = [
@@ -22,7 +26,7 @@ const deviantartHandler = {
22
26
  },
23
27
  resolve: (url) => {
24
28
  const { pathname } = new URL(url);
25
- const tagMatch = pathname.match(/^\/tag\/([^/]+)/);
29
+ const tagMatch = pathname.match(tagRegex);
26
30
  if (tagMatch?.[1]) {
27
31
  const tag = tagMatch[1];
28
32
  return [{
@@ -30,7 +34,7 @@ const deviantartHandler = {
30
34
  hint: require_utils.composeHint("deviantart:tag")
31
35
  }];
32
36
  }
33
- const favMatch = pathname.match(/^\/([a-zA-Z0-9_-]+)\/favourites\/?$/);
37
+ const favMatch = pathname.match(favouritesRegex);
34
38
  if (favMatch?.[1]) {
35
39
  const username = favMatch[1];
36
40
  if (!require_utils.isAnyOf(username, excludedPaths)) return [{
@@ -38,7 +42,7 @@ const deviantartHandler = {
38
42
  hint: require_utils.composeHint("deviantart:favorites")
39
43
  }];
40
44
  }
41
- const folderMatch = pathname.match(/^\/([a-zA-Z0-9_-]+)\/gallery\/(\d+)(?:\/|$)/);
45
+ const folderMatch = pathname.match(folderRegex);
42
46
  if (folderMatch?.[1] && folderMatch?.[2]) {
43
47
  const username = folderMatch[1];
44
48
  const folderId = folderMatch[2];
@@ -47,7 +51,7 @@ const deviantartHandler = {
47
51
  hint: require_utils.composeHint("deviantart:gallery")
48
52
  }];
49
53
  }
50
- const username = pathname.match(/^\/([a-zA-Z0-9_-]+)(?:\/gallery(?:\/all)?)?(?:\/|$)/)?.[1];
54
+ const username = pathname.match(profileRegex)?.[1];
51
55
  if (!username || require_utils.isAnyOf(username, excludedPaths)) return [];
52
56
  const query = `by:${username} sort:time meta:all`;
53
57
  return [{
@@ -1,5 +1,9 @@
1
1
  import { composeHint, isAnyOf, isHostOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/deviantart.ts
3
+ const tagRegex = /^\/tag\/([^/]+)/;
4
+ const favouritesRegex = /^\/([a-zA-Z0-9_-]+)\/favourites\/?$/;
5
+ const folderRegex = /^\/([a-zA-Z0-9_-]+)\/gallery\/(\d+)(?:\/|$)/;
6
+ const profileRegex = /^\/([a-zA-Z0-9_-]+)(?:\/gallery(?:\/all)?)?(?:\/|$)/;
3
7
  const hosts = ["deviantart.com", "www.deviantart.com"];
4
8
  const feedBaseUrl = "https://backend.deviantart.com/rss.xml";
5
9
  const excludedPaths = [
@@ -22,7 +26,7 @@ const deviantartHandler = {
22
26
  },
23
27
  resolve: (url) => {
24
28
  const { pathname } = new URL(url);
25
- const tagMatch = pathname.match(/^\/tag\/([^/]+)/);
29
+ const tagMatch = pathname.match(tagRegex);
26
30
  if (tagMatch?.[1]) {
27
31
  const tag = tagMatch[1];
28
32
  return [{
@@ -30,7 +34,7 @@ const deviantartHandler = {
30
34
  hint: composeHint("deviantart:tag")
31
35
  }];
32
36
  }
33
- const favMatch = pathname.match(/^\/([a-zA-Z0-9_-]+)\/favourites\/?$/);
37
+ const favMatch = pathname.match(favouritesRegex);
34
38
  if (favMatch?.[1]) {
35
39
  const username = favMatch[1];
36
40
  if (!isAnyOf(username, excludedPaths)) return [{
@@ -38,7 +42,7 @@ const deviantartHandler = {
38
42
  hint: composeHint("deviantart:favorites")
39
43
  }];
40
44
  }
41
- const folderMatch = pathname.match(/^\/([a-zA-Z0-9_-]+)\/gallery\/(\d+)(?:\/|$)/);
45
+ const folderMatch = pathname.match(folderRegex);
42
46
  if (folderMatch?.[1] && folderMatch?.[2]) {
43
47
  const username = folderMatch[1];
44
48
  const folderId = folderMatch[2];
@@ -47,7 +51,7 @@ const deviantartHandler = {
47
51
  hint: composeHint("deviantart:gallery")
48
52
  }];
49
53
  }
50
- const username = pathname.match(/^\/([a-zA-Z0-9_-]+)(?:\/gallery(?:\/all)?)?(?:\/|$)/)?.[1];
54
+ const username = pathname.match(profileRegex)?.[1];
51
55
  if (!username || isAnyOf(username, excludedPaths)) return [];
52
56
  const query = `by:${username} sort:time meta:all`;
53
57
  return [{
@@ -1,12 +1,14 @@
1
1
  const require_utils = require("../../../common/utils.cjs");
2
2
  //#region src/feeds/platform/handlers/douban.ts
3
+ const userRegex = /^\/people\/([^/]+)/;
4
+ const subjectRegex = /^\/subject\/(\d+)/;
3
5
  const doubanHandler = {
4
6
  match: (url) => {
5
7
  return require_utils.isHostOf(url, "douban.com") || require_utils.isSubdomainOf(url, "douban.com");
6
8
  },
7
9
  resolve: (url) => {
8
10
  const { pathname } = new URL(url);
9
- const userMatch = pathname.match(/^\/people\/([^/]+)/);
11
+ const userMatch = pathname.match(userRegex);
10
12
  if (userMatch?.[1]) {
11
13
  const user = userMatch[1];
12
14
  return [
@@ -24,7 +26,7 @@ const doubanHandler = {
24
26
  }
25
27
  ];
26
28
  }
27
- const subjectMatch = pathname.match(/^\/subject\/(\d+)/);
29
+ const subjectMatch = pathname.match(subjectRegex);
28
30
  if (subjectMatch?.[1]) return [{
29
31
  uri: `https://www.douban.com/feed/subject/${subjectMatch[1]}/reviews`,
30
32
  hint: require_utils.composeHint("douban:subjectReviews")
@@ -1,12 +1,14 @@
1
1
  import { composeHint, isHostOf, isSubdomainOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/douban.ts
3
+ const userRegex = /^\/people\/([^/]+)/;
4
+ const subjectRegex = /^\/subject\/(\d+)/;
3
5
  const doubanHandler = {
4
6
  match: (url) => {
5
7
  return isHostOf(url, "douban.com") || isSubdomainOf(url, "douban.com");
6
8
  },
7
9
  resolve: (url) => {
8
10
  const { pathname } = new URL(url);
9
- const userMatch = pathname.match(/^\/people\/([^/]+)/);
11
+ const userMatch = pathname.match(userRegex);
10
12
  if (userMatch?.[1]) {
11
13
  const user = userMatch[1];
12
14
  return [
@@ -24,7 +26,7 @@ const doubanHandler = {
24
26
  }
25
27
  ];
26
28
  }
27
- const subjectMatch = pathname.match(/^\/subject\/(\d+)/);
29
+ const subjectMatch = pathname.match(subjectRegex);
28
30
  if (subjectMatch?.[1]) return [{
29
31
  uri: `https://www.douban.com/feed/subject/${subjectMatch[1]}/reviews`,
30
32
  hint: composeHint("douban:subjectReviews")
@@ -1,5 +1,11 @@
1
1
  const require_utils = require("../../../common/utils.cjs");
2
2
  //#region src/feeds/platform/handlers/github.ts
3
+ const userRegex = /^\/([^/]+)\/?$/;
4
+ const repoRegex = /^\/([^/]+)\/([^/]+)/;
5
+ const wikiRegex = /\/wiki(\/|$)/;
6
+ const discussionsRegex = /\/discussions(\/|$)/;
7
+ const branchRegex = /^\/[^/]+\/[^/]+\/tree\/([^/]+)\/?$/;
8
+ const fileRegex = /^\/[^/]+\/[^/]+\/(?:blob|commits)\/([^/]+)\/(.+)/;
3
9
  const hosts = ["github.com", "www.github.com"];
4
10
  const excludedPaths = [
5
11
  "about",
@@ -55,12 +61,12 @@ const githubHandler = {
55
61
  resolve: (url) => {
56
62
  const { pathname } = new URL(url);
57
63
  const uris = [];
58
- const userMatch = pathname.match(/^\/([^/]+)\/?$/);
64
+ const userMatch = pathname.match(userRegex);
59
65
  if (userMatch?.[1] && !require_utils.isAnyOf(userMatch[1], excludedPaths)) return [{
60
66
  uri: `https://github.com/${userMatch[1]}.atom`,
61
67
  hint: require_utils.composeHint("github:activity")
62
68
  }];
63
- const repoMatch = pathname.match(/^\/([^/]+)\/([^/]+)/);
69
+ const repoMatch = pathname.match(repoRegex);
64
70
  const owner = repoMatch?.[1];
65
71
  const repo = repoMatch?.[2];
66
72
  if (!owner || !repo || require_utils.isAnyOf(owner, excludedPaths)) return [];
@@ -76,15 +82,15 @@ const githubHandler = {
76
82
  uri: `https://github.com/${owner}/${repo}/tags.atom`,
77
83
  hint: require_utils.composeHint("github:tags")
78
84
  });
79
- if (/\/wiki(\/|$)/.test(pathname)) uris.push({
85
+ if (wikiRegex.test(pathname)) uris.push({
80
86
  uri: `https://github.com/${owner}/${repo}/wiki.atom`,
81
87
  hint: require_utils.composeHint("github:wiki")
82
88
  });
83
- if (/\/discussions(\/|$)/.test(pathname)) uris.push({
89
+ if (discussionsRegex.test(pathname)) uris.push({
84
90
  uri: `https://github.com/${owner}/${repo}/discussions.atom`,
85
91
  hint: require_utils.composeHint("github:discussions")
86
92
  });
87
- const branchMatch = pathname.match(/^\/[^/]+\/[^/]+\/tree\/([^/]+)\/?$/);
93
+ const branchMatch = pathname.match(branchRegex);
88
94
  if (branchMatch?.[1]) {
89
95
  const branch = branchMatch[1];
90
96
  uris.push({
@@ -92,7 +98,7 @@ const githubHandler = {
92
98
  hint: require_utils.composeHint("github:branch-commits")
93
99
  });
94
100
  }
95
- const fileMatch = pathname.match(/^\/[^/]+\/[^/]+\/(?:blob|commits)\/([^/]+)\/(.+)/);
101
+ const fileMatch = pathname.match(fileRegex);
96
102
  if (fileMatch?.[1] && fileMatch?.[2]) {
97
103
  const branch = fileMatch[1];
98
104
  const filePath = fileMatch[2];
@@ -1,5 +1,11 @@
1
1
  import { composeHint, isAnyOf, isHostOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/github.ts
3
+ const userRegex = /^\/([^/]+)\/?$/;
4
+ const repoRegex = /^\/([^/]+)\/([^/]+)/;
5
+ const wikiRegex = /\/wiki(\/|$)/;
6
+ const discussionsRegex = /\/discussions(\/|$)/;
7
+ const branchRegex = /^\/[^/]+\/[^/]+\/tree\/([^/]+)\/?$/;
8
+ const fileRegex = /^\/[^/]+\/[^/]+\/(?:blob|commits)\/([^/]+)\/(.+)/;
3
9
  const hosts = ["github.com", "www.github.com"];
4
10
  const excludedPaths = [
5
11
  "about",
@@ -55,12 +61,12 @@ const githubHandler = {
55
61
  resolve: (url) => {
56
62
  const { pathname } = new URL(url);
57
63
  const uris = [];
58
- const userMatch = pathname.match(/^\/([^/]+)\/?$/);
64
+ const userMatch = pathname.match(userRegex);
59
65
  if (userMatch?.[1] && !isAnyOf(userMatch[1], excludedPaths)) return [{
60
66
  uri: `https://github.com/${userMatch[1]}.atom`,
61
67
  hint: composeHint("github:activity")
62
68
  }];
63
- const repoMatch = pathname.match(/^\/([^/]+)\/([^/]+)/);
69
+ const repoMatch = pathname.match(repoRegex);
64
70
  const owner = repoMatch?.[1];
65
71
  const repo = repoMatch?.[2];
66
72
  if (!owner || !repo || isAnyOf(owner, excludedPaths)) return [];
@@ -76,15 +82,15 @@ const githubHandler = {
76
82
  uri: `https://github.com/${owner}/${repo}/tags.atom`,
77
83
  hint: composeHint("github:tags")
78
84
  });
79
- if (/\/wiki(\/|$)/.test(pathname)) uris.push({
85
+ if (wikiRegex.test(pathname)) uris.push({
80
86
  uri: `https://github.com/${owner}/${repo}/wiki.atom`,
81
87
  hint: composeHint("github:wiki")
82
88
  });
83
- if (/\/discussions(\/|$)/.test(pathname)) uris.push({
89
+ if (discussionsRegex.test(pathname)) uris.push({
84
90
  uri: `https://github.com/${owner}/${repo}/discussions.atom`,
85
91
  hint: composeHint("github:discussions")
86
92
  });
87
- const branchMatch = pathname.match(/^\/[^/]+\/[^/]+\/tree\/([^/]+)\/?$/);
93
+ const branchMatch = pathname.match(branchRegex);
88
94
  if (branchMatch?.[1]) {
89
95
  const branch = branchMatch[1];
90
96
  uris.push({
@@ -92,7 +98,7 @@ const githubHandler = {
92
98
  hint: composeHint("github:branch-commits")
93
99
  });
94
100
  }
95
- const fileMatch = pathname.match(/^\/[^/]+\/[^/]+\/(?:blob|commits)\/([^/]+)\/(.+)/);
101
+ const fileMatch = pathname.match(fileRegex);
96
102
  if (fileMatch?.[1] && fileMatch?.[2]) {
97
103
  const branch = fileMatch[1];
98
104
  const filePath = fileMatch[2];
@@ -1,5 +1,8 @@
1
1
  const require_utils = require("../../../common/utils.cjs");
2
2
  //#region src/feeds/platform/handlers/githubGist.ts
3
+ const gistRegex = /^\/([^/]+)\/([a-f0-9]+)/;
4
+ const starredRegex = /^\/([^/]+)\/starred\/?$/;
5
+ const userRegex = /^\/([^/]+)\/?$/;
3
6
  const hosts = ["gist.github.com"];
4
7
  const excludedPaths = [
5
8
  "discover",
@@ -14,7 +17,7 @@ const githubGistHandler = {
14
17
  },
15
18
  resolve: (url) => {
16
19
  const { pathname } = new URL(url);
17
- const gistMatch = pathname.match(/^\/([^/]+)\/([a-f0-9]+)/);
20
+ const gistMatch = pathname.match(gistRegex);
18
21
  if (gistMatch?.[1] && gistMatch?.[2]) {
19
22
  const username = gistMatch[1];
20
23
  if (!require_utils.isAnyOf(username, excludedPaths)) return [{
@@ -23,12 +26,12 @@ const githubGistHandler = {
23
26
  }];
24
27
  return [];
25
28
  }
26
- const starredMatch = pathname.match(/^\/([^/]+)\/starred\/?$/);
29
+ const starredMatch = pathname.match(starredRegex);
27
30
  if (starredMatch?.[1] && !require_utils.isAnyOf(starredMatch[1], excludedPaths)) return [{
28
31
  uri: `https://gist.github.com/${starredMatch[1]}/starred.atom`,
29
32
  hint: require_utils.composeHint("github-gist:starred")
30
33
  }];
31
- const userMatch = pathname.match(/^\/([^/]+)\/?$/);
34
+ const userMatch = pathname.match(userRegex);
32
35
  if (userMatch?.[1] && !require_utils.isAnyOf(userMatch[1], excludedPaths)) return [{
33
36
  uri: `https://gist.github.com/${userMatch[1]}.atom`,
34
37
  hint: require_utils.composeHint("github-gist:gists")
@@ -1,5 +1,8 @@
1
1
  import { composeHint, isAnyOf, isHostOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/githubGist.ts
3
+ const gistRegex = /^\/([^/]+)\/([a-f0-9]+)/;
4
+ const starredRegex = /^\/([^/]+)\/starred\/?$/;
5
+ const userRegex = /^\/([^/]+)\/?$/;
3
6
  const hosts = ["gist.github.com"];
4
7
  const excludedPaths = [
5
8
  "discover",
@@ -14,7 +17,7 @@ const githubGistHandler = {
14
17
  },
15
18
  resolve: (url) => {
16
19
  const { pathname } = new URL(url);
17
- const gistMatch = pathname.match(/^\/([^/]+)\/([a-f0-9]+)/);
20
+ const gistMatch = pathname.match(gistRegex);
18
21
  if (gistMatch?.[1] && gistMatch?.[2]) {
19
22
  const username = gistMatch[1];
20
23
  if (!isAnyOf(username, excludedPaths)) return [{
@@ -23,12 +26,12 @@ const githubGistHandler = {
23
26
  }];
24
27
  return [];
25
28
  }
26
- const starredMatch = pathname.match(/^\/([^/]+)\/starred\/?$/);
29
+ const starredMatch = pathname.match(starredRegex);
27
30
  if (starredMatch?.[1] && !isAnyOf(starredMatch[1], excludedPaths)) return [{
28
31
  uri: `https://gist.github.com/${starredMatch[1]}/starred.atom`,
29
32
  hint: composeHint("github-gist:starred")
30
33
  }];
31
- const userMatch = pathname.match(/^\/([^/]+)\/?$/);
34
+ const userMatch = pathname.match(userRegex);
32
35
  if (userMatch?.[1] && !isAnyOf(userMatch[1], excludedPaths)) return [{
33
36
  uri: `https://gist.github.com/${userMatch[1]}.atom`,
34
37
  hint: composeHint("github-gist:gists")
@@ -24,9 +24,22 @@ const excludedPaths = [
24
24
  "sitemap",
25
25
  "-"
26
26
  ];
27
+ const isGitlabHtml = (content) => {
28
+ return require_utils.hasMetaContent(content, "og:site_name", "GitLab");
29
+ };
30
+ const isGitlabHeaders = (headers) => {
31
+ return headers.has("x-gitlab-meta");
32
+ };
27
33
  const gitlabHandler = {
28
- match: (url) => {
29
- return require_utils.isHostOf(url, hosts);
34
+ match: (url, content, headers) => {
35
+ if (require_utils.isHostOf(url, hosts)) return true;
36
+ try {
37
+ const { pathname } = new URL(url);
38
+ if (pathname.split("/").filter(Boolean).length === 0) return false;
39
+ if (content && isGitlabHtml(content)) return true;
40
+ if (headers && isGitlabHeaders(headers)) return true;
41
+ } catch {}
42
+ return false;
30
43
  },
31
44
  resolve: (url) => {
32
45
  const { origin, pathname } = new URL(url);
@@ -1,4 +1,4 @@
1
- import { composeHint, isAnyOf, isHostOf } from "../../../common/utils.js";
1
+ import { composeHint, hasMetaContent, isAnyOf, isHostOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/gitlab.ts
3
3
  const hosts = ["gitlab.com", "www.gitlab.com"];
4
4
  const excludedPaths = [
@@ -24,9 +24,22 @@ const excludedPaths = [
24
24
  "sitemap",
25
25
  "-"
26
26
  ];
27
+ const isGitlabHtml = (content) => {
28
+ return hasMetaContent(content, "og:site_name", "GitLab");
29
+ };
30
+ const isGitlabHeaders = (headers) => {
31
+ return headers.has("x-gitlab-meta");
32
+ };
27
33
  const gitlabHandler = {
28
- match: (url) => {
29
- return isHostOf(url, hosts);
34
+ match: (url, content, headers) => {
35
+ if (isHostOf(url, hosts)) return true;
36
+ try {
37
+ const { pathname } = new URL(url);
38
+ if (pathname.split("/").filter(Boolean).length === 0) return false;
39
+ if (content && isGitlabHtml(content)) return true;
40
+ if (headers && isGitlabHeaders(headers)) return true;
41
+ } catch {}
42
+ return false;
30
43
  },
31
44
  resolve: (url) => {
32
45
  const { origin, pathname } = new URL(url);
@@ -0,0 +1,46 @@
1
+ const require_utils = require("../../../common/utils.cjs");
2
+ //#region src/feeds/platform/handlers/lemmy.ts
3
+ const lemmyPoweredByRegex = /lemmy/i;
4
+ const isCommunityPath = (pathname) => {
5
+ const segments = pathname.split("/").filter(Boolean);
6
+ return segments.length >= 2 && segments[0] === "c";
7
+ };
8
+ const isUserPath = (pathname) => {
9
+ const segments = pathname.split("/").filter(Boolean);
10
+ return segments.length >= 2 && segments[0] === "u";
11
+ };
12
+ const isLemmyHtml = (content) => {
13
+ return require_utils.hasMetaContent(content, "generator", "Lemmy");
14
+ };
15
+ const isLemmyHeaders = (headers) => {
16
+ const poweredBy = headers.get("x-powered-by") ?? "";
17
+ return lemmyPoweredByRegex.test(poweredBy);
18
+ };
19
+ const lemmyHandler = {
20
+ match: (url, content, headers) => {
21
+ try {
22
+ const { pathname } = new URL(url);
23
+ if (!isCommunityPath(pathname) && !isUserPath(pathname)) return false;
24
+ if (content && isLemmyHtml(content)) return true;
25
+ if (headers && isLemmyHeaders(headers)) return true;
26
+ } catch {}
27
+ return false;
28
+ },
29
+ resolve: (url) => {
30
+ try {
31
+ const { origin, pathname } = new URL(url);
32
+ const segments = pathname.split("/").filter(Boolean);
33
+ if (isCommunityPath(pathname) && segments[1]) return [{
34
+ uri: `${origin}/feeds/c/${segments[1]}.xml`,
35
+ hint: require_utils.composeHint("lemmy:community")
36
+ }];
37
+ if (isUserPath(pathname) && segments[1]) return [{
38
+ uri: `${origin}/feeds/u/${segments[1]}.xml`,
39
+ hint: require_utils.composeHint("lemmy:user")
40
+ }];
41
+ } catch {}
42
+ return [];
43
+ }
44
+ };
45
+ //#endregion
46
+ exports.lemmyHandler = lemmyHandler;
@@ -0,0 +1,46 @@
1
+ import { composeHint, hasMetaContent } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/lemmy.ts
3
+ const lemmyPoweredByRegex = /lemmy/i;
4
+ const isCommunityPath = (pathname) => {
5
+ const segments = pathname.split("/").filter(Boolean);
6
+ return segments.length >= 2 && segments[0] === "c";
7
+ };
8
+ const isUserPath = (pathname) => {
9
+ const segments = pathname.split("/").filter(Boolean);
10
+ return segments.length >= 2 && segments[0] === "u";
11
+ };
12
+ const isLemmyHtml = (content) => {
13
+ return hasMetaContent(content, "generator", "Lemmy");
14
+ };
15
+ const isLemmyHeaders = (headers) => {
16
+ const poweredBy = headers.get("x-powered-by") ?? "";
17
+ return lemmyPoweredByRegex.test(poweredBy);
18
+ };
19
+ const lemmyHandler = {
20
+ match: (url, content, headers) => {
21
+ try {
22
+ const { pathname } = new URL(url);
23
+ if (!isCommunityPath(pathname) && !isUserPath(pathname)) return false;
24
+ if (content && isLemmyHtml(content)) return true;
25
+ if (headers && isLemmyHeaders(headers)) return true;
26
+ } catch {}
27
+ return false;
28
+ },
29
+ resolve: (url) => {
30
+ try {
31
+ const { origin, pathname } = new URL(url);
32
+ const segments = pathname.split("/").filter(Boolean);
33
+ if (isCommunityPath(pathname) && segments[1]) return [{
34
+ uri: `${origin}/feeds/c/${segments[1]}.xml`,
35
+ hint: composeHint("lemmy:community")
36
+ }];
37
+ if (isUserPath(pathname) && segments[1]) return [{
38
+ uri: `${origin}/feeds/u/${segments[1]}.xml`,
39
+ hint: composeHint("lemmy:user")
40
+ }];
41
+ } catch {}
42
+ return [];
43
+ }
44
+ };
45
+ //#endregion
46
+ export { lemmyHandler };
@@ -1,9 +1,11 @@
1
1
  const require_utils = require("../../../common/utils.cjs");
2
2
  const require_mastodon = require("../../../favicons/platform/handlers/mastodon.cjs");
3
3
  //#region src/feeds/platform/handlers/mastodon.ts
4
+ const profilePathRegex = /^\/@([^/]+)/;
5
+ const tagPathRegex = /^\/tags\/([^/]+)/;
4
6
  const isProfilePath = (pathname) => {
5
7
  const segments = pathname.split("/").filter(Boolean);
6
- return segments.length >= 1 && segments[0].startsWith("@");
8
+ return segments.length > 0 && segments[0].startsWith("@");
7
9
  };
8
10
  const isTagPath = (pathname) => {
9
11
  const segments = pathname.split("/").filter(Boolean);
@@ -22,12 +24,12 @@ const mastodonHandler = {
22
24
  resolve: (url) => {
23
25
  try {
24
26
  const { origin, pathname } = new URL(url);
25
- const userMatch = pathname.match(/^\/@([^/]+)/);
27
+ const userMatch = pathname.match(profilePathRegex);
26
28
  if (userMatch?.[1]) return [{
27
29
  uri: `${origin}/@${userMatch[1]}.rss`,
28
30
  hint: require_utils.composeHint("mastodon:posts")
29
31
  }];
30
- const tagMatch = pathname.match(/^\/tags\/([^/]+)/);
32
+ const tagMatch = pathname.match(tagPathRegex);
31
33
  if (tagMatch?.[1]) return [{
32
34
  uri: `${origin}/tags/${tagMatch[1]}.rss`,
33
35
  hint: require_utils.composeHint("mastodon:tag")
@@ -1,9 +1,11 @@
1
1
  import { composeHint } from "../../../common/utils.js";
2
2
  import { isMastodonHeaders, isMastodonHtml } from "../../../favicons/platform/handlers/mastodon.js";
3
3
  //#region src/feeds/platform/handlers/mastodon.ts
4
+ const profilePathRegex = /^\/@([^/]+)/;
5
+ const tagPathRegex = /^\/tags\/([^/]+)/;
4
6
  const isProfilePath = (pathname) => {
5
7
  const segments = pathname.split("/").filter(Boolean);
6
- return segments.length >= 1 && segments[0].startsWith("@");
8
+ return segments.length > 0 && segments[0].startsWith("@");
7
9
  };
8
10
  const isTagPath = (pathname) => {
9
11
  const segments = pathname.split("/").filter(Boolean);
@@ -22,12 +24,12 @@ const mastodonHandler = {
22
24
  resolve: (url) => {
23
25
  try {
24
26
  const { origin, pathname } = new URL(url);
25
- const userMatch = pathname.match(/^\/@([^/]+)/);
27
+ const userMatch = pathname.match(profilePathRegex);
26
28
  if (userMatch?.[1]) return [{
27
29
  uri: `${origin}/@${userMatch[1]}.rss`,
28
30
  hint: composeHint("mastodon:posts")
29
31
  }];
30
- const tagMatch = pathname.match(/^\/tags\/([^/]+)/);
32
+ const tagMatch = pathname.match(tagPathRegex);
31
33
  if (tagMatch?.[1]) return [{
32
34
  uri: `${origin}/tags/${tagMatch[1]}.rss`,
33
35
  hint: composeHint("mastodon:tag")
@@ -1,5 +1,10 @@
1
1
  const require_utils = require("../../../common/utils.cjs");
2
2
  //#region src/feeds/platform/handlers/medium.ts
3
+ const userPathRegex = /^\/@([^/]+)/;
4
+ const tagPathRegex = /^\/tag\/([^/]+)/;
5
+ const publicationTagPathRegex = /^\/([^/@][^/]+)\/tagged\/([^/]+)/;
6
+ const publicationPathRegex = /^\/([^/@][^/]+)/;
7
+ const subdomainTagPathRegex = /^\/tagged\/([^/]+)/;
3
8
  const hosts = ["medium.com", "www.medium.com"];
4
9
  const excludedPaths = [
5
10
  "search",
@@ -16,17 +21,17 @@ const mediumHandler = {
16
21
  const { hostname, pathname } = new URL(url);
17
22
  const lowerHostname = hostname.toLowerCase();
18
23
  if (hosts.includes(lowerHostname)) {
19
- const userMatch = pathname.match(/^\/@([^/]+)/);
24
+ const userMatch = pathname.match(userPathRegex);
20
25
  if (userMatch?.[1]) return [{
21
26
  uri: `https://medium.com/feed/@${userMatch[1]}`,
22
27
  hint: require_utils.composeHint("medium:posts")
23
28
  }];
24
- const tagMatch = pathname.match(/^\/tag\/([^/]+)/);
29
+ const tagMatch = pathname.match(tagPathRegex);
25
30
  if (tagMatch?.[1]) return [{
26
31
  uri: `https://medium.com/feed/tag/${tagMatch[1]}`,
27
32
  hint: require_utils.composeHint("medium:tag")
28
33
  }];
29
- const pubTagMatch = pathname.match(/^\/([^/@][^/]+)\/tagged\/([^/]+)/);
34
+ const pubTagMatch = pathname.match(publicationTagPathRegex);
30
35
  if (pubTagMatch?.[1] && pubTagMatch?.[2]) {
31
36
  const publication = pubTagMatch[1];
32
37
  const tag = pubTagMatch[2];
@@ -35,7 +40,7 @@ const mediumHandler = {
35
40
  hint: require_utils.composeHint("medium:tagged")
36
41
  }];
37
42
  }
38
- const pubMatch = pathname.match(/^\/([^/@][^/]+)/);
43
+ const pubMatch = pathname.match(publicationPathRegex);
39
44
  if (pubMatch?.[1]) {
40
45
  const publication = pubMatch[1];
41
46
  if (!require_utils.isAnyOf(publication, excludedPaths)) return [{
@@ -46,7 +51,7 @@ const mediumHandler = {
46
51
  }
47
52
  if (lowerHostname.endsWith(".medium.com") && lowerHostname !== "medium.com" && lowerHostname !== "www.medium.com") {
48
53
  const subdomain = lowerHostname.replace(".medium.com", "");
49
- const tagMatch = pathname.match(/^\/tagged\/([^/]+)/);
54
+ const tagMatch = pathname.match(subdomainTagPathRegex);
50
55
  if (tagMatch?.[1]) return [{
51
56
  uri: `https://medium.com/feed/${subdomain}/tagged/${tagMatch[1]}`,
52
57
  hint: require_utils.composeHint("medium:tagged")
@@ -1,5 +1,10 @@
1
1
  import { composeHint, isAnyOf, isHostOf, isSubdomainOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/medium.ts
3
+ const userPathRegex = /^\/@([^/]+)/;
4
+ const tagPathRegex = /^\/tag\/([^/]+)/;
5
+ const publicationTagPathRegex = /^\/([^/@][^/]+)\/tagged\/([^/]+)/;
6
+ const publicationPathRegex = /^\/([^/@][^/]+)/;
7
+ const subdomainTagPathRegex = /^\/tagged\/([^/]+)/;
3
8
  const hosts = ["medium.com", "www.medium.com"];
4
9
  const excludedPaths = [
5
10
  "search",
@@ -16,17 +21,17 @@ const mediumHandler = {
16
21
  const { hostname, pathname } = new URL(url);
17
22
  const lowerHostname = hostname.toLowerCase();
18
23
  if (hosts.includes(lowerHostname)) {
19
- const userMatch = pathname.match(/^\/@([^/]+)/);
24
+ const userMatch = pathname.match(userPathRegex);
20
25
  if (userMatch?.[1]) return [{
21
26
  uri: `https://medium.com/feed/@${userMatch[1]}`,
22
27
  hint: composeHint("medium:posts")
23
28
  }];
24
- const tagMatch = pathname.match(/^\/tag\/([^/]+)/);
29
+ const tagMatch = pathname.match(tagPathRegex);
25
30
  if (tagMatch?.[1]) return [{
26
31
  uri: `https://medium.com/feed/tag/${tagMatch[1]}`,
27
32
  hint: composeHint("medium:tag")
28
33
  }];
29
- const pubTagMatch = pathname.match(/^\/([^/@][^/]+)\/tagged\/([^/]+)/);
34
+ const pubTagMatch = pathname.match(publicationTagPathRegex);
30
35
  if (pubTagMatch?.[1] && pubTagMatch?.[2]) {
31
36
  const publication = pubTagMatch[1];
32
37
  const tag = pubTagMatch[2];
@@ -35,7 +40,7 @@ const mediumHandler = {
35
40
  hint: composeHint("medium:tagged")
36
41
  }];
37
42
  }
38
- const pubMatch = pathname.match(/^\/([^/@][^/]+)/);
43
+ const pubMatch = pathname.match(publicationPathRegex);
39
44
  if (pubMatch?.[1]) {
40
45
  const publication = pubMatch[1];
41
46
  if (!isAnyOf(publication, excludedPaths)) return [{
@@ -46,7 +51,7 @@ const mediumHandler = {
46
51
  }
47
52
  if (lowerHostname.endsWith(".medium.com") && lowerHostname !== "medium.com" && lowerHostname !== "www.medium.com") {
48
53
  const subdomain = lowerHostname.replace(".medium.com", "");
49
- const tagMatch = pathname.match(/^\/tagged\/([^/]+)/);
54
+ const tagMatch = pathname.match(subdomainTagPathRegex);
50
55
  if (tagMatch?.[1]) return [{
51
56
  uri: `https://medium.com/feed/${subdomain}/tagged/${tagMatch[1]}`,
52
57
  hint: composeHint("medium:tagged")