feedscout 1.8.0 → 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 (236) hide show
  1. package/README.md +1 -1
  2. package/dist/blogrolls/extractors.d.ts +2 -2
  3. package/dist/blogrolls/extractors.js +2 -2
  4. package/dist/blogrolls/index.js +4 -5
  5. package/dist/blogrolls.d.ts +2 -2
  6. package/dist/blogrolls.js +2 -2
  7. package/dist/common/discover/defaults.d.ts +6 -0
  8. package/dist/common/discover/defaults.js +44 -0
  9. package/dist/common/discover/index.js +2 -2
  10. package/dist/common/discover/utils.d.ts +3 -6
  11. package/dist/common/discover/utils.js +1 -28
  12. package/dist/common/locales.js +214 -10
  13. package/dist/common/types.d.ts +3 -2
  14. package/dist/common/uris/feed/types.d.ts +2 -2
  15. package/dist/common/uris/html/types.d.ts +4 -4
  16. package/dist/common/utils.d.ts +6 -4
  17. package/dist/common/utils.js +13 -11
  18. package/dist/favicons/extractors.d.ts +2 -2
  19. package/dist/favicons/extractors.js +2 -2
  20. package/dist/favicons/index.js +4 -5
  21. package/dist/favicons.d.ts +2 -2
  22. package/dist/favicons.js +2 -2
  23. package/dist/feeds/defaults.d.ts +2 -1
  24. package/dist/feeds/defaults.js +116 -9
  25. package/dist/feeds/extractors.d.ts +2 -2
  26. package/dist/feeds/extractors.js +6 -6
  27. package/dist/feeds/index.js +4 -5
  28. package/dist/feeds/platform/handlers/acast.js +26 -0
  29. package/dist/feeds/platform/handlers/ameblo.js +36 -0
  30. package/dist/feeds/platform/handlers/applePodcasts.js +1 -1
  31. package/dist/feeds/platform/handlers/arena.js +42 -0
  32. package/dist/feeds/platform/handlers/artstation.js +52 -0
  33. package/dist/feeds/platform/handlers/audioboom.js +23 -0
  34. package/dist/feeds/platform/handlers/bearblog.js +45 -0
  35. package/dist/feeds/platform/handlers/behance.js +7 -0
  36. package/dist/feeds/platform/handlers/blogspot.js +38 -2
  37. package/dist/feeds/platform/handlers/bookwyrm.js +48 -0
  38. package/dist/feeds/platform/handlers/buttondown.js +43 -0
  39. package/dist/feeds/platform/handlers/buzzsprout.js +22 -0
  40. package/dist/feeds/platform/handlers/codeberg.js +5 -5
  41. package/dist/feeds/platform/handlers/dailymotion.js +16 -0
  42. package/dist/feeds/platform/handlers/deviantart.js +25 -6
  43. package/dist/feeds/platform/handlers/devto.js +8 -0
  44. package/dist/feeds/platform/handlers/discourse.js +70 -0
  45. package/dist/feeds/platform/handlers/dreamwidth.js +48 -0
  46. package/dist/feeds/platform/handlers/exblog.js +35 -0
  47. package/dist/feeds/platform/handlers/fireside.js +24 -0
  48. package/dist/feeds/platform/handlers/friendica.js +44 -0
  49. package/dist/feeds/platform/handlers/ghost.js +30 -0
  50. package/dist/feeds/platform/handlers/github.js +6 -0
  51. package/dist/feeds/platform/handlers/githubGist.js +16 -5
  52. package/dist/feeds/platform/handlers/gitlab.js +31 -13
  53. package/dist/feeds/platform/handlers/goodreads.js +18 -8
  54. package/dist/feeds/platform/handlers/hackernews.js +21 -0
  55. package/dist/feeds/platform/handlers/hashnode.js +1 -1
  56. package/dist/feeds/platform/handlers/hatenablog.js +4 -1
  57. package/dist/feeds/platform/handlers/hearthis.js +32 -0
  58. package/dist/feeds/platform/handlers/heyWorld.js +18 -0
  59. package/dist/feeds/platform/handlers/insanejournal.js +69 -0
  60. package/dist/feeds/platform/handlers/itchio.js +24 -1
  61. package/dist/feeds/platform/handlers/lemmy.js +46 -4
  62. package/dist/feeds/platform/handlers/letterboxd.js +4 -0
  63. package/dist/feeds/platform/handlers/libsyn.js +25 -0
  64. package/dist/feeds/platform/handlers/listed.js +20 -0
  65. package/dist/feeds/platform/handlers/livejournal.js +68 -0
  66. package/dist/feeds/platform/handlers/mastodon.js +32 -0
  67. package/dist/feeds/platform/handlers/mataroa.js +16 -0
  68. package/dist/feeds/platform/handlers/medium.js +2 -2
  69. package/dist/feeds/platform/handlers/microblog.js +55 -0
  70. package/dist/feeds/platform/handlers/misskey.js +40 -0
  71. package/dist/feeds/platform/handlers/myanimelist.js +43 -0
  72. package/dist/feeds/platform/handlers/naverBlog.js +21 -0
  73. package/dist/feeds/platform/handlers/nebula.js +68 -0
  74. package/dist/feeds/platform/handlers/note.js +53 -0
  75. package/dist/feeds/platform/handlers/observable.js +34 -0
  76. package/dist/feeds/platform/handlers/odysee.js +20 -0
  77. package/dist/feeds/platform/handlers/pagecord.js +16 -0
  78. package/dist/feeds/platform/handlers/paragraph.js +1 -2
  79. package/dist/feeds/platform/handlers/pika.js +35 -0
  80. package/dist/feeds/platform/handlers/pinterest.js +13 -0
  81. package/dist/feeds/platform/handlers/pixelfed.js +46 -0
  82. package/dist/feeds/platform/handlers/pleroma.js +34 -0
  83. package/dist/feeds/platform/handlers/podbean.js +29 -0
  84. package/dist/feeds/platform/handlers/podigee.js +29 -0
  85. package/dist/feeds/platform/handlers/posthaven.js +24 -0
  86. package/dist/feeds/platform/handlers/prose.js +26 -0
  87. package/dist/feeds/platform/handlers/qiita.js +58 -0
  88. package/dist/feeds/platform/handlers/reddit.js +83 -9
  89. package/dist/feeds/platform/handlers/rssCom.js +20 -0
  90. package/dist/feeds/platform/handlers/seesaa.js +22 -0
  91. package/dist/feeds/platform/handlers/sourceforge.js +37 -4
  92. package/dist/feeds/platform/handlers/spreaker.js +21 -0
  93. package/dist/feeds/platform/handlers/stackExchange.js +26 -2
  94. package/dist/feeds/platform/handlers/steam.js +7 -0
  95. package/dist/feeds/platform/handlers/tildes.js +41 -0
  96. package/dist/feeds/platform/handlers/tistory.js +16 -0
  97. package/dist/feeds/platform/handlers/transistor.js +29 -0
  98. package/dist/feeds/platform/handlers/velog.js +24 -0
  99. package/dist/feeds/platform/handlers/vimeo.js +6 -0
  100. package/dist/feeds/platform/handlers/weblogLol.js +26 -0
  101. package/dist/feeds/platform/handlers/weebly.js +25 -0
  102. package/dist/feeds/platform/handlers/wordpress.js +173 -28
  103. package/dist/feeds/platform/handlers/writeas.js +51 -0
  104. package/dist/feeds/platform/handlers/ximalaya.js +1 -1
  105. package/dist/feeds/platform/handlers/youtube.js +4 -1
  106. package/dist/feeds/platform/handlers/zenn.js +54 -0
  107. package/dist/feeds.d.ts +3 -3
  108. package/dist/feeds.js +3 -3
  109. package/dist/hubs/discover/index.js +3 -3
  110. package/dist/hubs/feed/index.js +2 -2
  111. package/dist/hubs/headers/index.js +2 -2
  112. package/dist/hubs/html/index.js +2 -2
  113. package/dist/index.d.ts +2 -1
  114. package/dist/index.js +2 -1
  115. package/dist/utils.d.ts +2 -1
  116. package/package.json +24 -64
  117. package/dist/blogrolls/defaults.cjs +0 -51
  118. package/dist/blogrolls/defaults.d.cts +0 -17
  119. package/dist/blogrolls/extractors.cjs +0 -21
  120. package/dist/blogrolls/extractors.d.cts +0 -7
  121. package/dist/blogrolls/index.cjs +0 -26
  122. package/dist/blogrolls/index.d.cts +0 -7
  123. package/dist/blogrolls/types.d.cts +0 -6
  124. package/dist/blogrolls.cjs +0 -13
  125. package/dist/blogrolls.d.cts +0 -4
  126. package/dist/common/discover/index.cjs +0 -105
  127. package/dist/common/discover/utils.cjs +0 -130
  128. package/dist/common/discover/utils.d.cts +0 -8
  129. package/dist/common/locales.cjs +0 -144
  130. package/dist/common/types.cjs +0 -10
  131. package/dist/common/types.d.cts +0 -89
  132. package/dist/common/uris/feed/index.cjs +0 -12
  133. package/dist/common/uris/feed/index.d.cts +0 -6
  134. package/dist/common/uris/feed/types.d.cts +0 -9
  135. package/dist/common/uris/guess/index.cjs +0 -8
  136. package/dist/common/uris/guess/index.d.cts +0 -7
  137. package/dist/common/uris/guess/types.d.cts +0 -10
  138. package/dist/common/uris/guess/utils.cjs +0 -42
  139. package/dist/common/uris/guess/utils.d.cts +0 -8
  140. package/dist/common/uris/headers/index.cjs +0 -26
  141. package/dist/common/uris/headers/index.d.cts +0 -6
  142. package/dist/common/uris/headers/types.d.cts +0 -9
  143. package/dist/common/uris/html/handlers.cjs +0 -45
  144. package/dist/common/uris/html/index.cjs +0 -19
  145. package/dist/common/uris/html/index.d.cts +0 -6
  146. package/dist/common/uris/html/types.d.cts +0 -12
  147. package/dist/common/uris/index.cjs +0 -32
  148. package/dist/common/uris/platform/index.cjs +0 -10
  149. package/dist/common/uris/platform/index.d.cts +0 -7
  150. package/dist/common/uris/platform/types.d.cts +0 -13
  151. package/dist/common/utils.cjs +0 -97
  152. package/dist/common/utils.d.cts +0 -10
  153. package/dist/favicons/defaults.cjs +0 -65
  154. package/dist/favicons/defaults.d.cts +0 -18
  155. package/dist/favicons/extractors.cjs +0 -25
  156. package/dist/favicons/extractors.d.cts +0 -7
  157. package/dist/favicons/index.cjs +0 -30
  158. package/dist/favicons/index.d.cts +0 -7
  159. package/dist/favicons/platform/handlers/bluesky.cjs +0 -27
  160. package/dist/favicons/platform/handlers/codeberg.cjs +0 -19
  161. package/dist/favicons/platform/handlers/deviantart.cjs +0 -29
  162. package/dist/favicons/platform/handlers/devto.cjs +0 -31
  163. package/dist/favicons/platform/handlers/github.cjs +0 -19
  164. package/dist/favicons/platform/handlers/githubGist.cjs +0 -19
  165. package/dist/favicons/platform/handlers/gitlab.cjs +0 -38
  166. package/dist/favicons/platform/handlers/lobsters.cjs +0 -21
  167. package/dist/favicons/platform/handlers/mastodon.cjs +0 -40
  168. package/dist/favicons/platform/handlers/reddit.cjs +0 -42
  169. package/dist/favicons/platform/handlers/sourceforge.cjs +0 -21
  170. package/dist/favicons/platform/handlers/tumblr.cjs +0 -16
  171. package/dist/favicons/types.d.cts +0 -4
  172. package/dist/favicons/utils.cjs +0 -10
  173. package/dist/favicons.cjs +0 -12
  174. package/dist/favicons.d.cts +0 -4
  175. package/dist/feeds/defaults.cjs +0 -178
  176. package/dist/feeds/defaults.d.cts +0 -20
  177. package/dist/feeds/extractors.cjs +0 -46
  178. package/dist/feeds/extractors.d.cts +0 -7
  179. package/dist/feeds/index.cjs +0 -27
  180. package/dist/feeds/index.d.cts +0 -7
  181. package/dist/feeds/platform/handlers/applePodcasts.cjs +0 -26
  182. package/dist/feeds/platform/handlers/behance.cjs +0 -49
  183. package/dist/feeds/platform/handlers/blogspot.cjs +0 -36
  184. package/dist/feeds/platform/handlers/bluesky.cjs +0 -20
  185. package/dist/feeds/platform/handlers/codeberg.cjs +0 -69
  186. package/dist/feeds/platform/handlers/csdn.cjs +0 -20
  187. package/dist/feeds/platform/handlers/dailymotion.cjs +0 -70
  188. package/dist/feeds/platform/handlers/deviantart.cjs +0 -66
  189. package/dist/feeds/platform/handlers/devto.cjs +0 -50
  190. package/dist/feeds/platform/handlers/douban.cjs +0 -56
  191. package/dist/feeds/platform/handlers/github.cjs +0 -116
  192. package/dist/feeds/platform/handlers/githubGist.cjs +0 -45
  193. package/dist/feeds/platform/handlers/gitlab.cjs +0 -80
  194. package/dist/feeds/platform/handlers/goodreads.cjs +0 -39
  195. package/dist/feeds/platform/handlers/hashnode.cjs +0 -16
  196. package/dist/feeds/platform/handlers/hatenablog.cjs +0 -53
  197. package/dist/feeds/platform/handlers/itchio.cjs +0 -88
  198. package/dist/feeds/platform/handlers/kickstarter.cjs +0 -22
  199. package/dist/feeds/platform/handlers/lemmy.cjs +0 -46
  200. package/dist/feeds/platform/handlers/letterboxd.cjs +0 -42
  201. package/dist/feeds/platform/handlers/lobsters.cjs +0 -57
  202. package/dist/feeds/platform/handlers/mastodon.cjs +0 -42
  203. package/dist/feeds/platform/handlers/medium.cjs +0 -68
  204. package/dist/feeds/platform/handlers/paragraph.cjs +0 -21
  205. package/dist/feeds/platform/handlers/pinterest.cjs +0 -44
  206. package/dist/feeds/platform/handlers/producthunt.cjs +0 -29
  207. package/dist/feeds/platform/handlers/reddit.cjs +0 -75
  208. package/dist/feeds/platform/handlers/soundcloud.cjs +0 -39
  209. package/dist/feeds/platform/handlers/sourceforge.cjs +0 -20
  210. package/dist/feeds/platform/handlers/stackExchange.cjs +0 -40
  211. package/dist/feeds/platform/handlers/steam.cjs +0 -28
  212. package/dist/feeds/platform/handlers/substack.cjs +0 -23
  213. package/dist/feeds/platform/handlers/tumblr.cjs +0 -24
  214. package/dist/feeds/platform/handlers/v2ex.cjs +0 -35
  215. package/dist/feeds/platform/handlers/vimeo.cjs +0 -69
  216. package/dist/feeds/platform/handlers/wordpress.cjs +0 -64
  217. package/dist/feeds/platform/handlers/wpengine.cjs +0 -10
  218. package/dist/feeds/platform/handlers/ximalaya.cjs +0 -20
  219. package/dist/feeds/platform/handlers/youtube.cjs +0 -94
  220. package/dist/feeds/types.d.cts +0 -9
  221. package/dist/feeds.cjs +0 -15
  222. package/dist/feeds.d.cts +0 -4
  223. package/dist/hubs/discover/index.cjs +0 -30
  224. package/dist/hubs/discover/index.d.cts +0 -7
  225. package/dist/hubs/discover/types.d.cts +0 -15
  226. package/dist/hubs/feed/index.cjs +0 -32
  227. package/dist/hubs/headers/index.cjs +0 -17
  228. package/dist/hubs/html/index.cjs +0 -28
  229. package/dist/hubs.cjs +0 -0
  230. package/dist/hubs.d.cts +0 -2
  231. package/dist/index.cjs +0 -12
  232. package/dist/index.d.cts +0 -7
  233. package/dist/methods.cjs +0 -15
  234. package/dist/methods.d.cts +0 -7
  235. package/dist/utils.cjs +0 -9
  236. package/dist/utils.d.cts +0 -2
@@ -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 [{
@@ -0,0 +1,25 @@
1
+ import { composeHint, isHostOf, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/libsyn.ts
3
+ const numericRegex = /^\d+$/;
4
+ const libsynHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "libsyn.com");
7
+ },
8
+ resolve: (url) => {
9
+ const { origin, pathname } = new URL(url);
10
+ if (isHostOf(url, "feeds.libsyn.com")) {
11
+ const showId = pathname.split("/").filter(Boolean)[0];
12
+ if (showId && numericRegex.test(showId)) return [{
13
+ uri: `https://feeds.libsyn.com/${showId}/rss`,
14
+ hint: composeHint("libsyn:podcast")
15
+ }];
16
+ return [];
17
+ }
18
+ return [{
19
+ uri: `${origin}/rss`,
20
+ hint: composeHint("libsyn:podcast")
21
+ }];
22
+ }
23
+ };
24
+ //#endregion
25
+ export { libsynHandler };
@@ -0,0 +1,20 @@
1
+ import { composeHint, isHostOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/listed.ts
3
+ const hosts = ["listed.to", "www.listed.to"];
4
+ const userRegex = /^\/@([^/]+)/;
5
+ const listedHandler = {
6
+ match: (url) => {
7
+ return isHostOf(url, hosts);
8
+ },
9
+ resolve: (url) => {
10
+ const { pathname } = new URL(url);
11
+ const userMatch = pathname.match(userRegex);
12
+ if (!userMatch?.[1]) return [];
13
+ return [{
14
+ uri: `https://listed.to/@${userMatch[1]}/feed.rss`,
15
+ hint: composeHint("listed:blog")
16
+ }];
17
+ }
18
+ };
19
+ //#endregion
20
+ export { listedHandler };
@@ -0,0 +1,68 @@
1
+ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/livejournal.ts
3
+ const wwwUsersPathRegex = /^\/(?:users\/|~)([^/]+)/;
4
+ const legacyUserPathRegex = /^\/([^/]+)/;
5
+ const tagRegex = /^\/tag\/([^/]+)/;
6
+ const reservedHosts = new Set([
7
+ "livejournal.com",
8
+ "www.livejournal.com",
9
+ "users.livejournal.com",
10
+ "community.livejournal.com",
11
+ "syndicated.livejournal.com"
12
+ ]);
13
+ const livejournalHandler = {
14
+ match: (url) => {
15
+ if (!isSubdomainOf(url, "livejournal.com")) return false;
16
+ const { hostname, pathname } = new URL(url);
17
+ const lower = hostname.toLowerCase();
18
+ if (reservedHosts.has(lower)) {
19
+ if (lower === "www.livejournal.com") return wwwUsersPathRegex.test(pathname);
20
+ if (lower === "users.livejournal.com" || lower === "community.livejournal.com") return legacyUserPathRegex.test(pathname);
21
+ return false;
22
+ }
23
+ return true;
24
+ },
25
+ resolve: (url) => {
26
+ const { origin, hostname, pathname } = new URL(url);
27
+ const lowerHostname = hostname.toLowerCase();
28
+ const uris = [];
29
+ let userOrigin = origin;
30
+ if (lowerHostname === "www.livejournal.com") {
31
+ const userMatch = pathname.match(wwwUsersPathRegex);
32
+ if (userMatch?.[1]) userOrigin = `https://${userMatch[1]}.livejournal.com`;
33
+ else return uris;
34
+ }
35
+ if (lowerHostname === "users.livejournal.com" || lowerHostname === "community.livejournal.com") {
36
+ const userMatch = pathname.match(legacyUserPathRegex);
37
+ if (userMatch?.[1]) userOrigin = `https://${userMatch[1]}.livejournal.com`;
38
+ else return uris;
39
+ }
40
+ const tagMatch = pathname.match(tagRegex);
41
+ if (tagMatch?.[1]) {
42
+ const tag = encodeURIComponent(tagMatch[1]);
43
+ uris.push({
44
+ uri: `${userOrigin}/data/rss?tag=${tag}`,
45
+ hint: composeHint("livejournal:posts-tag-rss")
46
+ });
47
+ uris.push({
48
+ uri: `${userOrigin}/data/atom?tag=${tag}`,
49
+ hint: composeHint("livejournal:posts-tag-atom")
50
+ });
51
+ }
52
+ uris.push({
53
+ uri: `${userOrigin}/data/rss`,
54
+ hint: composeHint("livejournal:posts-rss")
55
+ });
56
+ uris.push({
57
+ uri: `${userOrigin}/data/atom`,
58
+ hint: composeHint("livejournal:posts-atom")
59
+ });
60
+ uris.push({
61
+ uri: `${userOrigin}/data/userpics`,
62
+ hint: composeHint("livejournal:userpics-atom")
63
+ });
64
+ return uris;
65
+ }
66
+ };
67
+ //#endregion
68
+ export { livejournalHandler };
@@ -2,6 +2,9 @@ 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
4
  const profileRegex = /^\/@([^/]+)/;
5
+ const taggedProfileRegex = /^\/@([^/]+)\/tagged\/([^/]+)/;
6
+ const repliesProfileRegex = /^\/@([^/]+)\/with_replies/;
7
+ const mediaProfileRegex = /^\/@([^/]+)\/media/;
5
8
  const tagRegex = /^\/tags\/([^/]+)/;
6
9
  const isProfilePath = (pathname) => {
7
10
  const segments = pathname.split("/").filter(Boolean);
@@ -24,6 +27,35 @@ const mastodonHandler = {
24
27
  resolve: (url) => {
25
28
  try {
26
29
  const { origin, pathname } = new URL(url);
30
+ const repliesMatch = pathname.match(repliesProfileRegex);
31
+ if (repliesMatch?.[1]) return [{
32
+ uri: `${origin}/@${repliesMatch[1]}/with_replies.rss`,
33
+ hint: composeHint("mastodon:replies")
34
+ }, {
35
+ uri: `${origin}/@${repliesMatch[1]}.rss`,
36
+ hint: composeHint("mastodon:posts")
37
+ }];
38
+ const mediaMatch = pathname.match(mediaProfileRegex);
39
+ if (mediaMatch?.[1]) return [{
40
+ uri: `${origin}/@${mediaMatch[1]}/media.rss`,
41
+ hint: composeHint("mastodon:media")
42
+ }, {
43
+ uri: `${origin}/@${mediaMatch[1]}.rss`,
44
+ hint: composeHint("mastodon:posts")
45
+ }];
46
+ const taggedMatch = pathname.match(taggedProfileRegex);
47
+ if (taggedMatch?.[1] && taggedMatch?.[2]) {
48
+ const uris = [];
49
+ uris.push({
50
+ uri: `${origin}/@${taggedMatch[1]}/tagged/${taggedMatch[2]}.rss`,
51
+ hint: composeHint("mastodon:tagged")
52
+ });
53
+ uris.push({
54
+ uri: `${origin}/@${taggedMatch[1]}.rss`,
55
+ hint: composeHint("mastodon:posts")
56
+ });
57
+ return uris;
58
+ }
27
59
  const userMatch = pathname.match(profileRegex);
28
60
  if (userMatch?.[1]) return [{
29
61
  uri: `${origin}/@${userMatch[1]}.rss`,
@@ -0,0 +1,16 @@
1
+ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/mataroa.ts
3
+ const mataroaHandler = {
4
+ match: (url) => {
5
+ return isSubdomainOf(url, "mataroa.blog");
6
+ },
7
+ resolve: (url) => {
8
+ const { origin } = new URL(url);
9
+ return [{
10
+ uri: `${origin}/rss/`,
11
+ hint: composeHint("mataroa:blog")
12
+ }];
13
+ }
14
+ };
15
+ //#endregion
16
+ export { mataroaHandler };
@@ -53,11 +53,11 @@ const mediumHandler = {
53
53
  const subdomain = lowerHostname.replace(".medium.com", "");
54
54
  const tagMatch = pathname.match(subdomainTagRegex);
55
55
  if (tagMatch?.[1]) return [{
56
- uri: `https://medium.com/feed/${subdomain}/tagged/${tagMatch[1]}`,
56
+ uri: `https://${subdomain}.medium.com/feed/tagged/${tagMatch[1]}`,
57
57
  hint: composeHint("medium:tagged")
58
58
  }];
59
59
  return [{
60
- uri: `https://medium.com/feed/${subdomain}`,
60
+ uri: `https://${subdomain}.medium.com/feed`,
61
61
  hint: composeHint("medium:publication")
62
62
  }];
63
63
  }
@@ -0,0 +1,55 @@
1
+ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/microblog.ts
3
+ const categoryRegex = /^\/categories\/([^/]+)/;
4
+ const microblogHandler = {
5
+ match: (url) => {
6
+ return isSubdomainOf(url, "micro.blog");
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 category = categoryMatch[1];
14
+ uris.push({
15
+ uri: `${origin}/categories/${category}/feed.xml`,
16
+ hint: composeHint("microblog:category-rss")
17
+ });
18
+ uris.push({
19
+ uri: `${origin}/categories/${category}/feed.json`,
20
+ hint: composeHint("microblog:category-json")
21
+ });
22
+ }
23
+ if (pathname.startsWith("/archive")) uris.push({
24
+ uri: `${origin}/archive/index.json`,
25
+ hint: composeHint("microblog:archive")
26
+ });
27
+ if (pathname.startsWith("/photos")) uris.push({
28
+ uri: `${origin}/photos/index.json`,
29
+ hint: composeHint("microblog:photos")
30
+ });
31
+ if (pathname.startsWith("/replies")) uris.push({
32
+ uri: `${origin}/replies.xml`,
33
+ hint: composeHint("microblog:replies")
34
+ });
35
+ uris.push({
36
+ uri: `${origin}/feed.xml`,
37
+ hint: composeHint("microblog:posts-rss")
38
+ });
39
+ uris.push({
40
+ uri: `${origin}/feed.json`,
41
+ hint: composeHint("microblog:posts-json")
42
+ });
43
+ uris.push({
44
+ uri: `${origin}/podcast.xml`,
45
+ hint: composeHint("microblog:podcast")
46
+ });
47
+ uris.push({
48
+ uri: `${origin}/podcast.json`,
49
+ hint: composeHint("microblog:podcast-json")
50
+ });
51
+ return uris;
52
+ }
53
+ };
54
+ //#endregion
55
+ export { microblogHandler };
@@ -0,0 +1,40 @@
1
+ import { composeHint, hasMetaContent } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/misskey.ts
3
+ const profileRegex = /^\/@([^/.]+)/;
4
+ const isMisskeyHtml = (content) => {
5
+ return hasMetaContent(content, "application-name", "Misskey");
6
+ };
7
+ const misskeyHandler = {
8
+ match: (url, content) => {
9
+ try {
10
+ if (!content || !isMisskeyHtml(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}/@${match[1]}.atom`,
24
+ hint: composeHint("misskey:posts-atom")
25
+ },
26
+ {
27
+ uri: `${origin}/@${match[1]}.rss`,
28
+ hint: composeHint("misskey:posts-rss")
29
+ },
30
+ {
31
+ uri: `${origin}/@${match[1]}.json`,
32
+ hint: composeHint("misskey:posts-json")
33
+ }
34
+ ];
35
+ } catch {}
36
+ return [];
37
+ }
38
+ };
39
+ //#endregion
40
+ export { misskeyHandler };
@@ -0,0 +1,43 @@
1
+ import { composeHint, isHostOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/myanimelist.ts
3
+ const hosts = ["myanimelist.net", "www.myanimelist.net"];
4
+ const userRegex = /^\/(?:profile|animelist|mangalist|history)\/([^/]+)/;
5
+ const myanimelistHandler = {
6
+ match: (url) => {
7
+ return isHostOf(url, hosts);
8
+ },
9
+ resolve: (url) => {
10
+ const { pathname } = new URL(url);
11
+ if (pathname === "/news" || pathname.startsWith("/news/")) return [{
12
+ uri: "https://myanimelist.net/rss/news.xml",
13
+ hint: composeHint("myanimelist:news")
14
+ }];
15
+ if (pathname === "/featured" || pathname.startsWith("/featured/")) return [{
16
+ uri: "https://myanimelist.net/rss/featured.xml",
17
+ hint: composeHint("myanimelist:featured")
18
+ }];
19
+ const match = pathname.match(userRegex);
20
+ if (!match?.[1]) return [];
21
+ const user = match[1];
22
+ const uris = [];
23
+ uris.push({
24
+ uri: `https://myanimelist.net/rss.php?type=rw&u=${user}`,
25
+ hint: composeHint("myanimelist:anime")
26
+ });
27
+ uris.push({
28
+ uri: `https://myanimelist.net/rss.php?type=rm&u=${user}`,
29
+ hint: composeHint("myanimelist:manga")
30
+ });
31
+ uris.push({
32
+ uri: `https://myanimelist.net/rss.php?type=rrw&u=${user}`,
33
+ hint: composeHint("myanimelist:recently-watched")
34
+ });
35
+ uris.push({
36
+ uri: `https://myanimelist.net/rss.php?type=rrm&u=${user}`,
37
+ hint: composeHint("myanimelist:recently-read")
38
+ });
39
+ return uris;
40
+ }
41
+ };
42
+ //#endregion
43
+ export { myanimelistHandler };
@@ -0,0 +1,21 @@
1
+ import { composeHint, isHostOf } from "../../../common/utils.js";
2
+ //#region src/feeds/platform/handlers/naverBlog.ts
3
+ const hosts = ["blog.naver.com", "m.blog.naver.com"];
4
+ const naverBlogHandler = {
5
+ match: (url) => {
6
+ return isHostOf(url, hosts);
7
+ },
8
+ resolve: (url) => {
9
+ const { pathname } = new URL(url);
10
+ const pathSegments = pathname.split("/").filter(Boolean);
11
+ if (pathSegments.length === 0) return [];
12
+ const blogId = pathSegments[0];
13
+ if (blogId.includes(".")) return [];
14
+ return [{
15
+ uri: `https://rss.blog.naver.com/${blogId}.xml`,
16
+ hint: composeHint("naver-blog:blog")
17
+ }];
18
+ }
19
+ };
20
+ //#endregion
21
+ export { naverBlogHandler };