feedscout 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +2 -1
- package/dist/common/uris/guess/utils.js +2 -1
- package/dist/common/uris/headers/index.cjs +2 -1
- package/dist/common/uris/headers/index.js +2 -1
- package/dist/common/utils.cjs +10 -3
- package/dist/common/utils.d.cts +2 -1
- package/dist/common/utils.d.ts +2 -1
- package/dist/common/utils.js +9 -3
- 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/dist/utils.cjs +1 -0
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Finds feeds by scanning links and anchors in HTML content, parsing HTTP headers,
|
|
|
22
22
|
| --- | --- |
|
|
23
23
|
| Feeds | RSS, Atom, JSON Feed, and RDF. Each feed is validated and returns metadata like format, title, description, and site URL. |
|
|
24
24
|
| Blogrolls | OPML files containing feed subscriptions. Validated and returns title. |
|
|
25
|
+
| Favicons | Site icons from HTML, feeds, platform APIs, and common paths. Validated to ensure URLs point to actual images. |
|
|
25
26
|
| WebSub hubs | Find hubs for real-time feed update notifications. |
|
|
26
27
|
|
|
27
28
|
### Discovery Methods
|
|
@@ -144,6 +145,19 @@ const blogrolls = await discoverBlogrolls('https://example.com')
|
|
|
144
145
|
// }]
|
|
145
146
|
```
|
|
146
147
|
|
|
148
|
+
### Discover Favicons
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { discoverFavicons } from 'feedscout'
|
|
152
|
+
|
|
153
|
+
const favicons = await discoverFavicons('https://example.com')
|
|
154
|
+
|
|
155
|
+
// [{
|
|
156
|
+
// url: 'https://example.com/apple-touch-icon.png',
|
|
157
|
+
// isValid: true,
|
|
158
|
+
// }]
|
|
159
|
+
```
|
|
160
|
+
|
|
147
161
|
### Discover WebSub Hubs
|
|
148
162
|
|
|
149
163
|
```typescript
|
package/dist/blogrolls/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/blogrolls/index.ts
|
|
7
|
-
const discoverBlogrolls =
|
|
7
|
+
const discoverBlogrolls = (input, options = {}) => {
|
|
8
8
|
return require_index.discover(input, {
|
|
9
9
|
...options,
|
|
10
10
|
methods: options.methods ?? [
|
|
@@ -14,7 +14,8 @@ const discoverBlogrolls = async (input, options = {}) => {
|
|
|
14
14
|
],
|
|
15
15
|
fetchFn: options.fetchFn ?? require_utils$1.defaultFetchFn,
|
|
16
16
|
extractFn: options.extractFn ?? require_extractors.defaultExtractor,
|
|
17
|
-
|
|
17
|
+
resolveUrlFn: options.resolveUrlFn ?? require_utils.resolveUrl,
|
|
18
|
+
resolveSiteUrlFn: options.resolveSiteUrlFn ?? require_utils$1.defaultResolveSiteUrlFn
|
|
18
19
|
}, {
|
|
19
20
|
html: require_defaults.defaultHtmlOptions,
|
|
20
21
|
headers: require_defaults.defaultHeadersOptions,
|
package/dist/blogrolls/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 { defaultGuessOptions, defaultHeadersOptions, defaultHtmlOptions } from "./defaults.js";
|
|
5
5
|
import { defaultExtractor } from "./extractors.js";
|
|
6
6
|
//#region src/blogrolls/index.ts
|
|
7
|
-
const discoverBlogrolls =
|
|
7
|
+
const discoverBlogrolls = (input, options = {}) => {
|
|
8
8
|
return discover(input, {
|
|
9
9
|
...options,
|
|
10
10
|
methods: options.methods ?? [
|
|
@@ -14,7 +14,8 @@ const discoverBlogrolls = async (input, options = {}) => {
|
|
|
14
14
|
],
|
|
15
15
|
fetchFn: options.fetchFn ?? defaultFetchFn,
|
|
16
16
|
extractFn: options.extractFn ?? defaultExtractor,
|
|
17
|
-
|
|
17
|
+
resolveUrlFn: options.resolveUrlFn ?? resolveUrl,
|
|
18
|
+
resolveSiteUrlFn: options.resolveSiteUrlFn ?? defaultResolveSiteUrlFn
|
|
18
19
|
}, {
|
|
19
20
|
html: defaultHtmlOptions,
|
|
20
21
|
headers: defaultHeadersOptions,
|
|
@@ -4,24 +4,36 @@ const require_index = require("../uris/index.cjs");
|
|
|
4
4
|
const require_utils$1 = require("./utils.cjs");
|
|
5
5
|
//#region src/common/discover/index.ts
|
|
6
6
|
const discover = async (input, options, defaults) => {
|
|
7
|
-
const { methods, fetchFn, extractFn,
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
7
|
+
const { methods, fetchFn, extractFn, resolveUrlFn, resolveSiteUrlFn, stopOnFirstMethod = false, stopOnFirstResult = false, concurrency = 3, includeInvalid = false, onProgress } = options;
|
|
8
|
+
const sourceInput = await require_utils$1.normalizeInput(input, fetchFn);
|
|
9
|
+
if (sourceInput.content) {
|
|
10
10
|
const result = await extractFn({
|
|
11
|
-
url:
|
|
12
|
-
content:
|
|
13
|
-
headers:
|
|
11
|
+
url: sourceInput.url,
|
|
12
|
+
content: sourceInput.content,
|
|
13
|
+
headers: sourceInput.headers
|
|
14
14
|
});
|
|
15
15
|
if (result.isValid) return [result];
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
let siteInput;
|
|
18
|
+
if (resolveSiteUrlFn) {
|
|
19
|
+
const siteUrl = resolveSiteUrlFn(sourceInput);
|
|
20
|
+
if (siteUrl) try {
|
|
21
|
+
const response = await fetchFn(siteUrl);
|
|
22
|
+
siteInput = {
|
|
23
|
+
url: response.url,
|
|
24
|
+
content: typeof response.body === "string" ? response.body : "",
|
|
25
|
+
headers: response.headers
|
|
26
|
+
};
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
const urisByMethod = await require_index.discoverUris(require_utils$1.normalizeMethodsConfig(sourceInput, siteInput, methods, defaults), fetchFn);
|
|
18
30
|
const seen = /* @__PURE__ */ new Set();
|
|
19
31
|
const methodGroups = [];
|
|
20
32
|
for (const method of require_types.discoverMethodOrder) {
|
|
21
33
|
const rawUris = urisByMethod[method];
|
|
22
|
-
if (!rawUris
|
|
34
|
+
if (!rawUris || rawUris.length === 0) continue;
|
|
23
35
|
const unique = rawUris.map((entry) => {
|
|
24
|
-
return require_utils$1.normalizeUriEntry(entry,
|
|
36
|
+
return require_utils$1.normalizeUriEntry(entry, resolveUrlFn, sourceInput.url);
|
|
25
37
|
}).filter((entry) => {
|
|
26
38
|
const key = typeof entry.uri === "string" ? entry.uri : entry.uri.join("\0");
|
|
27
39
|
if (seen.has(key)) return false;
|
|
@@ -4,24 +4,36 @@ import { discoverUris } from "../uris/index.js";
|
|
|
4
4
|
import { normalizeInput, normalizeMethodsConfig, normalizeUriEntry } from "./utils.js";
|
|
5
5
|
//#region src/common/discover/index.ts
|
|
6
6
|
const discover = async (input, options, defaults) => {
|
|
7
|
-
const { methods, fetchFn, extractFn,
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
7
|
+
const { methods, fetchFn, extractFn, resolveUrlFn, resolveSiteUrlFn, stopOnFirstMethod = false, stopOnFirstResult = false, concurrency = 3, includeInvalid = false, onProgress } = options;
|
|
8
|
+
const sourceInput = await normalizeInput(input, fetchFn);
|
|
9
|
+
if (sourceInput.content) {
|
|
10
10
|
const result = await extractFn({
|
|
11
|
-
url:
|
|
12
|
-
content:
|
|
13
|
-
headers:
|
|
11
|
+
url: sourceInput.url,
|
|
12
|
+
content: sourceInput.content,
|
|
13
|
+
headers: sourceInput.headers
|
|
14
14
|
});
|
|
15
15
|
if (result.isValid) return [result];
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
let siteInput;
|
|
18
|
+
if (resolveSiteUrlFn) {
|
|
19
|
+
const siteUrl = resolveSiteUrlFn(sourceInput);
|
|
20
|
+
if (siteUrl) try {
|
|
21
|
+
const response = await fetchFn(siteUrl);
|
|
22
|
+
siteInput = {
|
|
23
|
+
url: response.url,
|
|
24
|
+
content: typeof response.body === "string" ? response.body : "",
|
|
25
|
+
headers: response.headers
|
|
26
|
+
};
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
const urisByMethod = await discoverUris(normalizeMethodsConfig(sourceInput, siteInput, methods, defaults), fetchFn);
|
|
18
30
|
const seen = /* @__PURE__ */ new Set();
|
|
19
31
|
const methodGroups = [];
|
|
20
32
|
for (const method of discoverMethodOrder) {
|
|
21
33
|
const rawUris = urisByMethod[method];
|
|
22
|
-
if (!rawUris
|
|
34
|
+
if (!rawUris || rawUris.length === 0) continue;
|
|
23
35
|
const unique = rawUris.map((entry) => {
|
|
24
|
-
return normalizeUriEntry(entry,
|
|
36
|
+
return normalizeUriEntry(entry, resolveUrlFn, sourceInput.url);
|
|
25
37
|
}).filter((entry) => {
|
|
26
38
|
const key = typeof entry.uri === "string" ? entry.uri : entry.uri.join("\0");
|
|
27
39
|
if (seen.has(key)) return false;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const require_locales = require("../locales.cjs");
|
|
2
|
+
const require_utils = require("../utils.cjs");
|
|
3
|
+
let feedsmith = require("feedsmith");
|
|
2
4
|
//#region src/common/discover/utils.ts
|
|
3
5
|
const defaultFetchFn = async (url, options) => {
|
|
4
6
|
const response = await fetch(url, {
|
|
@@ -22,37 +24,59 @@ const normalizeInput = async (input, fetchFn) => {
|
|
|
22
24
|
headers: response.headers
|
|
23
25
|
};
|
|
24
26
|
};
|
|
25
|
-
const
|
|
27
|
+
const getLinkOfType = (links, rel) => {
|
|
28
|
+
return links?.find((link) => link.rel === rel);
|
|
29
|
+
};
|
|
30
|
+
const getFeedSiteUrl = (parsed) => {
|
|
31
|
+
const { format, feed } = parsed;
|
|
32
|
+
if (format === "rss" || format === "rdf") return getLinkOfType(feed.atom?.links, "alternate")?.href ?? feed.link;
|
|
33
|
+
if (format === "atom") return getLinkOfType(feed.links, "alternate")?.href;
|
|
34
|
+
if (format === "json") return feed.home_page_url;
|
|
35
|
+
};
|
|
36
|
+
const defaultResolveSiteUrlFn = (input) => {
|
|
37
|
+
if (!input.content) return;
|
|
38
|
+
try {
|
|
39
|
+
let siteUrl = getFeedSiteUrl((0, feedsmith.parseFeed)(input.content));
|
|
40
|
+
if (!siteUrl) try {
|
|
41
|
+
siteUrl = new URL(input.url).origin;
|
|
42
|
+
} catch {}
|
|
43
|
+
else siteUrl = require_utils.resolveUrl(siteUrl, input.url);
|
|
44
|
+
if (siteUrl && new URL(siteUrl).href === new URL(input.url).href) return;
|
|
45
|
+
return siteUrl;
|
|
46
|
+
} catch {}
|
|
47
|
+
};
|
|
48
|
+
const normalizeUriEntry = (entry, resolveUrlFn, baseUrl) => {
|
|
26
49
|
if (typeof entry.uri === "string") return {
|
|
27
50
|
...entry,
|
|
28
|
-
uri:
|
|
51
|
+
uri: resolveUrlFn(entry.uri, baseUrl)
|
|
29
52
|
};
|
|
30
53
|
return {
|
|
31
54
|
...entry,
|
|
32
|
-
uri: entry.uri.map((uri) =>
|
|
55
|
+
uri: entry.uri.map((uri) => resolveUrlFn(uri, baseUrl))
|
|
33
56
|
};
|
|
34
57
|
};
|
|
35
|
-
const normalizeMethodsConfig = (
|
|
58
|
+
const normalizeMethodsConfig = (sourceInput, siteInput, methods, defaults) => {
|
|
59
|
+
const resolvedInput = siteInput ?? sourceInput;
|
|
36
60
|
const methodsObj = Array.isArray(methods) ? Object.fromEntries(methods.map((method) => [method, true])) : methods;
|
|
37
61
|
const methodsConfig = {};
|
|
38
62
|
if (methodsObj.platform && defaults.platform) {
|
|
39
|
-
if (!
|
|
63
|
+
if (!resolvedInput.url || resolvedInput.url === "") throw new Error(require_locales.errors.platformMethodRequiresUrl);
|
|
40
64
|
const platformOptions = methodsObj.platform === true ? {} : methodsObj.platform;
|
|
41
65
|
methodsConfig.platform = {
|
|
42
|
-
content:
|
|
43
|
-
headers:
|
|
66
|
+
content: resolvedInput.content,
|
|
67
|
+
headers: resolvedInput.headers,
|
|
44
68
|
options: {
|
|
45
69
|
...defaults.platform,
|
|
46
70
|
...platformOptions,
|
|
47
|
-
baseUrl:
|
|
71
|
+
baseUrl: resolvedInput.url
|
|
48
72
|
}
|
|
49
73
|
};
|
|
50
74
|
}
|
|
51
75
|
if (methodsObj.feed && defaults.feed) {
|
|
52
|
-
if (
|
|
76
|
+
if (sourceInput.content === void 0) throw new Error(require_locales.errors.feedMethodRequiresContent);
|
|
53
77
|
const feedOptions = methodsObj.feed === true ? {} : methodsObj.feed;
|
|
54
78
|
methodsConfig.feed = {
|
|
55
|
-
content:
|
|
79
|
+
content: sourceInput.content,
|
|
56
80
|
options: {
|
|
57
81
|
...defaults.feed,
|
|
58
82
|
...feedOptions
|
|
@@ -60,42 +84,44 @@ const normalizeMethodsConfig = (input, methods, defaults) => {
|
|
|
60
84
|
};
|
|
61
85
|
}
|
|
62
86
|
if (methodsObj.html && defaults.html) {
|
|
63
|
-
if (
|
|
87
|
+
if (resolvedInput.content === void 0) throw new Error(require_locales.errors.htmlMethodRequiresContent);
|
|
64
88
|
const htmlOptions = methodsObj.html === true ? {} : methodsObj.html;
|
|
65
89
|
methodsConfig.html = {
|
|
66
|
-
html:
|
|
90
|
+
html: resolvedInput.content,
|
|
67
91
|
options: {
|
|
68
92
|
...defaults.html,
|
|
69
93
|
...htmlOptions,
|
|
70
|
-
baseUrl:
|
|
94
|
+
baseUrl: resolvedInput.url
|
|
71
95
|
}
|
|
72
96
|
};
|
|
73
97
|
}
|
|
74
98
|
if (methodsObj.headers && defaults.headers) {
|
|
75
|
-
if (
|
|
99
|
+
if (resolvedInput.headers === void 0) throw new Error(require_locales.errors.headersMethodRequiresHeaders);
|
|
76
100
|
const headersOptions = methodsObj.headers === true ? {} : methodsObj.headers;
|
|
77
101
|
methodsConfig.headers = {
|
|
78
|
-
headers:
|
|
102
|
+
headers: resolvedInput.headers,
|
|
79
103
|
options: {
|
|
80
104
|
...defaults.headers,
|
|
81
105
|
...headersOptions,
|
|
82
|
-
baseUrl:
|
|
106
|
+
baseUrl: resolvedInput.url
|
|
83
107
|
}
|
|
84
108
|
};
|
|
85
109
|
}
|
|
86
110
|
if (methodsObj.guess && defaults.guess) {
|
|
87
|
-
if (!
|
|
111
|
+
if (!resolvedInput.url || resolvedInput.url === "") throw new Error(require_locales.errors.guessMethodRequiresUrl);
|
|
88
112
|
const guessOptions = methodsObj.guess === true ? {} : methodsObj.guess;
|
|
89
113
|
methodsConfig.guess = { options: {
|
|
90
114
|
...defaults.guess,
|
|
91
115
|
...guessOptions,
|
|
92
|
-
baseUrl:
|
|
116
|
+
baseUrl: resolvedInput.url
|
|
93
117
|
} };
|
|
94
118
|
}
|
|
95
119
|
return methodsConfig;
|
|
96
120
|
};
|
|
97
121
|
//#endregion
|
|
98
122
|
exports.defaultFetchFn = defaultFetchFn;
|
|
123
|
+
exports.defaultResolveSiteUrlFn = defaultResolveSiteUrlFn;
|
|
124
|
+
exports.getFeedSiteUrl = getFeedSiteUrl;
|
|
99
125
|
exports.normalizeInput = normalizeInput;
|
|
100
126
|
exports.normalizeMethodsConfig = normalizeMethodsConfig;
|
|
101
127
|
exports.normalizeUriEntry = normalizeUriEntry;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DiscoverResolveSiteUrlFn } from "../types.cjs";
|
|
2
|
+
import { parseFeed } from "feedsmith";
|
|
3
|
+
|
|
4
|
+
//#region src/common/discover/utils.d.ts
|
|
5
|
+
declare const getFeedSiteUrl: (parsed: ReturnType<typeof parseFeed>) => string | undefined;
|
|
6
|
+
declare const defaultResolveSiteUrlFn: DiscoverResolveSiteUrlFn;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { defaultResolveSiteUrlFn, getFeedSiteUrl };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DiscoverResolveSiteUrlFn } from "../types.js";
|
|
2
|
+
import { parseFeed } from "feedsmith";
|
|
3
|
+
|
|
4
|
+
//#region src/common/discover/utils.d.ts
|
|
5
|
+
declare const getFeedSiteUrl: (parsed: ReturnType<typeof parseFeed>) => string | undefined;
|
|
6
|
+
declare const defaultResolveSiteUrlFn: DiscoverResolveSiteUrlFn;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { defaultResolveSiteUrlFn, getFeedSiteUrl };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { errors } from "../locales.js";
|
|
2
|
+
import { resolveUrl } from "../utils.js";
|
|
3
|
+
import { parseFeed } from "feedsmith";
|
|
2
4
|
//#region src/common/discover/utils.ts
|
|
3
5
|
const defaultFetchFn = async (url, options) => {
|
|
4
6
|
const response = await fetch(url, {
|
|
@@ -22,37 +24,59 @@ const normalizeInput = async (input, fetchFn) => {
|
|
|
22
24
|
headers: response.headers
|
|
23
25
|
};
|
|
24
26
|
};
|
|
25
|
-
const
|
|
27
|
+
const getLinkOfType = (links, rel) => {
|
|
28
|
+
return links?.find((link) => link.rel === rel);
|
|
29
|
+
};
|
|
30
|
+
const getFeedSiteUrl = (parsed) => {
|
|
31
|
+
const { format, feed } = parsed;
|
|
32
|
+
if (format === "rss" || format === "rdf") return getLinkOfType(feed.atom?.links, "alternate")?.href ?? feed.link;
|
|
33
|
+
if (format === "atom") return getLinkOfType(feed.links, "alternate")?.href;
|
|
34
|
+
if (format === "json") return feed.home_page_url;
|
|
35
|
+
};
|
|
36
|
+
const defaultResolveSiteUrlFn = (input) => {
|
|
37
|
+
if (!input.content) return;
|
|
38
|
+
try {
|
|
39
|
+
let siteUrl = getFeedSiteUrl(parseFeed(input.content));
|
|
40
|
+
if (!siteUrl) try {
|
|
41
|
+
siteUrl = new URL(input.url).origin;
|
|
42
|
+
} catch {}
|
|
43
|
+
else siteUrl = resolveUrl(siteUrl, input.url);
|
|
44
|
+
if (siteUrl && new URL(siteUrl).href === new URL(input.url).href) return;
|
|
45
|
+
return siteUrl;
|
|
46
|
+
} catch {}
|
|
47
|
+
};
|
|
48
|
+
const normalizeUriEntry = (entry, resolveUrlFn, baseUrl) => {
|
|
26
49
|
if (typeof entry.uri === "string") return {
|
|
27
50
|
...entry,
|
|
28
|
-
uri:
|
|
51
|
+
uri: resolveUrlFn(entry.uri, baseUrl)
|
|
29
52
|
};
|
|
30
53
|
return {
|
|
31
54
|
...entry,
|
|
32
|
-
uri: entry.uri.map((uri) =>
|
|
55
|
+
uri: entry.uri.map((uri) => resolveUrlFn(uri, baseUrl))
|
|
33
56
|
};
|
|
34
57
|
};
|
|
35
|
-
const normalizeMethodsConfig = (
|
|
58
|
+
const normalizeMethodsConfig = (sourceInput, siteInput, methods, defaults) => {
|
|
59
|
+
const resolvedInput = siteInput ?? sourceInput;
|
|
36
60
|
const methodsObj = Array.isArray(methods) ? Object.fromEntries(methods.map((method) => [method, true])) : methods;
|
|
37
61
|
const methodsConfig = {};
|
|
38
62
|
if (methodsObj.platform && defaults.platform) {
|
|
39
|
-
if (!
|
|
63
|
+
if (!resolvedInput.url || resolvedInput.url === "") throw new Error(errors.platformMethodRequiresUrl);
|
|
40
64
|
const platformOptions = methodsObj.platform === true ? {} : methodsObj.platform;
|
|
41
65
|
methodsConfig.platform = {
|
|
42
|
-
content:
|
|
43
|
-
headers:
|
|
66
|
+
content: resolvedInput.content,
|
|
67
|
+
headers: resolvedInput.headers,
|
|
44
68
|
options: {
|
|
45
69
|
...defaults.platform,
|
|
46
70
|
...platformOptions,
|
|
47
|
-
baseUrl:
|
|
71
|
+
baseUrl: resolvedInput.url
|
|
48
72
|
}
|
|
49
73
|
};
|
|
50
74
|
}
|
|
51
75
|
if (methodsObj.feed && defaults.feed) {
|
|
52
|
-
if (
|
|
76
|
+
if (sourceInput.content === void 0) throw new Error(errors.feedMethodRequiresContent);
|
|
53
77
|
const feedOptions = methodsObj.feed === true ? {} : methodsObj.feed;
|
|
54
78
|
methodsConfig.feed = {
|
|
55
|
-
content:
|
|
79
|
+
content: sourceInput.content,
|
|
56
80
|
options: {
|
|
57
81
|
...defaults.feed,
|
|
58
82
|
...feedOptions
|
|
@@ -60,39 +84,39 @@ const normalizeMethodsConfig = (input, methods, defaults) => {
|
|
|
60
84
|
};
|
|
61
85
|
}
|
|
62
86
|
if (methodsObj.html && defaults.html) {
|
|
63
|
-
if (
|
|
87
|
+
if (resolvedInput.content === void 0) throw new Error(errors.htmlMethodRequiresContent);
|
|
64
88
|
const htmlOptions = methodsObj.html === true ? {} : methodsObj.html;
|
|
65
89
|
methodsConfig.html = {
|
|
66
|
-
html:
|
|
90
|
+
html: resolvedInput.content,
|
|
67
91
|
options: {
|
|
68
92
|
...defaults.html,
|
|
69
93
|
...htmlOptions,
|
|
70
|
-
baseUrl:
|
|
94
|
+
baseUrl: resolvedInput.url
|
|
71
95
|
}
|
|
72
96
|
};
|
|
73
97
|
}
|
|
74
98
|
if (methodsObj.headers && defaults.headers) {
|
|
75
|
-
if (
|
|
99
|
+
if (resolvedInput.headers === void 0) throw new Error(errors.headersMethodRequiresHeaders);
|
|
76
100
|
const headersOptions = methodsObj.headers === true ? {} : methodsObj.headers;
|
|
77
101
|
methodsConfig.headers = {
|
|
78
|
-
headers:
|
|
102
|
+
headers: resolvedInput.headers,
|
|
79
103
|
options: {
|
|
80
104
|
...defaults.headers,
|
|
81
105
|
...headersOptions,
|
|
82
|
-
baseUrl:
|
|
106
|
+
baseUrl: resolvedInput.url
|
|
83
107
|
}
|
|
84
108
|
};
|
|
85
109
|
}
|
|
86
110
|
if (methodsObj.guess && defaults.guess) {
|
|
87
|
-
if (!
|
|
111
|
+
if (!resolvedInput.url || resolvedInput.url === "") throw new Error(errors.guessMethodRequiresUrl);
|
|
88
112
|
const guessOptions = methodsObj.guess === true ? {} : methodsObj.guess;
|
|
89
113
|
methodsConfig.guess = { options: {
|
|
90
114
|
...defaults.guess,
|
|
91
115
|
...guessOptions,
|
|
92
|
-
baseUrl:
|
|
116
|
+
baseUrl: resolvedInput.url
|
|
93
117
|
} };
|
|
94
118
|
}
|
|
95
119
|
return methodsConfig;
|
|
96
120
|
};
|
|
97
121
|
//#endregion
|
|
98
|
-
export { defaultFetchFn, normalizeInput, normalizeMethodsConfig, normalizeUriEntry };
|
|
122
|
+
export { defaultFetchFn, defaultResolveSiteUrlFn, getFeedSiteUrl, normalizeInput, normalizeMethodsConfig, normalizeUriEntry };
|
package/dist/common/locales.cjs
CHANGED
|
@@ -118,7 +118,9 @@ var hints = {
|
|
|
118
118
|
"v2ex:node": "Node",
|
|
119
119
|
"v2ex:member": "Member",
|
|
120
120
|
"v2ex:tab": "Tab",
|
|
121
|
-
"ximalaya:album": "Album"
|
|
121
|
+
"ximalaya:album": "Album",
|
|
122
|
+
"lemmy:community": "Community",
|
|
123
|
+
"lemmy:user": "User"
|
|
122
124
|
};
|
|
123
125
|
//#endregion
|
|
124
126
|
Object.defineProperty(exports, "errors", {
|
package/dist/common/locales.js
CHANGED
|
@@ -118,7 +118,9 @@ var hints = {
|
|
|
118
118
|
"v2ex:node": "Node",
|
|
119
119
|
"v2ex:member": "Member",
|
|
120
120
|
"v2ex:tab": "Tab",
|
|
121
|
-
"ximalaya:album": "Album"
|
|
121
|
+
"ximalaya:album": "Album",
|
|
122
|
+
"lemmy:community": "Community",
|
|
123
|
+
"lemmy:user": "User"
|
|
122
124
|
};
|
|
123
125
|
//#endregion
|
|
124
126
|
export { errors, hints };
|
package/dist/common/types.d.cts
CHANGED
|
@@ -20,7 +20,8 @@ type LinkSelector = {
|
|
|
20
20
|
rel: string;
|
|
21
21
|
types?: Array<string>;
|
|
22
22
|
};
|
|
23
|
-
type
|
|
23
|
+
type DiscoverResolveUrlFn = (url: string, baseUrl: string | undefined) => string;
|
|
24
|
+
type DiscoverResolveSiteUrlFn = (input: DiscoverInputObject) => string | undefined;
|
|
24
25
|
type DiscoverFetchFnOptions = {
|
|
25
26
|
method?: 'GET' | 'HEAD';
|
|
26
27
|
headers?: Record<string, string>;
|
|
@@ -57,7 +58,7 @@ type DiscoverExtractFn<TValid> = (input: {
|
|
|
57
58
|
content: string;
|
|
58
59
|
headers?: Headers;
|
|
59
60
|
status?: number;
|
|
60
|
-
}) => Promise<DiscoverResult<TValid
|
|
61
|
+
}) => Promise<DiscoverResult<TValid>> | DiscoverResult<TValid>;
|
|
61
62
|
type DiscoverInputObject = {
|
|
62
63
|
url: string;
|
|
63
64
|
content?: string;
|
|
@@ -75,7 +76,8 @@ type DiscoverOptions<TValid, TMethods extends DiscoverMethod = DiscoverMethod> =
|
|
|
75
76
|
methods?: DiscoverMethodsConfig<TMethods>;
|
|
76
77
|
fetchFn?: DiscoverFetchFn;
|
|
77
78
|
extractFn?: DiscoverExtractFn<TValid>;
|
|
78
|
-
|
|
79
|
+
resolveUrlFn?: DiscoverResolveUrlFn;
|
|
80
|
+
resolveSiteUrlFn?: DiscoverResolveSiteUrlFn;
|
|
79
81
|
stopOnFirstMethod?: boolean;
|
|
80
82
|
stopOnFirstResult?: boolean;
|
|
81
83
|
concurrency?: number;
|
|
@@ -83,4 +85,4 @@ type DiscoverOptions<TValid, TMethods extends DiscoverMethod = DiscoverMethod> =
|
|
|
83
85
|
includeInvalid?: boolean;
|
|
84
86
|
};
|
|
85
87
|
//#endregion
|
|
86
|
-
export { DiscoverExtractFn, DiscoverFetchFn, DiscoverFetchFnOptions, DiscoverFetchFnResponse, DiscoverInput, DiscoverInputObject, DiscoverMethod, DiscoverMethodsConfig,
|
|
88
|
+
export { DiscoverExtractFn, DiscoverFetchFn, DiscoverFetchFnOptions, DiscoverFetchFnResponse, DiscoverInput, DiscoverInputObject, DiscoverMethod, DiscoverMethodsConfig, DiscoverOnProgressFn, DiscoverOptions, DiscoverProgress, DiscoverResolveSiteUrlFn, DiscoverResolveUrlFn, DiscoverResult, DiscoverUriEntry, DiscoverUriHint, LinkSelector, UriEntry };
|
package/dist/common/types.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ type LinkSelector = {
|
|
|
20
20
|
rel: string;
|
|
21
21
|
types?: Array<string>;
|
|
22
22
|
};
|
|
23
|
-
type
|
|
23
|
+
type DiscoverResolveUrlFn = (url: string, baseUrl: string | undefined) => string;
|
|
24
|
+
type DiscoverResolveSiteUrlFn = (input: DiscoverInputObject) => string | undefined;
|
|
24
25
|
type DiscoverFetchFnOptions = {
|
|
25
26
|
method?: 'GET' | 'HEAD';
|
|
26
27
|
headers?: Record<string, string>;
|
|
@@ -57,7 +58,7 @@ type DiscoverExtractFn<TValid> = (input: {
|
|
|
57
58
|
content: string;
|
|
58
59
|
headers?: Headers;
|
|
59
60
|
status?: number;
|
|
60
|
-
}) => Promise<DiscoverResult<TValid
|
|
61
|
+
}) => Promise<DiscoverResult<TValid>> | DiscoverResult<TValid>;
|
|
61
62
|
type DiscoverInputObject = {
|
|
62
63
|
url: string;
|
|
63
64
|
content?: string;
|
|
@@ -75,7 +76,8 @@ type DiscoverOptions<TValid, TMethods extends DiscoverMethod = DiscoverMethod> =
|
|
|
75
76
|
methods?: DiscoverMethodsConfig<TMethods>;
|
|
76
77
|
fetchFn?: DiscoverFetchFn;
|
|
77
78
|
extractFn?: DiscoverExtractFn<TValid>;
|
|
78
|
-
|
|
79
|
+
resolveUrlFn?: DiscoverResolveUrlFn;
|
|
80
|
+
resolveSiteUrlFn?: DiscoverResolveSiteUrlFn;
|
|
79
81
|
stopOnFirstMethod?: boolean;
|
|
80
82
|
stopOnFirstResult?: boolean;
|
|
81
83
|
concurrency?: number;
|
|
@@ -83,4 +85,4 @@ type DiscoverOptions<TValid, TMethods extends DiscoverMethod = DiscoverMethod> =
|
|
|
83
85
|
includeInvalid?: boolean;
|
|
84
86
|
};
|
|
85
87
|
//#endregion
|
|
86
|
-
export { DiscoverExtractFn, DiscoverFetchFn, DiscoverFetchFnOptions, DiscoverFetchFnResponse, DiscoverInput, DiscoverInputObject, DiscoverMethod, DiscoverMethodsConfig,
|
|
88
|
+
export { DiscoverExtractFn, DiscoverFetchFn, DiscoverFetchFnOptions, DiscoverFetchFnResponse, DiscoverInput, DiscoverInputObject, DiscoverMethod, DiscoverMethodsConfig, DiscoverOnProgressFn, DiscoverOptions, DiscoverProgress, DiscoverResolveSiteUrlFn, DiscoverResolveUrlFn, DiscoverResult, DiscoverUriEntry, DiscoverUriHint, LinkSelector, UriEntry };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//#region src/common/uris/guess/utils.ts
|
|
2
|
+
const ipAddressRegex = /^\d+\.\d+\.\d+\.\d+$/;
|
|
2
3
|
const resolveUri = (uri, base, origin, pathname) => {
|
|
3
4
|
if (uri.startsWith("/")) return `${origin}${uri}`;
|
|
4
5
|
if (uri.startsWith("?")) return `${origin}${pathname}${uri}`;
|
|
@@ -24,7 +25,7 @@ const getWwwCounterpart = (baseUrl) => {
|
|
|
24
25
|
const getSubdomainVariants = (baseUrl, prefixes) => {
|
|
25
26
|
const url = new URL(baseUrl);
|
|
26
27
|
const hostname = url.hostname;
|
|
27
|
-
const isIpAddress =
|
|
28
|
+
const isIpAddress = ipAddressRegex.test(hostname);
|
|
28
29
|
if (hostname === "localhost" || isIpAddress) return [];
|
|
29
30
|
const hostnameParts = hostname.split(".");
|
|
30
31
|
if (hostnameParts.length < 2) return [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//#region src/common/uris/guess/utils.ts
|
|
2
|
+
const ipAddressRegex = /^\d+\.\d+\.\d+\.\d+$/;
|
|
2
3
|
const resolveUri = (uri, base, origin, pathname) => {
|
|
3
4
|
if (uri.startsWith("/")) return `${origin}${uri}`;
|
|
4
5
|
if (uri.startsWith("?")) return `${origin}${pathname}${uri}`;
|
|
@@ -24,7 +25,7 @@ const getWwwCounterpart = (baseUrl) => {
|
|
|
24
25
|
const getSubdomainVariants = (baseUrl, prefixes) => {
|
|
25
26
|
const url = new URL(baseUrl);
|
|
26
27
|
const hostname = url.hostname;
|
|
27
|
-
const isIpAddress =
|
|
28
|
+
const isIpAddress = ipAddressRegex.test(hostname);
|
|
28
29
|
if (hostname === "localhost" || isIpAddress) return [];
|
|
29
30
|
const hostnameParts = hostname.split(".");
|
|
30
31
|
if (hostnameParts.length < 2) return [];
|
|
@@ -3,11 +3,12 @@ const require_utils = require("../../utils.cjs");
|
|
|
3
3
|
const urlRegex = /<([^<>]+)>/;
|
|
4
4
|
const relRegex = /rel\s*=\s*["']?([^"';,]+)["']?/i;
|
|
5
5
|
const typeRegex = /type\s*=\s*["']?([^"';,]+)["']?/i;
|
|
6
|
+
const linkSplitRegex = /,(?=\s*<)/;
|
|
6
7
|
const discoverUrisFromHeaders = (headers, options) => {
|
|
7
8
|
const uris = /* @__PURE__ */ new Set();
|
|
8
9
|
const linkHeader = headers.get("link");
|
|
9
10
|
if (!linkHeader) return [];
|
|
10
|
-
const links = linkHeader.split(
|
|
11
|
+
const links = linkHeader.split(linkSplitRegex);
|
|
11
12
|
for (const link of links) {
|
|
12
13
|
const urlMatch = link.match(urlRegex);
|
|
13
14
|
const relMatch = link.match(relRegex);
|
|
@@ -3,11 +3,12 @@ import { matchesAnyOfLinkSelectors } from "../../utils.js";
|
|
|
3
3
|
const urlRegex = /<([^<>]+)>/;
|
|
4
4
|
const relRegex = /rel\s*=\s*["']?([^"';,]+)["']?/i;
|
|
5
5
|
const typeRegex = /type\s*=\s*["']?([^"';,]+)["']?/i;
|
|
6
|
+
const linkSplitRegex = /,(?=\s*<)/;
|
|
6
7
|
const discoverUrisFromHeaders = (headers, options) => {
|
|
7
8
|
const uris = /* @__PURE__ */ new Set();
|
|
8
9
|
const linkHeader = headers.get("link");
|
|
9
10
|
if (!linkHeader) return [];
|
|
10
|
-
const links = linkHeader.split(
|
|
11
|
+
const links = linkHeader.split(linkSplitRegex);
|
|
11
12
|
for (const link of links) {
|
|
12
13
|
const urlMatch = link.match(urlRegex);
|
|
13
14
|
const relMatch = link.match(relRegex);
|