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.
- package/README.md +14 -0
- package/dist/blogrolls/extractors.cjs +1 -1
- package/dist/blogrolls/extractors.js +1 -1
- package/dist/blogrolls/index.cjs +3 -2
- package/dist/blogrolls/index.js +5 -4
- package/dist/common/discover/index.cjs +21 -9
- package/dist/common/discover/index.js +21 -9
- package/dist/common/discover/utils.cjs +44 -18
- package/dist/common/discover/utils.d.cts +8 -0
- package/dist/common/discover/utils.d.ts +8 -0
- package/dist/common/discover/utils.js +43 -19
- package/dist/common/locales.cjs +3 -1
- package/dist/common/locales.js +3 -1
- package/dist/common/types.d.cts +6 -4
- package/dist/common/types.d.ts +6 -4
- package/dist/common/uris/guess/utils.cjs +3 -2
- package/dist/common/uris/guess/utils.js +3 -2
- package/dist/common/uris/headers/index.cjs +2 -1
- package/dist/common/uris/headers/index.js +2 -1
- package/dist/common/utils.cjs +15 -4
- package/dist/common/utils.js +14 -4
- package/dist/favicons/extractors.cjs +16 -4
- package/dist/favicons/extractors.js +16 -4
- package/dist/favicons/index.cjs +3 -2
- package/dist/favicons/index.js +5 -4
- package/dist/favicons/platform/handlers/bluesky.cjs +3 -3
- package/dist/favicons/platform/handlers/bluesky.js +3 -3
- package/dist/favicons/platform/handlers/mastodon.cjs +7 -5
- package/dist/favicons/platform/handlers/mastodon.js +7 -5
- package/dist/favicons/platform/handlers/reddit.cjs +5 -6
- package/dist/favicons/platform/handlers/reddit.js +5 -6
- package/dist/favicons/utils.cjs +10 -0
- package/dist/favicons/utils.js +9 -0
- package/dist/feeds/defaults.cjs +2 -0
- package/dist/feeds/defaults.js +2 -0
- package/dist/feeds/extractors.cjs +10 -8
- package/dist/feeds/extractors.js +10 -8
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +3 -3
- package/dist/feeds/platform/handlers/blogspot.cjs +2 -1
- package/dist/feeds/platform/handlers/blogspot.js +2 -1
- package/dist/feeds/platform/handlers/bluesky.cjs +2 -1
- package/dist/feeds/platform/handlers/bluesky.js +2 -1
- package/dist/feeds/platform/handlers/csdn.cjs +2 -1
- package/dist/feeds/platform/handlers/csdn.js +2 -1
- package/dist/feeds/platform/handlers/deviantart.cjs +8 -4
- package/dist/feeds/platform/handlers/deviantart.js +8 -4
- package/dist/feeds/platform/handlers/douban.cjs +4 -2
- package/dist/feeds/platform/handlers/douban.js +4 -2
- package/dist/feeds/platform/handlers/github.cjs +12 -6
- package/dist/feeds/platform/handlers/github.js +12 -6
- package/dist/feeds/platform/handlers/githubGist.cjs +6 -3
- package/dist/feeds/platform/handlers/githubGist.js +6 -3
- package/dist/feeds/platform/handlers/gitlab.cjs +15 -2
- package/dist/feeds/platform/handlers/gitlab.js +16 -3
- package/dist/feeds/platform/handlers/lemmy.cjs +46 -0
- package/dist/feeds/platform/handlers/lemmy.js +46 -0
- package/dist/feeds/platform/handlers/mastodon.cjs +5 -3
- package/dist/feeds/platform/handlers/mastodon.js +5 -3
- package/dist/feeds/platform/handlers/medium.cjs +10 -5
- package/dist/feeds/platform/handlers/medium.js +10 -5
- package/dist/feeds/platform/handlers/reddit.cjs +10 -5
- package/dist/feeds/platform/handlers/reddit.js +10 -5
- package/dist/feeds/platform/handlers/soundcloud.cjs +3 -2
- package/dist/feeds/platform/handlers/soundcloud.js +3 -2
- package/dist/feeds/platform/handlers/stackExchange.cjs +6 -3
- package/dist/feeds/platform/handlers/stackExchange.js +6 -3
- package/dist/feeds/platform/handlers/steam.cjs +4 -2
- package/dist/feeds/platform/handlers/steam.js +4 -2
- package/dist/feeds/platform/handlers/v2ex.cjs +4 -2
- package/dist/feeds/platform/handlers/v2ex.js +4 -2
- package/dist/feeds/platform/handlers/vimeo.cjs +3 -2
- package/dist/feeds/platform/handlers/vimeo.js +3 -2
- package/dist/feeds/platform/handlers/ximalaya.cjs +2 -1
- package/dist/feeds/platform/handlers/ximalaya.js +2 -1
- package/dist/feeds/platform/handlers/youtube.cjs +10 -9
- package/dist/feeds/platform/handlers/youtube.js +10 -9
- package/dist/hubs/discover/index.cjs +4 -4
- package/dist/hubs/discover/index.js +5 -5
- package/dist/hubs/discover/types.d.cts +2 -2
- package/dist/hubs/discover/types.d.ts +2 -2
- package/dist/hubs/feed/index.cjs +7 -5
- package/dist/hubs/feed/index.js +7 -5
- package/dist/hubs/headers/index.cjs +3 -3
- package/dist/hubs/headers/index.js +4 -4
- package/dist/hubs/html/index.cjs +3 -3
- package/dist/hubs/html/index.js +4 -4
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/package.json +3 -3
package/dist/common/utils.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_locales = require("./locales.cjs");
|
|
2
2
|
//#region src/common/utils.ts
|
|
3
|
+
const whitespaceRegex = /\s+/;
|
|
3
4
|
const composeHint = (key) => ({
|
|
4
5
|
key,
|
|
5
6
|
label: require_locales.hints[key]
|
|
@@ -30,7 +31,7 @@ const isAnyOf = (value, patterns, parser) => {
|
|
|
30
31
|
return patterns.some((pattern) => parsedValue === pattern.toLowerCase().trim());
|
|
31
32
|
};
|
|
32
33
|
const anyWordMatchesAnyOf = (value, patterns) => {
|
|
33
|
-
return value.toLowerCase().split(
|
|
34
|
+
return value.toLowerCase().split(whitespaceRegex).some((word) => isAnyOf(word, patterns));
|
|
34
35
|
};
|
|
35
36
|
const endsWithAnyOf = (value, patterns) => {
|
|
36
37
|
const lowerValue = value.toLowerCase();
|
|
@@ -41,13 +42,22 @@ const isOfAllowedMimeType = (type, allowedTypes) => {
|
|
|
41
42
|
if (!type) return false;
|
|
42
43
|
return isAnyOf(type, allowedTypes, normalizeMimeType);
|
|
43
44
|
};
|
|
45
|
+
const hasMetaContent = (content, name, value) => {
|
|
46
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48
|
+
return new RegExp(`<meta(?=[^>]*(?:name|property)=["']${escapedName}["'])(?=[^>]*content=["']${escapedValue})`, "i").test(content);
|
|
49
|
+
};
|
|
44
50
|
const omitEmpty = (array) => {
|
|
45
51
|
const result = [];
|
|
46
52
|
for (const item of array) if (item != null && item !== "") result.push(item);
|
|
47
53
|
return result;
|
|
48
54
|
};
|
|
49
|
-
const
|
|
50
|
-
|
|
55
|
+
const resolveUrl = (url, baseUrl) => {
|
|
56
|
+
try {
|
|
57
|
+
return new URL(url, baseUrl).href;
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
51
61
|
};
|
|
52
62
|
const matchesAnyOfLinkSelectors = (rel, type, selectors) => {
|
|
53
63
|
return selectors.some((selector) => {
|
|
@@ -76,11 +86,12 @@ const processConcurrently = async (items, processFn, options) => {
|
|
|
76
86
|
exports.anyWordMatchesAnyOf = anyWordMatchesAnyOf;
|
|
77
87
|
exports.composeHint = composeHint;
|
|
78
88
|
exports.endsWithAnyOf = endsWithAnyOf;
|
|
89
|
+
exports.hasMetaContent = hasMetaContent;
|
|
79
90
|
exports.includesAnyOf = includesAnyOf;
|
|
80
91
|
exports.isAnyOf = isAnyOf;
|
|
81
92
|
exports.isHostOf = isHostOf;
|
|
82
93
|
exports.isSubdomainOf = isSubdomainOf;
|
|
83
94
|
exports.matchesAnyOfLinkSelectors = matchesAnyOfLinkSelectors;
|
|
84
|
-
exports.normalizeUrl = normalizeUrl;
|
|
85
95
|
exports.omitEmpty = omitEmpty;
|
|
86
96
|
exports.processConcurrently = processConcurrently;
|
|
97
|
+
exports.resolveUrl = resolveUrl;
|
package/dist/common/utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hints } from "./locales.js";
|
|
2
2
|
//#region src/common/utils.ts
|
|
3
|
+
const whitespaceRegex = /\s+/;
|
|
3
4
|
const composeHint = (key) => ({
|
|
4
5
|
key,
|
|
5
6
|
label: hints[key]
|
|
@@ -30,7 +31,7 @@ const isAnyOf = (value, patterns, parser) => {
|
|
|
30
31
|
return patterns.some((pattern) => parsedValue === pattern.toLowerCase().trim());
|
|
31
32
|
};
|
|
32
33
|
const anyWordMatchesAnyOf = (value, patterns) => {
|
|
33
|
-
return value.toLowerCase().split(
|
|
34
|
+
return value.toLowerCase().split(whitespaceRegex).some((word) => isAnyOf(word, patterns));
|
|
34
35
|
};
|
|
35
36
|
const endsWithAnyOf = (value, patterns) => {
|
|
36
37
|
const lowerValue = value.toLowerCase();
|
|
@@ -41,13 +42,22 @@ const isOfAllowedMimeType = (type, allowedTypes) => {
|
|
|
41
42
|
if (!type) return false;
|
|
42
43
|
return isAnyOf(type, allowedTypes, normalizeMimeType);
|
|
43
44
|
};
|
|
45
|
+
const hasMetaContent = (content, name, value) => {
|
|
46
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48
|
+
return new RegExp(`<meta(?=[^>]*(?:name|property)=["']${escapedName}["'])(?=[^>]*content=["']${escapedValue})`, "i").test(content);
|
|
49
|
+
};
|
|
44
50
|
const omitEmpty = (array) => {
|
|
45
51
|
const result = [];
|
|
46
52
|
for (const item of array) if (item != null && item !== "") result.push(item);
|
|
47
53
|
return result;
|
|
48
54
|
};
|
|
49
|
-
const
|
|
50
|
-
|
|
55
|
+
const resolveUrl = (url, baseUrl) => {
|
|
56
|
+
try {
|
|
57
|
+
return new URL(url, baseUrl).href;
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
51
61
|
};
|
|
52
62
|
const matchesAnyOfLinkSelectors = (rel, type, selectors) => {
|
|
53
63
|
return selectors.some((selector) => {
|
|
@@ -73,4 +83,4 @@ const processConcurrently = async (items, processFn, options) => {
|
|
|
73
83
|
}
|
|
74
84
|
};
|
|
75
85
|
//#endregion
|
|
76
|
-
export { anyWordMatchesAnyOf, composeHint, endsWithAnyOf, includesAnyOf, isAnyOf, isHostOf, isSubdomainOf, matchesAnyOfLinkSelectors,
|
|
86
|
+
export { anyWordMatchesAnyOf, composeHint, endsWithAnyOf, hasMetaContent, includesAnyOf, isAnyOf, isHostOf, isSubdomainOf, matchesAnyOfLinkSelectors, omitEmpty, processConcurrently, resolveUrl };
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
//#region src/favicons/extractors.ts
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const isImageContentType = (headers) => {
|
|
3
|
+
return headers?.get("content-type")?.startsWith("image/") ?? false;
|
|
4
|
+
};
|
|
5
|
+
const isImageContent = (content) => {
|
|
6
|
+
if (content.includes("<html")) return false;
|
|
7
|
+
const trimmed = content.trimStart();
|
|
8
|
+
const head = trimmed.slice(0, 200);
|
|
9
|
+
return trimmed.startsWith("<svg") || trimmed.startsWith("<?xml") && head.includes("<svg") || content.slice(1, 4) === "PNG" || content.startsWith("GIF8") || content.startsWith("RIFF") && content.includes("WEBP");
|
|
10
|
+
};
|
|
11
|
+
const isSuccessStatus = (status) => {
|
|
12
|
+
return status !== void 0 && status >= 200 && status < 400;
|
|
13
|
+
};
|
|
14
|
+
const defaultExtractor = (input) => {
|
|
15
|
+
if (isImageContentType(input.headers) || isImageContent(input.content) || isSuccessStatus(input.status)) return {
|
|
16
|
+
url: input.url,
|
|
5
17
|
isValid: true
|
|
6
18
|
};
|
|
7
19
|
return {
|
|
8
|
-
url,
|
|
20
|
+
url: input.url,
|
|
9
21
|
isValid: false
|
|
10
22
|
};
|
|
11
23
|
};
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
//#region src/favicons/extractors.ts
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const isImageContentType = (headers) => {
|
|
3
|
+
return headers?.get("content-type")?.startsWith("image/") ?? false;
|
|
4
|
+
};
|
|
5
|
+
const isImageContent = (content) => {
|
|
6
|
+
if (content.includes("<html")) return false;
|
|
7
|
+
const trimmed = content.trimStart();
|
|
8
|
+
const head = trimmed.slice(0, 200);
|
|
9
|
+
return trimmed.startsWith("<svg") || trimmed.startsWith("<?xml") && head.includes("<svg") || content.slice(1, 4) === "PNG" || content.startsWith("GIF8") || content.startsWith("RIFF") && content.includes("WEBP");
|
|
10
|
+
};
|
|
11
|
+
const isSuccessStatus = (status) => {
|
|
12
|
+
return status !== void 0 && status >= 200 && status < 400;
|
|
13
|
+
};
|
|
14
|
+
const defaultExtractor = (input) => {
|
|
15
|
+
if (isImageContentType(input.headers) || isImageContent(input.content) || isSuccessStatus(input.status)) return {
|
|
16
|
+
url: input.url,
|
|
5
17
|
isValid: true
|
|
6
18
|
};
|
|
7
19
|
return {
|
|
8
|
-
url,
|
|
20
|
+
url: input.url,
|
|
9
21
|
isValid: false
|
|
10
22
|
};
|
|
11
23
|
};
|
package/dist/favicons/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const require_index = require("../common/discover/index.cjs");
|
|
|
4
4
|
const require_defaults = require("./defaults.cjs");
|
|
5
5
|
const require_extractors = require("./extractors.cjs");
|
|
6
6
|
//#region src/favicons/index.ts
|
|
7
|
-
const discoverFavicons =
|
|
7
|
+
const discoverFavicons = (input, options = {}) => {
|
|
8
8
|
return require_index.discover(input, {
|
|
9
9
|
...options,
|
|
10
10
|
methods: options.methods ?? [
|
|
@@ -16,7 +16,8 @@ const discoverFavicons = async (input, options = {}) => {
|
|
|
16
16
|
],
|
|
17
17
|
fetchFn: options.fetchFn ?? require_utils$1.defaultFetchFn,
|
|
18
18
|
extractFn: options.extractFn ?? require_extractors.defaultExtractor,
|
|
19
|
-
|
|
19
|
+
resolveUrlFn: options.resolveUrlFn ?? require_utils.resolveUrl,
|
|
20
|
+
resolveSiteUrlFn: options.resolveSiteUrlFn ?? require_utils$1.defaultResolveSiteUrlFn
|
|
20
21
|
}, {
|
|
21
22
|
platform: require_defaults.defaultPlatformOptions,
|
|
22
23
|
feed: require_defaults.defaultFeedOptions,
|
package/dist/favicons/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { defaultFetchFn } from "../common/discover/utils.js";
|
|
1
|
+
import { resolveUrl } from "../common/utils.js";
|
|
2
|
+
import { defaultFetchFn, defaultResolveSiteUrlFn } from "../common/discover/utils.js";
|
|
3
3
|
import { discover } from "../common/discover/index.js";
|
|
4
4
|
import { defaultFeedOptions, defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions } from "./defaults.js";
|
|
5
5
|
import { defaultExtractor } from "./extractors.js";
|
|
6
6
|
//#region src/favicons/index.ts
|
|
7
|
-
const discoverFavicons =
|
|
7
|
+
const discoverFavicons = (input, options = {}) => {
|
|
8
8
|
return discover(input, {
|
|
9
9
|
...options,
|
|
10
10
|
methods: options.methods ?? [
|
|
@@ -16,7 +16,8 @@ const discoverFavicons = async (input, options = {}) => {
|
|
|
16
16
|
],
|
|
17
17
|
fetchFn: options.fetchFn ?? defaultFetchFn,
|
|
18
18
|
extractFn: options.extractFn ?? defaultExtractor,
|
|
19
|
-
|
|
19
|
+
resolveUrlFn: options.resolveUrlFn ?? resolveUrl,
|
|
20
|
+
resolveSiteUrlFn: options.resolveSiteUrlFn ?? defaultResolveSiteUrlFn
|
|
20
21
|
}, {
|
|
21
22
|
platform: defaultPlatformOptions,
|
|
22
23
|
feed: defaultFeedOptions,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_utils = require("../../../common/utils.cjs");
|
|
2
|
+
const require_utils$1 = require("../../utils.cjs");
|
|
2
3
|
//#region src/favicons/platform/handlers/bluesky.ts
|
|
3
4
|
const hosts = ["bsky.app", "www.bsky.app"];
|
|
4
5
|
const isProfilePath = (pathname) => {
|
|
@@ -16,9 +17,8 @@ const blueskyHandler = {
|
|
|
16
17
|
if (!fetchFn) return [];
|
|
17
18
|
try {
|
|
18
19
|
const { pathname } = new URL(url);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
if (typeof data.avatar === "string" && data.avatar.length > 0) return [{ uri: data.avatar }];
|
|
20
|
+
const data = require_utils$1.parseBodyJson((await fetchFn(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${pathname.split("/").filter(Boolean)[1]}`)).body);
|
|
21
|
+
if (require_utils$1.isNonEmptyString(data.avatar)) return [{ uri: data.avatar }];
|
|
22
22
|
} catch {}
|
|
23
23
|
return [];
|
|
24
24
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isHostOf } from "../../../common/utils.js";
|
|
2
|
+
import { isNonEmptyString, parseBodyJson } from "../../utils.js";
|
|
2
3
|
//#region src/favicons/platform/handlers/bluesky.ts
|
|
3
4
|
const hosts = ["bsky.app", "www.bsky.app"];
|
|
4
5
|
const isProfilePath = (pathname) => {
|
|
@@ -16,9 +17,8 @@ const blueskyHandler = {
|
|
|
16
17
|
if (!fetchFn) return [];
|
|
17
18
|
try {
|
|
18
19
|
const { pathname } = new URL(url);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
if (typeof data.avatar === "string" && data.avatar.length > 0) return [{ uri: data.avatar }];
|
|
20
|
+
const data = parseBodyJson((await fetchFn(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${pathname.split("/").filter(Boolean)[1]}`)).body);
|
|
21
|
+
if (isNonEmptyString(data.avatar)) return [{ uri: data.avatar }];
|
|
22
22
|
} catch {}
|
|
23
23
|
return [];
|
|
24
24
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
const require_utils = require("../../../common/utils.cjs");
|
|
2
|
+
const require_utils$1 = require("../../utils.cjs");
|
|
1
3
|
//#region src/favicons/platform/handlers/mastodon.ts
|
|
4
|
+
const mastodonRegex = /mastodon/i;
|
|
2
5
|
const isProfilePath = (pathname) => {
|
|
3
6
|
const segments = pathname.split("/").filter(Boolean);
|
|
4
7
|
return segments.length === 1 && segments[0].startsWith("@");
|
|
5
8
|
};
|
|
6
9
|
const isMastodonHtml = (content) => {
|
|
7
|
-
return
|
|
10
|
+
return require_utils.hasMetaContent(content, "generator", "Mastodon");
|
|
8
11
|
};
|
|
9
12
|
const isMastodonHeaders = (headers) => {
|
|
10
|
-
return
|
|
13
|
+
return mastodonRegex.test(headers.get("server") ?? "");
|
|
11
14
|
};
|
|
12
15
|
const mastodonHandler = {
|
|
13
16
|
match: (url, content, headers) => {
|
|
@@ -23,9 +26,8 @@ const mastodonHandler = {
|
|
|
23
26
|
if (!fetchFn) return [];
|
|
24
27
|
try {
|
|
25
28
|
const { hostname, pathname } = new URL(url);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
if (typeof data.avatar === "string" && data.avatar.length > 0) return [{ uri: data.avatar }];
|
|
29
|
+
const data = require_utils$1.parseBodyJson((await fetchFn(`https://${hostname}/api/v1/accounts/lookup?acct=${pathname.split("/").filter(Boolean)[0].replace("@", "")}`)).body);
|
|
30
|
+
if (require_utils$1.isNonEmptyString(data.avatar)) return [{ uri: data.avatar }];
|
|
29
31
|
} catch {}
|
|
30
32
|
return [];
|
|
31
33
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { hasMetaContent } from "../../../common/utils.js";
|
|
2
|
+
import { isNonEmptyString, parseBodyJson } from "../../utils.js";
|
|
1
3
|
//#region src/favicons/platform/handlers/mastodon.ts
|
|
4
|
+
const mastodonRegex = /mastodon/i;
|
|
2
5
|
const isProfilePath = (pathname) => {
|
|
3
6
|
const segments = pathname.split("/").filter(Boolean);
|
|
4
7
|
return segments.length === 1 && segments[0].startsWith("@");
|
|
5
8
|
};
|
|
6
9
|
const isMastodonHtml = (content) => {
|
|
7
|
-
return
|
|
10
|
+
return hasMetaContent(content, "generator", "Mastodon");
|
|
8
11
|
};
|
|
9
12
|
const isMastodonHeaders = (headers) => {
|
|
10
|
-
return
|
|
13
|
+
return mastodonRegex.test(headers.get("server") ?? "");
|
|
11
14
|
};
|
|
12
15
|
const mastodonHandler = {
|
|
13
16
|
match: (url, content, headers) => {
|
|
@@ -23,9 +26,8 @@ const mastodonHandler = {
|
|
|
23
26
|
if (!fetchFn) return [];
|
|
24
27
|
try {
|
|
25
28
|
const { hostname, pathname } = new URL(url);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
if (typeof data.avatar === "string" && data.avatar.length > 0) return [{ uri: data.avatar }];
|
|
29
|
+
const data = parseBodyJson((await fetchFn(`https://${hostname}/api/v1/accounts/lookup?acct=${pathname.split("/").filter(Boolean)[0].replace("@", "")}`)).body);
|
|
30
|
+
if (isNonEmptyString(data.avatar)) return [{ uri: data.avatar }];
|
|
29
31
|
} catch {}
|
|
30
32
|
return [];
|
|
31
33
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_utils = require("../../../common/utils.cjs");
|
|
2
|
+
const require_utils$1 = require("../../utils.cjs");
|
|
2
3
|
const require_reddit = require("../../../feeds/platform/handlers/reddit.cjs");
|
|
3
4
|
//#region src/favicons/platform/handlers/reddit.ts
|
|
4
5
|
const isSubredditPath = (pathname) => {
|
|
@@ -22,16 +23,14 @@ const redditHandler = {
|
|
|
22
23
|
try {
|
|
23
24
|
const { pathname } = new URL(url);
|
|
24
25
|
if (isSubredditPath(pathname)) {
|
|
25
|
-
const
|
|
26
|
-
const data = JSON.parse(typeof response.body === "string" ? response.body : "");
|
|
26
|
+
const data = require_utils$1.parseBodyJson((await fetchFn(`https://www.reddit.com/r/${pathname.split("/").filter(Boolean)[1]}/about.json`)).body);
|
|
27
27
|
const icon = data?.data?.community_icon?.split("?")[0] || data?.data?.icon_img;
|
|
28
|
-
if (
|
|
28
|
+
if (require_utils$1.isNonEmptyString(icon)) return [{ uri: icon }];
|
|
29
29
|
}
|
|
30
30
|
if (isUserPath(pathname)) {
|
|
31
|
-
const
|
|
32
|
-
const data = JSON.parse(typeof response.body === "string" ? response.body : "");
|
|
31
|
+
const data = require_utils$1.parseBodyJson((await fetchFn(`https://www.reddit.com/user/${pathname.split("/").filter(Boolean)[1]}/about.json`)).body);
|
|
33
32
|
const icon = data?.data?.icon_img || data?.data?.snoovatar_img;
|
|
34
|
-
if (
|
|
33
|
+
if (require_utils$1.isNonEmptyString(icon)) return [{ uri: icon }];
|
|
35
34
|
}
|
|
36
35
|
} catch {}
|
|
37
36
|
return [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isHostOf } from "../../../common/utils.js";
|
|
2
|
+
import { isNonEmptyString, parseBodyJson } from "../../utils.js";
|
|
2
3
|
import { hosts } from "../../../feeds/platform/handlers/reddit.js";
|
|
3
4
|
//#region src/favicons/platform/handlers/reddit.ts
|
|
4
5
|
const isSubredditPath = (pathname) => {
|
|
@@ -22,16 +23,14 @@ const redditHandler = {
|
|
|
22
23
|
try {
|
|
23
24
|
const { pathname } = new URL(url);
|
|
24
25
|
if (isSubredditPath(pathname)) {
|
|
25
|
-
const
|
|
26
|
-
const data = JSON.parse(typeof response.body === "string" ? response.body : "");
|
|
26
|
+
const data = parseBodyJson((await fetchFn(`https://www.reddit.com/r/${pathname.split("/").filter(Boolean)[1]}/about.json`)).body);
|
|
27
27
|
const icon = data?.data?.community_icon?.split("?")[0] || data?.data?.icon_img;
|
|
28
|
-
if (
|
|
28
|
+
if (isNonEmptyString(icon)) return [{ uri: icon }];
|
|
29
29
|
}
|
|
30
30
|
if (isUserPath(pathname)) {
|
|
31
|
-
const
|
|
32
|
-
const data = JSON.parse(typeof response.body === "string" ? response.body : "");
|
|
31
|
+
const data = parseBodyJson((await fetchFn(`https://www.reddit.com/user/${pathname.split("/").filter(Boolean)[1]}/about.json`)).body);
|
|
33
32
|
const icon = data?.data?.icon_img || data?.data?.snoovatar_img;
|
|
34
|
-
if (
|
|
33
|
+
if (isNonEmptyString(icon)) return [{ uri: icon }];
|
|
35
34
|
}
|
|
36
35
|
} catch {}
|
|
37
36
|
return [];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/favicons/utils.ts
|
|
2
|
+
const parseBodyJson = (body) => {
|
|
3
|
+
return JSON.parse(typeof body === "string" ? body : "");
|
|
4
|
+
};
|
|
5
|
+
const isNonEmptyString = (value) => {
|
|
6
|
+
return typeof value === "string" && value.length > 0;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
exports.isNonEmptyString = isNonEmptyString;
|
|
10
|
+
exports.parseBodyJson = parseBodyJson;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/favicons/utils.ts
|
|
2
|
+
const parseBodyJson = (body) => {
|
|
3
|
+
return JSON.parse(typeof body === "string" ? body : "");
|
|
4
|
+
};
|
|
5
|
+
const isNonEmptyString = (value) => {
|
|
6
|
+
return typeof value === "string" && value.length > 0;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { isNonEmptyString, parseBodyJson };
|
package/dist/feeds/defaults.cjs
CHANGED
|
@@ -19,6 +19,7 @@ const require_hashnode = require("./platform/handlers/hashnode.cjs");
|
|
|
19
19
|
const require_hatenablog = require("./platform/handlers/hatenablog.cjs");
|
|
20
20
|
const require_itchio = require("./platform/handlers/itchio.cjs");
|
|
21
21
|
const require_kickstarter = require("./platform/handlers/kickstarter.cjs");
|
|
22
|
+
const require_lemmy = require("./platform/handlers/lemmy.cjs");
|
|
22
23
|
const require_letterboxd = require("./platform/handlers/letterboxd.cjs");
|
|
23
24
|
const require_mastodon = require("./platform/handlers/mastodon.cjs");
|
|
24
25
|
const require_medium = require("./platform/handlers/medium.cjs");
|
|
@@ -139,6 +140,7 @@ const defaultPlatformOptions = { handlers: [
|
|
|
139
140
|
require_gitlab.gitlabHandler,
|
|
140
141
|
require_itchio.itchioHandler,
|
|
141
142
|
require_kickstarter.kickstarterHandler,
|
|
143
|
+
require_lemmy.lemmyHandler,
|
|
142
144
|
require_letterboxd.letterboxdHandler,
|
|
143
145
|
require_lobsters.lobstersHandler,
|
|
144
146
|
require_mastodon.mastodonHandler,
|
package/dist/feeds/defaults.js
CHANGED
|
@@ -19,6 +19,7 @@ import { hashnodeHandler } from "./platform/handlers/hashnode.js";
|
|
|
19
19
|
import { hatenablogHandler } from "./platform/handlers/hatenablog.js";
|
|
20
20
|
import { itchioHandler } from "./platform/handlers/itchio.js";
|
|
21
21
|
import { kickstarterHandler } from "./platform/handlers/kickstarter.js";
|
|
22
|
+
import { lemmyHandler } from "./platform/handlers/lemmy.js";
|
|
22
23
|
import { letterboxdHandler } from "./platform/handlers/letterboxd.js";
|
|
23
24
|
import { mastodonHandler } from "./platform/handlers/mastodon.js";
|
|
24
25
|
import { mediumHandler } from "./platform/handlers/medium.js";
|
|
@@ -139,6 +140,7 @@ const defaultPlatformOptions = { handlers: [
|
|
|
139
140
|
gitlabHandler,
|
|
140
141
|
itchioHandler,
|
|
141
142
|
kickstarterHandler,
|
|
143
|
+
lemmyHandler,
|
|
142
144
|
letterboxdHandler,
|
|
143
145
|
lobstersHandler,
|
|
144
146
|
mastodonHandler,
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
+
const require_utils = require("../common/utils.cjs");
|
|
2
|
+
const require_utils$1 = require("../common/discover/utils.cjs");
|
|
1
3
|
let feedsmith = require("feedsmith");
|
|
2
4
|
//#region src/feeds/extractors.ts
|
|
3
|
-
const
|
|
4
|
-
return links?.find((link) => link.rel === rel);
|
|
5
|
-
};
|
|
6
|
-
const defaultExtractor = async ({ content, url }) => {
|
|
5
|
+
const defaultExtractor = ({ content, url }) => {
|
|
7
6
|
if (!content) return {
|
|
8
7
|
url,
|
|
9
8
|
isValid: false
|
|
10
9
|
};
|
|
11
10
|
try {
|
|
12
|
-
const
|
|
11
|
+
const parsed = (0, feedsmith.parseFeed)(content);
|
|
12
|
+
const { format, feed } = parsed;
|
|
13
|
+
const rawSiteUrl = require_utils$1.getFeedSiteUrl(parsed);
|
|
14
|
+
const siteUrl = rawSiteUrl ? require_utils.resolveUrl(rawSiteUrl, url) : void 0;
|
|
13
15
|
if (format === "rss" || format === "rdf") return {
|
|
14
16
|
url,
|
|
15
17
|
isValid: true,
|
|
16
18
|
format,
|
|
17
19
|
title: feed.title,
|
|
18
20
|
description: feed.description,
|
|
19
|
-
siteUrl
|
|
21
|
+
siteUrl
|
|
20
22
|
};
|
|
21
23
|
if (format === "atom") return {
|
|
22
24
|
url,
|
|
@@ -24,7 +26,7 @@ const defaultExtractor = async ({ content, url }) => {
|
|
|
24
26
|
format,
|
|
25
27
|
title: feed.title,
|
|
26
28
|
description: feed.subtitle,
|
|
27
|
-
siteUrl
|
|
29
|
+
siteUrl
|
|
28
30
|
};
|
|
29
31
|
if (format === "json") return {
|
|
30
32
|
url,
|
|
@@ -32,7 +34,7 @@ const defaultExtractor = async ({ content, url }) => {
|
|
|
32
34
|
format,
|
|
33
35
|
title: feed.title,
|
|
34
36
|
description: feed.description,
|
|
35
|
-
siteUrl
|
|
37
|
+
siteUrl
|
|
36
38
|
};
|
|
37
39
|
} catch {}
|
|
38
40
|
return {
|
package/dist/feeds/extractors.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
+
import { resolveUrl } from "../common/utils.js";
|
|
2
|
+
import { getFeedSiteUrl } from "../common/discover/utils.js";
|
|
1
3
|
import { parseFeed } from "feedsmith";
|
|
2
4
|
//#region src/feeds/extractors.ts
|
|
3
|
-
const
|
|
4
|
-
return links?.find((link) => link.rel === rel);
|
|
5
|
-
};
|
|
6
|
-
const defaultExtractor = async ({ content, url }) => {
|
|
5
|
+
const defaultExtractor = ({ content, url }) => {
|
|
7
6
|
if (!content) return {
|
|
8
7
|
url,
|
|
9
8
|
isValid: false
|
|
10
9
|
};
|
|
11
10
|
try {
|
|
12
|
-
const
|
|
11
|
+
const parsed = parseFeed(content);
|
|
12
|
+
const { format, feed } = parsed;
|
|
13
|
+
const rawSiteUrl = getFeedSiteUrl(parsed);
|
|
14
|
+
const siteUrl = rawSiteUrl ? resolveUrl(rawSiteUrl, url) : void 0;
|
|
13
15
|
if (format === "rss" || format === "rdf") return {
|
|
14
16
|
url,
|
|
15
17
|
isValid: true,
|
|
16
18
|
format,
|
|
17
19
|
title: feed.title,
|
|
18
20
|
description: feed.description,
|
|
19
|
-
siteUrl
|
|
21
|
+
siteUrl
|
|
20
22
|
};
|
|
21
23
|
if (format === "atom") return {
|
|
22
24
|
url,
|
|
@@ -24,7 +26,7 @@ const defaultExtractor = async ({ content, url }) => {
|
|
|
24
26
|
format,
|
|
25
27
|
title: feed.title,
|
|
26
28
|
description: feed.subtitle,
|
|
27
|
-
siteUrl
|
|
29
|
+
siteUrl
|
|
28
30
|
};
|
|
29
31
|
if (format === "json") return {
|
|
30
32
|
url,
|
|
@@ -32,7 +34,7 @@ const defaultExtractor = async ({ content, url }) => {
|
|
|
32
34
|
format,
|
|
33
35
|
title: feed.title,
|
|
34
36
|
description: feed.description,
|
|
35
|
-
siteUrl
|
|
37
|
+
siteUrl
|
|
36
38
|
};
|
|
37
39
|
} catch {}
|
|
38
40
|
return {
|
package/dist/feeds/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const require_index = require("../common/discover/index.cjs");
|
|
|
4
4
|
const require_defaults = require("./defaults.cjs");
|
|
5
5
|
const require_extractors = require("./extractors.cjs");
|
|
6
6
|
//#region src/feeds/index.ts
|
|
7
|
-
const discoverFeeds =
|
|
7
|
+
const discoverFeeds = (input, options = {}) => {
|
|
8
8
|
return require_index.discover(input, {
|
|
9
9
|
...options,
|
|
10
10
|
methods: options.methods ?? [
|
|
@@ -15,7 +15,7 @@ const discoverFeeds = async (input, options = {}) => {
|
|
|
15
15
|
],
|
|
16
16
|
fetchFn: options.fetchFn ?? require_utils$1.defaultFetchFn,
|
|
17
17
|
extractFn: options.extractFn ?? require_extractors.defaultExtractor,
|
|
18
|
-
|
|
18
|
+
resolveUrlFn: options.resolveUrlFn ?? require_utils.resolveUrl
|
|
19
19
|
}, {
|
|
20
20
|
platform: require_defaults.defaultPlatformOptions,
|
|
21
21
|
html: require_defaults.defaultHtmlOptions,
|
package/dist/feeds/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveUrl } from "../common/utils.js";
|
|
2
2
|
import { defaultFetchFn } from "../common/discover/utils.js";
|
|
3
3
|
import { discover } from "../common/discover/index.js";
|
|
4
4
|
import { defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions, defaultPlatformOptions } from "./defaults.js";
|
|
5
5
|
import { defaultExtractor } from "./extractors.js";
|
|
6
6
|
//#region src/feeds/index.ts
|
|
7
|
-
const discoverFeeds =
|
|
7
|
+
const discoverFeeds = (input, options = {}) => {
|
|
8
8
|
return discover(input, {
|
|
9
9
|
...options,
|
|
10
10
|
methods: options.methods ?? [
|
|
@@ -15,7 +15,7 @@ const discoverFeeds = async (input, options = {}) => {
|
|
|
15
15
|
],
|
|
16
16
|
fetchFn: options.fetchFn ?? defaultFetchFn,
|
|
17
17
|
extractFn: options.extractFn ?? defaultExtractor,
|
|
18
|
-
|
|
18
|
+
resolveUrlFn: options.resolveUrlFn ?? resolveUrl
|
|
19
19
|
}, {
|
|
20
20
|
platform: defaultPlatformOptions,
|
|
21
21
|
html: defaultHtmlOptions,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const require_utils = require("../../../common/utils.cjs");
|
|
2
2
|
//#region src/feeds/platform/handlers/blogspot.ts
|
|
3
3
|
const blogspotDomainRegex = /^.+\.blogspot\.(?:com|co\.[a-z]{2}|com\.[a-z]{2}|[a-z]{2,3})$/;
|
|
4
|
+
const labelRegex = /^\/search\/label\/([^/]+)/;
|
|
4
5
|
const blogspotHandler = {
|
|
5
6
|
match: (url) => {
|
|
6
7
|
try {
|
|
@@ -12,7 +13,7 @@ const blogspotHandler = {
|
|
|
12
13
|
resolve: (url) => {
|
|
13
14
|
const { origin, pathname } = new URL(url);
|
|
14
15
|
const uris = [];
|
|
15
|
-
const labelMatch = pathname.match(
|
|
16
|
+
const labelMatch = pathname.match(labelRegex);
|
|
16
17
|
if (labelMatch?.[1]) {
|
|
17
18
|
const label = labelMatch[1];
|
|
18
19
|
uris.push({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { composeHint } from "../../../common/utils.js";
|
|
2
2
|
//#region src/feeds/platform/handlers/blogspot.ts
|
|
3
3
|
const blogspotDomainRegex = /^.+\.blogspot\.(?:com|co\.[a-z]{2}|com\.[a-z]{2}|[a-z]{2,3})$/;
|
|
4
|
+
const labelRegex = /^\/search\/label\/([^/]+)/;
|
|
4
5
|
const blogspotHandler = {
|
|
5
6
|
match: (url) => {
|
|
6
7
|
try {
|
|
@@ -12,7 +13,7 @@ const blogspotHandler = {
|
|
|
12
13
|
resolve: (url) => {
|
|
13
14
|
const { origin, pathname } = new URL(url);
|
|
14
15
|
const uris = [];
|
|
15
|
-
const labelMatch = pathname.match(
|
|
16
|
+
const labelMatch = pathname.match(labelRegex);
|
|
16
17
|
if (labelMatch?.[1]) {
|
|
17
18
|
const label = labelMatch[1];
|
|
18
19
|
uris.push({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_utils = require("../../../common/utils.cjs");
|
|
2
2
|
//#region src/feeds/platform/handlers/bluesky.ts
|
|
3
|
+
const profileRegex = /^\/profile\/([^/]+)/;
|
|
3
4
|
const hosts = ["bsky.app", "www.bsky.app"];
|
|
4
5
|
const blueskyHandler = {
|
|
5
6
|
match: (url) => {
|
|
@@ -7,7 +8,7 @@ const blueskyHandler = {
|
|
|
7
8
|
},
|
|
8
9
|
resolve: (url) => {
|
|
9
10
|
const { pathname } = new URL(url);
|
|
10
|
-
const handle = pathname.match(
|
|
11
|
+
const handle = pathname.match(profileRegex)?.[1];
|
|
11
12
|
if (!handle) return [];
|
|
12
13
|
return [{
|
|
13
14
|
uri: `https://bsky.app/profile/${handle}/rss`,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { composeHint, isHostOf } from "../../../common/utils.js";
|
|
2
2
|
//#region src/feeds/platform/handlers/bluesky.ts
|
|
3
|
+
const profileRegex = /^\/profile\/([^/]+)/;
|
|
3
4
|
const hosts = ["bsky.app", "www.bsky.app"];
|
|
4
5
|
const blueskyHandler = {
|
|
5
6
|
match: (url) => {
|
|
@@ -7,7 +8,7 @@ const blueskyHandler = {
|
|
|
7
8
|
},
|
|
8
9
|
resolve: (url) => {
|
|
9
10
|
const { pathname } = new URL(url);
|
|
10
|
-
const handle = pathname.match(
|
|
11
|
+
const handle = pathname.match(profileRegex)?.[1];
|
|
11
12
|
if (!handle) return [];
|
|
12
13
|
return [{
|
|
13
14
|
uri: `https://bsky.app/profile/${handle}/rss`,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_utils = require("../../../common/utils.cjs");
|
|
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(
|
|
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`],
|