feedscout 1.8.1 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +1 -1
  2. package/dist/common/discover/utils.d.ts +2 -3
  3. package/dist/common/locales.js +214 -10
  4. package/dist/common/types.d.ts +2 -1
  5. package/dist/common/uris/feed/types.d.ts +2 -2
  6. package/dist/common/uris/html/types.d.ts +4 -4
  7. package/dist/common/utils.d.ts +6 -4
  8. package/dist/common/utils.js +12 -3
  9. package/dist/feeds/defaults.d.ts +2 -1
  10. package/dist/feeds/defaults.js +116 -9
  11. package/dist/feeds/extractors.js +2 -2
  12. package/dist/feeds/platform/handlers/acast.js +26 -0
  13. package/dist/feeds/platform/handlers/ameblo.js +36 -0
  14. package/dist/feeds/platform/handlers/applePodcasts.js +1 -1
  15. package/dist/feeds/platform/handlers/arena.js +42 -0
  16. package/dist/feeds/platform/handlers/artstation.js +52 -0
  17. package/dist/feeds/platform/handlers/audioboom.js +23 -0
  18. package/dist/feeds/platform/handlers/bearblog.js +45 -0
  19. package/dist/feeds/platform/handlers/behance.js +7 -0
  20. package/dist/feeds/platform/handlers/blogspot.js +38 -2
  21. package/dist/feeds/platform/handlers/bookwyrm.js +48 -0
  22. package/dist/feeds/platform/handlers/buttondown.js +43 -0
  23. package/dist/feeds/platform/handlers/buzzsprout.js +22 -0
  24. package/dist/feeds/platform/handlers/codeberg.js +5 -5
  25. package/dist/feeds/platform/handlers/dailymotion.js +16 -0
  26. package/dist/feeds/platform/handlers/deviantart.js +25 -6
  27. package/dist/feeds/platform/handlers/devto.js +8 -0
  28. package/dist/feeds/platform/handlers/discourse.js +70 -0
  29. package/dist/feeds/platform/handlers/dreamwidth.js +48 -0
  30. package/dist/feeds/platform/handlers/exblog.js +35 -0
  31. package/dist/feeds/platform/handlers/fireside.js +24 -0
  32. package/dist/feeds/platform/handlers/friendica.js +44 -0
  33. package/dist/feeds/platform/handlers/ghost.js +30 -0
  34. package/dist/feeds/platform/handlers/github.js +6 -0
  35. package/dist/feeds/platform/handlers/githubGist.js +16 -5
  36. package/dist/feeds/platform/handlers/gitlab.js +31 -13
  37. package/dist/feeds/platform/handlers/goodreads.js +18 -8
  38. package/dist/feeds/platform/handlers/hackernews.js +21 -0
  39. package/dist/feeds/platform/handlers/hashnode.js +1 -1
  40. package/dist/feeds/platform/handlers/hatenablog.js +4 -1
  41. package/dist/feeds/platform/handlers/hearthis.js +32 -0
  42. package/dist/feeds/platform/handlers/heyWorld.js +18 -0
  43. package/dist/feeds/platform/handlers/insanejournal.js +69 -0
  44. package/dist/feeds/platform/handlers/itchio.js +24 -1
  45. package/dist/feeds/platform/handlers/lemmy.js +46 -4
  46. package/dist/feeds/platform/handlers/letterboxd.js +4 -0
  47. package/dist/feeds/platform/handlers/libsyn.js +25 -0
  48. package/dist/feeds/platform/handlers/listed.js +20 -0
  49. package/dist/feeds/platform/handlers/livejournal.js +68 -0
  50. package/dist/feeds/platform/handlers/mastodon.js +32 -0
  51. package/dist/feeds/platform/handlers/mataroa.js +16 -0
  52. package/dist/feeds/platform/handlers/medium.js +2 -2
  53. package/dist/feeds/platform/handlers/microblog.js +55 -0
  54. package/dist/feeds/platform/handlers/misskey.js +40 -0
  55. package/dist/feeds/platform/handlers/myanimelist.js +43 -0
  56. package/dist/feeds/platform/handlers/naverBlog.js +21 -0
  57. package/dist/feeds/platform/handlers/nebula.js +68 -0
  58. package/dist/feeds/platform/handlers/note.js +53 -0
  59. package/dist/feeds/platform/handlers/observable.js +34 -0
  60. package/dist/feeds/platform/handlers/odysee.js +20 -0
  61. package/dist/feeds/platform/handlers/pagecord.js +16 -0
  62. package/dist/feeds/platform/handlers/paragraph.js +1 -2
  63. package/dist/feeds/platform/handlers/pika.js +35 -0
  64. package/dist/feeds/platform/handlers/pinterest.js +13 -0
  65. package/dist/feeds/platform/handlers/pixelfed.js +46 -0
  66. package/dist/feeds/platform/handlers/pleroma.js +34 -0
  67. package/dist/feeds/platform/handlers/podbean.js +29 -0
  68. package/dist/feeds/platform/handlers/podigee.js +29 -0
  69. package/dist/feeds/platform/handlers/posthaven.js +24 -0
  70. package/dist/feeds/platform/handlers/prose.js +26 -0
  71. package/dist/feeds/platform/handlers/qiita.js +58 -0
  72. package/dist/feeds/platform/handlers/reddit.js +83 -9
  73. package/dist/feeds/platform/handlers/rssCom.js +20 -0
  74. package/dist/feeds/platform/handlers/seesaa.js +22 -0
  75. package/dist/feeds/platform/handlers/sourceforge.js +37 -4
  76. package/dist/feeds/platform/handlers/spreaker.js +21 -0
  77. package/dist/feeds/platform/handlers/stackExchange.js +26 -2
  78. package/dist/feeds/platform/handlers/steam.js +7 -0
  79. package/dist/feeds/platform/handlers/tildes.js +41 -0
  80. package/dist/feeds/platform/handlers/tistory.js +16 -0
  81. package/dist/feeds/platform/handlers/transistor.js +29 -0
  82. package/dist/feeds/platform/handlers/velog.js +24 -0
  83. package/dist/feeds/platform/handlers/vimeo.js +6 -0
  84. package/dist/feeds/platform/handlers/weblogLol.js +26 -0
  85. package/dist/feeds/platform/handlers/weebly.js +25 -0
  86. package/dist/feeds/platform/handlers/wordpress.js +173 -28
  87. package/dist/feeds/platform/handlers/writeas.js +51 -0
  88. package/dist/feeds/platform/handlers/ximalaya.js +1 -1
  89. package/dist/feeds/platform/handlers/youtube.js +4 -1
  90. package/dist/feeds/platform/handlers/zenn.js +54 -0
  91. package/dist/feeds.d.ts +2 -2
  92. package/dist/feeds.js +2 -2
  93. package/dist/utils.d.ts +2 -1
  94. package/package.json +24 -64
  95. package/dist/blogrolls/defaults.cjs +0 -51
  96. package/dist/blogrolls/defaults.d.cts +0 -17
  97. package/dist/blogrolls/extractors.cjs +0 -21
  98. package/dist/blogrolls/extractors.d.cts +0 -7
  99. package/dist/blogrolls/index.cjs +0 -25
  100. package/dist/blogrolls/index.d.cts +0 -7
  101. package/dist/blogrolls/types.d.cts +0 -6
  102. package/dist/blogrolls.cjs +0 -13
  103. package/dist/blogrolls.d.cts +0 -4
  104. package/dist/common/discover/defaults.cjs +0 -46
  105. package/dist/common/discover/defaults.d.cts +0 -6
  106. package/dist/common/discover/index.cjs +0 -105
  107. package/dist/common/discover/utils.cjs +0 -101
  108. package/dist/common/discover/utils.d.cts +0 -6
  109. package/dist/common/locales.cjs +0 -144
  110. package/dist/common/types.cjs +0 -10
  111. package/dist/common/types.d.cts +0 -89
  112. package/dist/common/uris/feed/index.cjs +0 -12
  113. package/dist/common/uris/feed/index.d.cts +0 -6
  114. package/dist/common/uris/feed/types.d.cts +0 -9
  115. package/dist/common/uris/guess/index.cjs +0 -8
  116. package/dist/common/uris/guess/index.d.cts +0 -7
  117. package/dist/common/uris/guess/types.d.cts +0 -10
  118. package/dist/common/uris/guess/utils.cjs +0 -42
  119. package/dist/common/uris/guess/utils.d.cts +0 -8
  120. package/dist/common/uris/headers/index.cjs +0 -26
  121. package/dist/common/uris/headers/index.d.cts +0 -6
  122. package/dist/common/uris/headers/types.d.cts +0 -9
  123. package/dist/common/uris/html/handlers.cjs +0 -45
  124. package/dist/common/uris/html/index.cjs +0 -19
  125. package/dist/common/uris/html/index.d.cts +0 -6
  126. package/dist/common/uris/html/types.d.cts +0 -12
  127. package/dist/common/uris/index.cjs +0 -32
  128. package/dist/common/uris/platform/index.cjs +0 -10
  129. package/dist/common/uris/platform/index.d.cts +0 -7
  130. package/dist/common/uris/platform/types.d.cts +0 -13
  131. package/dist/common/utils.cjs +0 -89
  132. package/dist/common/utils.d.cts +0 -10
  133. package/dist/favicons/defaults.cjs +0 -65
  134. package/dist/favicons/defaults.d.cts +0 -18
  135. package/dist/favicons/extractors.cjs +0 -25
  136. package/dist/favicons/extractors.d.cts +0 -7
  137. package/dist/favicons/index.cjs +0 -29
  138. package/dist/favicons/index.d.cts +0 -7
  139. package/dist/favicons/platform/handlers/bluesky.cjs +0 -27
  140. package/dist/favicons/platform/handlers/codeberg.cjs +0 -19
  141. package/dist/favicons/platform/handlers/deviantart.cjs +0 -29
  142. package/dist/favicons/platform/handlers/devto.cjs +0 -31
  143. package/dist/favicons/platform/handlers/github.cjs +0 -19
  144. package/dist/favicons/platform/handlers/githubGist.cjs +0 -19
  145. package/dist/favicons/platform/handlers/gitlab.cjs +0 -38
  146. package/dist/favicons/platform/handlers/lobsters.cjs +0 -21
  147. package/dist/favicons/platform/handlers/mastodon.cjs +0 -40
  148. package/dist/favicons/platform/handlers/reddit.cjs +0 -42
  149. package/dist/favicons/platform/handlers/sourceforge.cjs +0 -21
  150. package/dist/favicons/platform/handlers/tumblr.cjs +0 -16
  151. package/dist/favicons/types.d.cts +0 -4
  152. package/dist/favicons/utils.cjs +0 -10
  153. package/dist/favicons.cjs +0 -12
  154. package/dist/favicons.d.cts +0 -4
  155. package/dist/feeds/defaults.cjs +0 -178
  156. package/dist/feeds/defaults.d.cts +0 -20
  157. package/dist/feeds/extractors.cjs +0 -46
  158. package/dist/feeds/extractors.d.cts +0 -7
  159. package/dist/feeds/index.cjs +0 -26
  160. package/dist/feeds/index.d.cts +0 -7
  161. package/dist/feeds/platform/handlers/applePodcasts.cjs +0 -26
  162. package/dist/feeds/platform/handlers/behance.cjs +0 -49
  163. package/dist/feeds/platform/handlers/blogspot.cjs +0 -36
  164. package/dist/feeds/platform/handlers/bluesky.cjs +0 -20
  165. package/dist/feeds/platform/handlers/codeberg.cjs +0 -69
  166. package/dist/feeds/platform/handlers/csdn.cjs +0 -20
  167. package/dist/feeds/platform/handlers/dailymotion.cjs +0 -70
  168. package/dist/feeds/platform/handlers/deviantart.cjs +0 -66
  169. package/dist/feeds/platform/handlers/devto.cjs +0 -50
  170. package/dist/feeds/platform/handlers/douban.cjs +0 -56
  171. package/dist/feeds/platform/handlers/github.cjs +0 -116
  172. package/dist/feeds/platform/handlers/githubGist.cjs +0 -45
  173. package/dist/feeds/platform/handlers/gitlab.cjs +0 -80
  174. package/dist/feeds/platform/handlers/goodreads.cjs +0 -39
  175. package/dist/feeds/platform/handlers/hashnode.cjs +0 -16
  176. package/dist/feeds/platform/handlers/hatenablog.cjs +0 -53
  177. package/dist/feeds/platform/handlers/itchio.cjs +0 -88
  178. package/dist/feeds/platform/handlers/kickstarter.cjs +0 -22
  179. package/dist/feeds/platform/handlers/lemmy.cjs +0 -46
  180. package/dist/feeds/platform/handlers/letterboxd.cjs +0 -42
  181. package/dist/feeds/platform/handlers/lobsters.cjs +0 -57
  182. package/dist/feeds/platform/handlers/mastodon.cjs +0 -42
  183. package/dist/feeds/platform/handlers/medium.cjs +0 -68
  184. package/dist/feeds/platform/handlers/paragraph.cjs +0 -21
  185. package/dist/feeds/platform/handlers/pinterest.cjs +0 -44
  186. package/dist/feeds/platform/handlers/producthunt.cjs +0 -29
  187. package/dist/feeds/platform/handlers/reddit.cjs +0 -75
  188. package/dist/feeds/platform/handlers/soundcloud.cjs +0 -39
  189. package/dist/feeds/platform/handlers/sourceforge.cjs +0 -20
  190. package/dist/feeds/platform/handlers/stackExchange.cjs +0 -40
  191. package/dist/feeds/platform/handlers/steam.cjs +0 -28
  192. package/dist/feeds/platform/handlers/substack.cjs +0 -23
  193. package/dist/feeds/platform/handlers/tumblr.cjs +0 -24
  194. package/dist/feeds/platform/handlers/v2ex.cjs +0 -35
  195. package/dist/feeds/platform/handlers/vimeo.cjs +0 -69
  196. package/dist/feeds/platform/handlers/wordpress.cjs +0 -64
  197. package/dist/feeds/platform/handlers/wpengine.cjs +0 -10
  198. package/dist/feeds/platform/handlers/ximalaya.cjs +0 -20
  199. package/dist/feeds/platform/handlers/youtube.cjs +0 -94
  200. package/dist/feeds/types.d.cts +0 -9
  201. package/dist/feeds.cjs +0 -15
  202. package/dist/feeds.d.cts +0 -4
  203. package/dist/hubs/discover/index.cjs +0 -30
  204. package/dist/hubs/discover/index.d.cts +0 -7
  205. package/dist/hubs/discover/types.d.cts +0 -15
  206. package/dist/hubs/feed/index.cjs +0 -32
  207. package/dist/hubs/headers/index.cjs +0 -17
  208. package/dist/hubs/html/index.cjs +0 -28
  209. package/dist/hubs.cjs +0 -0
  210. package/dist/hubs.d.cts +0 -2
  211. package/dist/index.cjs +0 -13
  212. package/dist/index.d.cts +0 -8
  213. package/dist/methods.cjs +0 -15
  214. package/dist/methods.d.cts +0 -7
  215. package/dist/utils.cjs +0 -9
  216. package/dist/utils.d.cts +0 -2
@@ -0,0 +1,48 @@
1
+ import { composeHint, isHostOf, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/dreamwidth.ts
3
+ const usersPathRegex = /^\/(?:users\/|~)([^/]+)/;
4
+ const tagRegex = /^\/tag\/([^/]+)/;
5
+ const dreamwidthHandler = {
6
+ match: (url) => {
7
+ if (!isSubdomainOf(url, "dreamwidth.org")) return false;
8
+ if (isHostOf(url, ["www.dreamwidth.org", "dreamwidth.org"])) return usersPathRegex.test(new URL(url).pathname);
9
+ return true;
10
+ },
11
+ resolve: (url) => {
12
+ const { origin, pathname } = new URL(url);
13
+ const uris = [];
14
+ let userOrigin = origin;
15
+ if (isHostOf(url, ["www.dreamwidth.org", "dreamwidth.org"])) {
16
+ const userMatch = pathname.match(usersPathRegex);
17
+ if (userMatch?.[1]) userOrigin = `https://${userMatch[1]}.dreamwidth.org`;
18
+ else return uris;
19
+ }
20
+ const tagMatch = pathname.match(tagRegex);
21
+ if (tagMatch?.[1]) {
22
+ const tag = encodeURIComponent(tagMatch[1]);
23
+ uris.push({
24
+ uri: `${userOrigin}/data/rss?tag=${tag}`,
25
+ hint: composeHint("dreamwidth:posts-tag-rss")
26
+ });
27
+ uris.push({
28
+ uri: `${userOrigin}/data/atom?tag=${tag}`,
29
+ hint: composeHint("dreamwidth:posts-tag-atom")
30
+ });
31
+ }
32
+ uris.push({
33
+ uri: `${userOrigin}/data/rss`,
34
+ hint: composeHint("dreamwidth:posts-rss")
35
+ });
36
+ uris.push({
37
+ uri: `${userOrigin}/data/atom`,
38
+ hint: composeHint("dreamwidth:posts-atom")
39
+ });
40
+ uris.push({
41
+ uri: `${userOrigin}/data/userpics`,
42
+ hint: composeHint("dreamwidth:userpics-atom")
43
+ });
44
+ return uris;
45
+ }
46
+ };
47
+ //#endregion
48
+ export { dreamwidthHandler };
@@ -0,0 +1,35 @@
1
+ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/exblog.ts
3
+ const categoryRegex = /^\/i(\d+)/;
4
+ const exblogHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "exblog.jp");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin, pathname } = new URL(url);
10
+ const uris = [];
11
+ const categoryMatch = pathname.match(categoryRegex);
12
+ if (categoryMatch?.[1]) {
13
+ const categoryId = categoryMatch[1];
14
+ uris.push({
15
+ uri: `${origin}/i${categoryId}/index.xml`,
16
+ hint: composeHint("exblog:category-rss")
17
+ });
18
+ uris.push({
19
+ uri: `${origin}/i${categoryId}/atom.xml`,
20
+ hint: composeHint("exblog:category-atom")
21
+ });
22
+ }
23
+ uris.push({
24
+ uri: `${origin}/index.xml`,
25
+ hint: composeHint("exblog:posts-rss")
26
+ });
27
+ uris.push({
28
+ uri: `${origin}/atom.xml`,
29
+ hint: composeHint("exblog:posts-atom")
30
+ });
31
+ return uris;
32
+ }
33
+ };
34
+ //#endregion
35
+ export { exblogHandler };
@@ -0,0 +1,24 @@
1
+ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/fireside.ts
3
+ const domainSuffix = /\.fireside\.fm$/i;
4
+ const firesideHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "fireside.fm");
7
+ },
8
+ resolve: (url) => {
9
+ const { hostname } = new URL(url);
10
+ const slug = hostname.replace(domainSuffix, "");
11
+ const uris = [];
12
+ uris.push({
13
+ uri: `https://feeds.fireside.fm/${slug}/rss`,
14
+ hint: composeHint("fireside:podcast-rss")
15
+ });
16
+ uris.push({
17
+ uri: `https://${slug}.fireside.fm/json`,
18
+ hint: composeHint("fireside:podcast-json")
19
+ });
20
+ return uris;
21
+ }
22
+ };
23
+ //#endregion
24
+ export { firesideHandler };
@@ -0,0 +1,44 @@
1
+ import { composeHint, hasMetaContent } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/friendica.ts
3
+ const profileRegex = /^\/profile\/([^/]+)/;
4
+ const isFriendicaHtml = (content) => {
5
+ return hasMetaContent(content, "generator", "Friendica");
6
+ };
7
+ const friendicaHandler = {
8
+ match: (url, content) => {
9
+ try {
10
+ if (!content || !isFriendicaHtml(content)) return false;
11
+ const { pathname } = new URL(url);
12
+ return profileRegex.test(pathname);
13
+ } catch {}
14
+ return false;
15
+ },
16
+ resolve: (url) => {
17
+ try {
18
+ const { origin, pathname } = new URL(url);
19
+ const match = pathname.match(profileRegex);
20
+ if (!match?.[1]) return [];
21
+ return [
22
+ {
23
+ uri: `${origin}/feed/${match[1]}`,
24
+ hint: composeHint("friendica:posts")
25
+ },
26
+ {
27
+ uri: `${origin}/feed/${match[1]}/comments`,
28
+ hint: composeHint("friendica:comments")
29
+ },
30
+ {
31
+ uri: `${origin}/feed/${match[1]}/replies`,
32
+ hint: composeHint("friendica:replies")
33
+ },
34
+ {
35
+ uri: `${origin}/feed/${match[1]}/activity`,
36
+ hint: composeHint("friendica:activity")
37
+ }
38
+ ];
39
+ } catch {}
40
+ return [];
41
+ }
42
+ };
43
+ //#endregion
44
+ export { friendicaHandler };
@@ -0,0 +1,30 @@
1
+ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/ghost.ts
3
+ const tagRegex = /^\/tag\/([^/]+)/;
4
+ const authorRegex = /^\/author\/([^/]+)/;
5
+ const ghostHandler = {
6
+ match: (url) => {
7
+ return isSubdomainOf(url, "ghost.io");
8
+ },
9
+ resolve: (url) => {
10
+ const { origin, pathname } = new URL(url);
11
+ const uris = [];
12
+ const tagMatch = pathname.match(tagRegex);
13
+ if (tagMatch?.[1]) uris.push({
14
+ uri: `${origin}/tag/${tagMatch[1]}/rss/`,
15
+ hint: composeHint("ghost:tag")
16
+ });
17
+ const authorMatch = pathname.match(authorRegex);
18
+ if (authorMatch?.[1]) uris.push({
19
+ uri: `${origin}/author/${authorMatch[1]}/rss/`,
20
+ hint: composeHint("ghost:author")
21
+ });
22
+ uris.push({
23
+ uri: `${origin}/rss/`,
24
+ hint: composeHint("ghost:blog")
25
+ });
26
+ return uris;
27
+ }
28
+ };
29
+ //#endregion
30
+ export { ghostHandler };
@@ -4,6 +4,7 @@ const userRegex = /^\/([^/]+)\/?$/;
4
4
  const repoRegex = /^\/([^/]+)\/([^/]+)/;
5
5
  const wikiRegex = /\/wiki(\/|$)/;
6
6
  const discussionsRegex = /\/discussions(\/|$)/;
7
+ const discussionCategoryRegex = /\/discussions\/categories\/([^/]+)/;
7
8
  const branchRegex = /^\/[^/]+\/[^/]+\/tree\/([^/]+)\/?$/;
8
9
  const fileRegex = /^\/[^/]+\/[^/]+\/(?:blob|commits)\/([^/]+)\/(.+)/;
9
10
  const hosts = ["github.com", "www.github.com"];
@@ -90,6 +91,11 @@ const githubHandler = {
90
91
  uri: `https://github.com/${owner}/${repo}/discussions.atom`,
91
92
  hint: composeHint("github:discussions")
92
93
  });
94
+ const discussionCategoryMatch = pathname.match(discussionCategoryRegex);
95
+ if (discussionCategoryMatch?.[1]) uris.push({
96
+ uri: `https://github.com/${owner}/${repo}/discussions/categories/${discussionCategoryMatch[1]}.atom`,
97
+ hint: composeHint("github:discussion-category")
98
+ });
93
99
  const branchMatch = pathname.match(branchRegex);
94
100
  if (branchMatch?.[1]) {
95
101
  const branch = branchMatch[1];
@@ -2,7 +2,9 @@ import { composeHint, isAnyOf, isHostOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/githubGist.ts
3
3
  const gistRegex = /^\/([^/]+)\/([a-f0-9]+)/;
4
4
  const starredRegex = /^\/([^/]+)\/starred\/?$/;
5
+ const forksRegex = /^\/([^/]+)\/forks\/?$/;
5
6
  const userRegex = /^\/([^/]+)\/?$/;
7
+ const discoverRegex = /^\/discover\/?$/;
6
8
  const hosts = ["gist.github.com"];
7
9
  const excludedPaths = [
8
10
  "discover",
@@ -17,6 +19,20 @@ const githubGistHandler = {
17
19
  },
18
20
  resolve: (url) => {
19
21
  const { pathname } = new URL(url);
22
+ if (discoverRegex.test(pathname)) return [{
23
+ uri: "https://gist.github.com/discover.atom",
24
+ hint: composeHint("github-gist:discover")
25
+ }];
26
+ const starredMatch = pathname.match(starredRegex);
27
+ if (starredMatch?.[1] && !isAnyOf(starredMatch[1], excludedPaths)) return [{
28
+ uri: `https://gist.github.com/${starredMatch[1]}/starred.atom`,
29
+ hint: composeHint("github-gist:starred")
30
+ }];
31
+ const forksMatch = pathname.match(forksRegex);
32
+ if (forksMatch?.[1] && !isAnyOf(forksMatch[1], excludedPaths)) return [{
33
+ uri: `https://gist.github.com/${forksMatch[1]}/forks.atom`,
34
+ hint: composeHint("github-gist:forks")
35
+ }];
20
36
  const gistMatch = pathname.match(gistRegex);
21
37
  if (gistMatch?.[1] && gistMatch?.[2]) {
22
38
  const username = gistMatch[1];
@@ -26,11 +42,6 @@ const githubGistHandler = {
26
42
  }];
27
43
  return [];
28
44
  }
29
- const starredMatch = pathname.match(starredRegex);
30
- if (starredMatch?.[1] && !isAnyOf(starredMatch[1], excludedPaths)) return [{
31
- uri: `https://gist.github.com/${starredMatch[1]}/starred.atom`,
32
- hint: composeHint("github-gist:starred")
33
- }];
34
45
  const userMatch = pathname.match(userRegex);
35
46
  if (userMatch?.[1] && !isAnyOf(userMatch[1], excludedPaths)) return [{
36
47
  uri: `https://gist.github.com/${userMatch[1]}.atom`,
@@ -54,20 +54,38 @@ const gitlabHandler = {
54
54
  if (pathSegments.length >= 2) {
55
55
  const user = pathSegments[0];
56
56
  const repo = pathSegments[1];
57
- if (!isAnyOf(user, excludedPaths)) return [
58
- {
59
- uri: `${origin}/${user}/${repo}/-/releases.atom`,
60
- hint: composeHint("gitlab:releases")
61
- },
62
- {
63
- uri: `${origin}/${user}/${repo}/-/tags?format=atom`,
64
- hint: composeHint("gitlab:tags")
65
- },
66
- {
67
- uri: `${origin}/${user}/${repo}.atom`,
68
- hint: composeHint("gitlab:activity")
57
+ if (!isAnyOf(user, excludedPaths)) {
58
+ const repoFeeds = [
59
+ {
60
+ uri: `${origin}/${user}/${repo}/-/releases.atom`,
61
+ hint: composeHint("gitlab:releases")
62
+ },
63
+ {
64
+ uri: `${origin}/${user}/${repo}/-/tags?format=atom`,
65
+ hint: composeHint("gitlab:tags")
66
+ },
67
+ {
68
+ uri: `${origin}/${user}/${repo}/-/issues.atom`,
69
+ hint: composeHint("gitlab:issues")
70
+ },
71
+ {
72
+ uri: `${origin}/${user}/${repo}/-/merge_requests.atom`,
73
+ hint: composeHint("gitlab:merge-requests")
74
+ },
75
+ {
76
+ uri: `${origin}/${user}/${repo}.atom`,
77
+ hint: composeHint("gitlab:activity")
78
+ }
79
+ ];
80
+ if (pathSegments[2] === "-" && (pathSegments[3] === "commits" || pathSegments[3] === "tree") && pathSegments[4]) {
81
+ const branch = pathSegments[4];
82
+ repoFeeds.unshift({
83
+ uri: `${origin}/${user}/${repo}/-/commits/${branch}?format=atom`,
84
+ hint: composeHint("gitlab:branch-commits")
85
+ });
69
86
  }
70
- ];
87
+ return repoFeeds;
88
+ }
71
89
  }
72
90
  return [];
73
91
  }
@@ -10,8 +10,9 @@ const goodreadsHandler = {
10
10
  return isHostOf(url, hosts);
11
11
  },
12
12
  resolve: (url) => {
13
- const { origin, pathname } = new URL(url);
13
+ const { origin, pathname, searchParams } = new URL(url);
14
14
  const pathSegments = pathname.split("/").filter(Boolean);
15
+ const shelf = searchParams.get("shelf");
15
16
  if (pathSegments[0] === "user" && pathSegments[1] === "show" && pathSegments[2]) {
16
17
  const userId = parseUserId(pathSegments[2]);
17
18
  if (userId) return [{
@@ -24,13 +25,22 @@ const goodreadsHandler = {
24
25
  }
25
26
  if (pathSegments[0] === "review" && pathSegments[1] === "list" && pathSegments[2]) {
26
27
  const userId = parseUserId(pathSegments[2]);
27
- if (userId) return [{
28
- uri: `${origin}/review/list_rss/${userId}`,
29
- hint: composeHint("goodreads:reviews")
30
- }, {
31
- uri: `${origin}/user/updates_rss/${userId}`,
32
- hint: composeHint("goodreads:updates")
33
- }];
28
+ if (userId) {
29
+ const uris = [];
30
+ if (shelf) uris.push({
31
+ uri: `${origin}/review/list_rss/${userId}?shelf=${encodeURIComponent(shelf)}`,
32
+ hint: composeHint("goodreads:shelf")
33
+ });
34
+ uris.push({
35
+ uri: `${origin}/review/list_rss/${userId}`,
36
+ hint: composeHint("goodreads:reviews")
37
+ });
38
+ uris.push({
39
+ uri: `${origin}/user/updates_rss/${userId}`,
40
+ hint: composeHint("goodreads:updates")
41
+ });
42
+ return uris;
43
+ }
34
44
  }
35
45
  return [];
36
46
  }
@@ -0,0 +1,21 @@
1
+ import { composeHint, isHostOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/hackernews.ts
3
+ const hosts = ["news.ycombinator.com"];
4
+ const hackernewsHandler = {
5
+ match: (url) => {
6
+ return isHostOf(url, hosts);
7
+ },
8
+ resolve: (url) => {
9
+ const { pathname } = new URL(url);
10
+ if (pathname === "/show" || pathname === "/shownew") return [{
11
+ uri: "https://news.ycombinator.com/showrss",
12
+ hint: composeHint("hackernews:show")
13
+ }];
14
+ return [{
15
+ uri: "https://news.ycombinator.com/rss",
16
+ hint: composeHint("hackernews:front")
17
+ }];
18
+ }
19
+ };
20
+ //#endregion
21
+ export { hackernewsHandler };
@@ -2,7 +2,7 @@ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/hashnode.ts
3
3
  const hashnodeHandler = {
4
4
  match: (url) => {
5
- return isSubdomainOf(url, "hashnode.dev");
5
+ return isSubdomainOf(url, ["hashnode.dev", "hashnode.com"]);
6
6
  },
7
7
  resolve: (url) => {
8
8
  const { origin } = new URL(url);
@@ -3,7 +3,10 @@ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
3
3
  const domains = [
4
4
  "hatenablog.com",
5
5
  "hatenablog.jp",
6
- "hateblo.jp"
6
+ "hateblo.jp",
7
+ "hatenadiary.com",
8
+ "hatenadiary.jp",
9
+ "hatenadiary.org"
7
10
  ];
8
11
  const categoryRegex = /^\/archive\/category\/([^/]+)/;
9
12
  const authorRegex = /^\/archive\/author\/([^/]+)/;
@@ -0,0 +1,32 @@
1
+ import { composeHint, isAnyOf, isHostOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/hearthis.ts
3
+ const hosts = ["hearthis.at", "www.hearthis.at"];
4
+ const excludedPaths = [
5
+ "about",
6
+ "api",
7
+ "feed",
8
+ "login",
9
+ "privacy",
10
+ "search",
11
+ "set",
12
+ "signup",
13
+ "terms"
14
+ ];
15
+ const hearthisHandler = {
16
+ match: (url) => {
17
+ return isHostOf(url, hosts);
18
+ },
19
+ resolve: (url) => {
20
+ const { pathname } = new URL(url);
21
+ const pathSegments = pathname.split("/").filter(Boolean);
22
+ if (pathSegments.length === 0) return [];
23
+ const username = pathSegments[0];
24
+ if (isAnyOf(username, excludedPaths)) return [];
25
+ return [{
26
+ uri: `https://hearthis.at/${username}/podcast/`,
27
+ hint: composeHint("hearthis:tracks")
28
+ }];
29
+ }
30
+ };
31
+ //#endregion
32
+ export { hearthisHandler };
@@ -0,0 +1,18 @@
1
+ import { composeHint, isHostOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/heyWorld.ts
3
+ const heyWorldHandler = {
4
+ match: (url) => {
5
+ return isHostOf(url, "world.hey.com");
6
+ },
7
+ resolve: (url) => {
8
+ const { pathname } = new URL(url);
9
+ const pathSegments = pathname.split("/").filter(Boolean);
10
+ if (pathSegments.length === 0) return [];
11
+ return [{
12
+ uri: `https://world.hey.com/${pathSegments[0]}/feed.atom`,
13
+ hint: composeHint("hey-world:blog")
14
+ }];
15
+ }
16
+ };
17
+ //#endregion
18
+ export { heyWorldHandler };
@@ -0,0 +1,69 @@
1
+ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/insanejournal.ts
3
+ const wwwUsersPathRegex = /^\/(?:users\/|~)([^/]+)/;
4
+ const wwwAsylumPathRegex = /^\/(?:asylum|community)\/([^/]+)/;
5
+ const firstSegmentRegex = /^\/([^/]+)/;
6
+ const tagRegex = /^\/tag\/([^/]+)/;
7
+ const insanejournalHandler = {
8
+ match: (url) => {
9
+ if (!isSubdomainOf(url, "insanejournal.com")) return false;
10
+ const { hostname, pathname } = new URL(url);
11
+ const lower = hostname.toLowerCase();
12
+ if (lower === "www.insanejournal.com" || lower === "insanejournal.com") return wwwUsersPathRegex.test(pathname) || wwwAsylumPathRegex.test(pathname);
13
+ if (lower === "asylums.insanejournal.com" || lower === "feeds.insanejournal.com") return firstSegmentRegex.test(pathname);
14
+ return true;
15
+ },
16
+ resolve: (url) => {
17
+ const { origin, hostname, pathname } = new URL(url);
18
+ const lowerHostname = hostname.toLowerCase();
19
+ const uris = [];
20
+ let feedOrigin = origin;
21
+ let feedPathPrefix = "";
22
+ if (lowerHostname === "www.insanejournal.com" || lowerHostname === "insanejournal.com") {
23
+ const userMatch = pathname.match(wwwUsersPathRegex);
24
+ if (userMatch?.[1]) feedOrigin = `https://${userMatch[1]}.insanejournal.com`;
25
+ else {
26
+ const asylumMatch = pathname.match(wwwAsylumPathRegex);
27
+ if (asylumMatch?.[1]) {
28
+ feedOrigin = "https://asylums.insanejournal.com";
29
+ feedPathPrefix = `/${asylumMatch[1]}`;
30
+ } else return uris;
31
+ }
32
+ } else if (lowerHostname === "asylums.insanejournal.com") {
33
+ const segMatch = pathname.match(firstSegmentRegex);
34
+ if (segMatch?.[1]) feedPathPrefix = `/${segMatch[1]}`;
35
+ else return uris;
36
+ } else if (lowerHostname === "feeds.insanejournal.com") {
37
+ const segMatch = pathname.match(firstSegmentRegex);
38
+ if (segMatch?.[1]) feedPathPrefix = `/${segMatch[1]}`;
39
+ else return uris;
40
+ }
41
+ const tagMatch = pathname.match(tagRegex);
42
+ if (tagMatch?.[1]) {
43
+ const tag = encodeURIComponent(tagMatch[1]);
44
+ uris.push({
45
+ uri: `${feedOrigin}${feedPathPrefix}/data/rss?tag=${tag}`,
46
+ hint: composeHint("insanejournal:posts-tag-rss")
47
+ });
48
+ uris.push({
49
+ uri: `${feedOrigin}${feedPathPrefix}/data/atom?tag=${tag}`,
50
+ hint: composeHint("insanejournal:posts-tag-atom")
51
+ });
52
+ }
53
+ uris.push({
54
+ uri: `${feedOrigin}${feedPathPrefix}/data/rss`,
55
+ hint: composeHint("insanejournal:posts-rss")
56
+ });
57
+ uris.push({
58
+ uri: `${feedOrigin}${feedPathPrefix}/data/atom`,
59
+ hint: composeHint("insanejournal:posts-atom")
60
+ });
61
+ uris.push({
62
+ uri: `${feedOrigin}${feedPathPrefix}/data/userpics`,
63
+ hint: composeHint("insanejournal:userpics-atom")
64
+ });
65
+ return uris;
66
+ }
67
+ };
68
+ //#endregion
69
+ export { insanejournalHandler };
@@ -14,10 +14,14 @@ const sorts = [
14
14
  "newest",
15
15
  "top-rated",
16
16
  "top-sellers",
17
- "on-sale"
17
+ "on-sale",
18
+ "free"
18
19
  ];
19
20
  const byUserRegex = /^\/games\/by-([^/]+)/;
20
21
  const tagRegex = /^\/games\/tag-([^/]+)/;
22
+ const platformRegex = /^\/games\/platform-([^/.]+)/;
23
+ const genreRegex = /^\/games\/genre-([^/.]+)/;
24
+ const madeWithRegex = /^\/games\/made-with-([^/.]+)/;
21
25
  const sortRegex = /^\/games\/([^/.]+)/;
22
26
  const sectionRegex = /^\/([^/.]+)/;
23
27
  const gameRegex = /^\/([^/]+)/;
@@ -50,6 +54,21 @@ const itchioHandler = {
50
54
  uri: `https://itch.io/games/tag-${tagMatch[1]}.xml`,
51
55
  hint: composeHint("itchio:tag")
52
56
  }];
57
+ const platformMatch = pathname.match(platformRegex);
58
+ if (platformMatch?.[1]) return [{
59
+ uri: `https://itch.io/games/platform-${platformMatch[1]}.xml`,
60
+ hint: composeHint("itchio:platform")
61
+ }];
62
+ const genreMatch = pathname.match(genreRegex);
63
+ if (genreMatch?.[1]) return [{
64
+ uri: `https://itch.io/games/genre-${genreMatch[1]}.xml`,
65
+ hint: composeHint("itchio:genre")
66
+ }];
67
+ const madeWithMatch = pathname.match(madeWithRegex);
68
+ if (madeWithMatch?.[1]) return [{
69
+ uri: `https://itch.io/games/made-with-${madeWithMatch[1]}.xml`,
70
+ hint: composeHint("itchio:made-with")
71
+ }];
53
72
  const sortMatch = pathname.match(sortRegex);
54
73
  if (sortMatch?.[1] && isAnyOf(sortMatch[1], sorts)) return [{
55
74
  uri: `https://itch.io/games/${sortMatch[1]}.xml`,
@@ -81,6 +100,10 @@ const itchioHandler = {
81
100
  uri: "https://itch.io/feed/sales.xml",
82
101
  hint: composeHint("itchio:sales")
83
102
  });
103
+ uris.push({
104
+ uri: "https://itch.io/blog.rss",
105
+ hint: composeHint("itchio:blog")
106
+ });
84
107
  return uris;
85
108
  }
86
109
  };
@@ -1,6 +1,37 @@
1
1
  import { composeHint, hasMetaContent } from "../../../common/utils.js";
2
2
  //#region src/feeds/platform/handlers/lemmy.ts
3
3
  const lemmyPoweredByRegex = /lemmy/i;
4
+ const validSorts = new Set([
5
+ "Active",
6
+ "Hot",
7
+ "New",
8
+ "Old",
9
+ "TopHour",
10
+ "TopSixHour",
11
+ "TopTwelveHour",
12
+ "TopDay",
13
+ "TopWeek",
14
+ "TopMonth",
15
+ "TopThreeMonths",
16
+ "TopSixMonths",
17
+ "TopNineMonths",
18
+ "TopYear",
19
+ "TopAll",
20
+ "Controversial",
21
+ "Scaled",
22
+ "MostComments",
23
+ "NewComments"
24
+ ]);
25
+ const numericRegex = /^\d+$/;
26
+ const getQuerySuffix = (searchParams) => {
27
+ const params = new URLSearchParams();
28
+ const sort = searchParams.get("sort");
29
+ if (sort && validSorts.has(sort)) params.set("sort", sort);
30
+ const limit = searchParams.get("limit");
31
+ if (limit && numericRegex.test(limit)) params.set("limit", limit);
32
+ const query = params.toString();
33
+ return query ? `?${query}` : "";
34
+ };
4
35
  const isCommunityPath = (pathname) => {
5
36
  const segments = pathname.split("/").filter(Boolean);
6
37
  return segments.length >= 2 && segments[0] === "c";
@@ -9,6 +40,9 @@ const isUserPath = (pathname) => {
9
40
  const segments = pathname.split("/").filter(Boolean);
10
41
  return segments.length >= 2 && segments[0] === "u";
11
42
  };
43
+ const isHomePath = (pathname) => {
44
+ return pathname === "/" || pathname === "" || pathname === "/home";
45
+ };
12
46
  const isLemmyHtml = (content) => {
13
47
  return hasMetaContent(content, "generator", "Lemmy");
14
48
  };
@@ -20,7 +54,7 @@ const lemmyHandler = {
20
54
  match: (url, content, headers) => {
21
55
  try {
22
56
  const { pathname } = new URL(url);
23
- if (!isCommunityPath(pathname) && !isUserPath(pathname)) return false;
57
+ if (!isCommunityPath(pathname) && !isUserPath(pathname) && !isHomePath(pathname)) return false;
24
58
  if (content && isLemmyHtml(content)) return true;
25
59
  if (headers && isLemmyHeaders(headers)) return true;
26
60
  } catch {}
@@ -28,16 +62,24 @@ const lemmyHandler = {
28
62
  },
29
63
  resolve: (url) => {
30
64
  try {
31
- const { origin, pathname } = new URL(url);
65
+ const { origin, pathname, searchParams } = new URL(url);
32
66
  const segments = pathname.split("/").filter(Boolean);
67
+ const sortSuffix = getQuerySuffix(searchParams);
33
68
  if (isCommunityPath(pathname) && segments[1]) return [{
34
- uri: `${origin}/feeds/c/${segments[1]}.xml`,
69
+ uri: `${origin}/feeds/c/${segments[1]}.xml${sortSuffix}`,
35
70
  hint: composeHint("lemmy:community")
36
71
  }];
37
72
  if (isUserPath(pathname) && segments[1]) return [{
38
- uri: `${origin}/feeds/u/${segments[1]}.xml`,
73
+ uri: `${origin}/feeds/u/${segments[1]}.xml${sortSuffix}`,
39
74
  hint: composeHint("lemmy:user")
40
75
  }];
76
+ if (isHomePath(pathname)) return [{
77
+ uri: `${origin}/feeds/all.xml${sortSuffix}`,
78
+ hint: composeHint("lemmy:all")
79
+ }, {
80
+ uri: `${origin}/feeds/local.xml${sortSuffix}`,
81
+ hint: composeHint("lemmy:local")
82
+ }];
41
83
  } catch {}
42
84
  return [];
43
85
  }
@@ -30,6 +30,10 @@ const letterboxdHandler = {
30
30
  const { pathname } = new URL(url);
31
31
  const pathSegments = pathname.split("/").filter(Boolean);
32
32
  if (pathSegments.length === 0) return [];
33
+ if (pathSegments[0] === "journal") return [{
34
+ uri: "https://letterboxd.com/journal/rss/",
35
+ hint: composeHint("letterboxd:journal")
36
+ }];
33
37
  const username = pathSegments[0];
34
38
  if (isAnyOf(username, excludedPaths)) return [];
35
39
  return [{