feedscout 2.0.0-beta.3 → 2.0.0-beta.4
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/dist/blogrolls/extractors.js +3 -2
- package/dist/common/discover/index.js +23 -10
- package/dist/common/discover/utils.js +9 -3
- package/dist/common/types.d.ts +7 -0
- package/dist/common/uris/headers/index.js +5 -17
- package/dist/common/utils.js +11 -2
- package/dist/favicons/extractors.js +3 -8
- package/dist/feeds/extractors.js +3 -2
- package/dist/feeds/platform/handlers/artstation.js +2 -2
- package/dist/feeds/platform/handlers/discourse.js +1 -1
- package/dist/feeds/platform/handlers/fireside.js +2 -2
- package/dist/feeds/platform/handlers/lemmy.js +1 -1
- package/dist/feeds/platform/handlers/livejournal.js +1 -1
- package/dist/feeds/platform/handlers/nebula.js +1 -1
- package/dist/feeds/platform/handlers/pinterest.js +1 -1
- package/dist/feeds/platform/handlers/podbean.js +4 -4
- package/dist/feeds/platform/handlers/podigee.js +3 -3
- package/dist/feeds/platform/handlers/reddit.js +2 -2
- package/dist/feeds/platform/handlers/stackExchange.js +1 -1
- package/dist/feeds/platform/handlers/transistor.js +4 -4
- package/package.json +4 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { isSuccessfulStatus } from "../common/utils.js";
|
|
1
2
|
import { parseOpml } from "feedsmith";
|
|
2
3
|
//#region src/blogrolls/extractors.ts
|
|
3
|
-
const defaultExtractFn = ({ content, url }) => {
|
|
4
|
-
if (!content) return {
|
|
4
|
+
const defaultExtractFn = ({ content, url, status }) => {
|
|
5
|
+
if (!content || !isSuccessfulStatus(status)) return {
|
|
5
6
|
url,
|
|
6
7
|
isValid: false
|
|
7
8
|
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { processConcurrently, toPositiveInteger } from "../utils.js";
|
|
1
2
|
import { normalizeInput, normalizeMethodsConfig, normalizeUriEntry } from "./utils.js";
|
|
2
3
|
import { discoverMethodOrder } from "../types.js";
|
|
3
|
-
import { processConcurrently } from "../utils.js";
|
|
4
4
|
import { discoverUris } from "../uris/index.js";
|
|
5
5
|
//#region src/common/discover/index.ts
|
|
6
6
|
const discover = async (input, options, defaults) => {
|
|
7
|
-
const { methods, fetchFn, extractFn, resolveUrlFn, resolveSiteUrlFn, stopOnFirstMethod = false, stopOnFirstResult = false, concurrency = 3, includeInvalid = false, onProgress } = options;
|
|
8
|
-
const
|
|
7
|
+
const { methods, fetchFn, extractFn, resolveUrlFn, resolveSiteUrlFn, stopOnFirstMethod = false, stopOnFirstResult = false, concurrency = 3, maxUris = 50, includeInvalid = false, onProgress, onError } = options;
|
|
8
|
+
const safeConcurrency = toPositiveInteger(concurrency, 3);
|
|
9
|
+
const safeMaxUris = toPositiveInteger(maxUris, 50);
|
|
10
|
+
const sourceInput = await normalizeInput(input, fetchFn, onError);
|
|
9
11
|
if (sourceInput.content) {
|
|
10
12
|
const result = await extractFn({
|
|
11
13
|
url: sourceInput.url,
|
|
@@ -24,26 +26,37 @@ const discover = async (input, options, defaults) => {
|
|
|
24
26
|
content: typeof response.body === "string" ? response.body : "",
|
|
25
27
|
headers: response.headers
|
|
26
28
|
};
|
|
27
|
-
} catch {
|
|
29
|
+
} catch (error) {
|
|
30
|
+
onError?.(error, {
|
|
31
|
+
phase: "resolveSiteUrl",
|
|
32
|
+
url: siteUrl
|
|
33
|
+
});
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
36
|
const urisByMethod = await discoverUris(normalizeMethodsConfig(sourceInput, siteInput, methods, defaults), fetchFn);
|
|
30
37
|
const seen = /* @__PURE__ */ new Set();
|
|
31
38
|
const methodGroups = [];
|
|
39
|
+
let remaining = safeMaxUris;
|
|
32
40
|
for (const method of discoverMethodOrder) {
|
|
41
|
+
if (remaining <= 0) break;
|
|
33
42
|
const rawUris = urisByMethod[method];
|
|
34
43
|
if (!rawUris || rawUris.length === 0) continue;
|
|
35
44
|
const unique = rawUris.map((entry) => {
|
|
36
45
|
return normalizeUriEntry(entry, resolveUrlFn, sourceInput.url);
|
|
37
46
|
}).filter((entry) => {
|
|
38
|
-
const key = typeof entry.uri === "string" ? entry.uri : entry.uri.join("\0");
|
|
47
|
+
const key = typeof entry.uri === "string" ? entry.uri : [...entry.uri].sort().join("\0");
|
|
39
48
|
if (seen.has(key)) return false;
|
|
40
49
|
seen.add(key);
|
|
41
50
|
return true;
|
|
42
51
|
});
|
|
43
|
-
if (unique.length > 0)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
if (unique.length > 0) {
|
|
53
|
+
const capped = unique.slice(0, remaining);
|
|
54
|
+
remaining -= capped.length;
|
|
55
|
+
methodGroups.push({
|
|
56
|
+
method,
|
|
57
|
+
entries: capped
|
|
58
|
+
});
|
|
59
|
+
}
|
|
47
60
|
}
|
|
48
61
|
const total = methodGroups.reduce((sum, group) => sum + group.entries.length, 0);
|
|
49
62
|
const results = [];
|
|
@@ -92,7 +105,7 @@ const discover = async (input, options, defaults) => {
|
|
|
92
105
|
for (const { method, entries } of methodGroups) {
|
|
93
106
|
const foundBefore = found;
|
|
94
107
|
await processConcurrently(entries, (entry) => processUri(entry, method), {
|
|
95
|
-
concurrency,
|
|
108
|
+
concurrency: safeConcurrency,
|
|
96
109
|
shouldStop: () => {
|
|
97
110
|
return stopOnFirstResult && found > 0;
|
|
98
111
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { errors } from "../locales.js";
|
|
2
|
+
import { isObject } from "../utils.js";
|
|
2
3
|
//#region src/common/discover/utils.ts
|
|
3
|
-
const normalizeInput = async (input, fetchFn) => {
|
|
4
|
-
if (
|
|
4
|
+
const normalizeInput = async (input, fetchFn, onError) => {
|
|
5
|
+
if (isObject(input)) return input;
|
|
5
6
|
try {
|
|
6
7
|
const response = await fetchFn(input);
|
|
7
8
|
return {
|
|
@@ -9,7 +10,12 @@ const normalizeInput = async (input, fetchFn) => {
|
|
|
9
10
|
content: typeof response.body === "string" ? response.body : void 0,
|
|
10
11
|
headers: response.headers
|
|
11
12
|
};
|
|
12
|
-
} catch {
|
|
13
|
+
} catch (error) {
|
|
14
|
+
onError?.(error, {
|
|
15
|
+
phase: "fetchInput",
|
|
16
|
+
url: input
|
|
17
|
+
});
|
|
18
|
+
}
|
|
13
19
|
return { url: input };
|
|
14
20
|
};
|
|
15
21
|
const getLinkOfType = (links, rel) => {
|
package/dist/common/types.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ type DiscoverProgress = {
|
|
|
43
43
|
current: string;
|
|
44
44
|
};
|
|
45
45
|
type DiscoverOnProgressFn = (progress: DiscoverProgress) => void;
|
|
46
|
+
type DiscoverErrorContext = {
|
|
47
|
+
phase: 'fetchInput' | 'resolveSiteUrl';
|
|
48
|
+
url?: string;
|
|
49
|
+
};
|
|
50
|
+
type DiscoverOnErrorFn = (error: unknown, context: DiscoverErrorContext) => void;
|
|
46
51
|
type DiscoverResult<TValid = object> = ({
|
|
47
52
|
url: string;
|
|
48
53
|
isValid: true;
|
|
@@ -83,7 +88,9 @@ type DiscoverOptions<TValid, TMethods extends DiscoverMethod = DiscoverMethod> =
|
|
|
83
88
|
stopOnFirstMethod?: boolean;
|
|
84
89
|
stopOnFirstResult?: boolean;
|
|
85
90
|
concurrency?: number;
|
|
91
|
+
maxUris?: number;
|
|
86
92
|
onProgress?: DiscoverOnProgressFn;
|
|
93
|
+
onError?: DiscoverOnErrorFn;
|
|
87
94
|
includeInvalid?: boolean;
|
|
88
95
|
};
|
|
89
96
|
//#endregion
|
|
@@ -1,25 +1,13 @@
|
|
|
1
1
|
import { matchesAnyOfLinkSelectors } from "../../utils.js";
|
|
2
|
+
import LinkHeader from "http-link-header";
|
|
2
3
|
//#region src/common/uris/headers/index.ts
|
|
3
|
-
const linkSplitRegex = /,(?=\s*<)/;
|
|
4
|
-
const urlRegex = /<([^<>]+)>/;
|
|
5
|
-
const relRegex = /rel\s*=\s*["']?([^"';,]+)["']?/i;
|
|
6
|
-
const typeRegex = /type\s*=\s*["']?([^"';,]+)["']?/i;
|
|
7
4
|
const discoverUrisFromHeaders = (headers, options) => {
|
|
8
|
-
const uris = /* @__PURE__ */ new Set();
|
|
9
5
|
const linkHeader = headers.get("link");
|
|
10
6
|
if (!linkHeader) return [];
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const typeMatch = link.match(typeRegex);
|
|
16
|
-
if (!urlMatch) continue;
|
|
17
|
-
const url = urlMatch[1];
|
|
18
|
-
const rel = relMatch?.[1]?.toLowerCase();
|
|
19
|
-
const type = typeMatch?.[1];
|
|
20
|
-
if (!rel) continue;
|
|
21
|
-
if (matchesAnyOfLinkSelectors(rel, type, options.linkSelectors)) uris.add(url);
|
|
22
|
-
}
|
|
7
|
+
const uris = /* @__PURE__ */ new Set();
|
|
8
|
+
try {
|
|
9
|
+
for (const ref of LinkHeader.parse(linkHeader).refs) if (ref.rel && matchesAnyOfLinkSelectors(ref.rel, ref.type, options.linkSelectors)) uris.add(ref.uri);
|
|
10
|
+
} catch {}
|
|
23
11
|
return [...uris];
|
|
24
12
|
};
|
|
25
13
|
//#endregion
|
package/dist/common/utils.js
CHANGED
|
@@ -5,6 +5,15 @@ const composeHint = (key) => ({
|
|
|
5
5
|
key,
|
|
6
6
|
label: hints[key]
|
|
7
7
|
});
|
|
8
|
+
const isSuccessfulStatus = (status) => {
|
|
9
|
+
return status === void 0 || status >= 200 && status < 300;
|
|
10
|
+
};
|
|
11
|
+
const isObject = (value) => {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
};
|
|
14
|
+
const toPositiveInteger = (value, fallback) => {
|
|
15
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : fallback;
|
|
16
|
+
};
|
|
8
17
|
const normalizeMimeType = (type) => {
|
|
9
18
|
return type.split(";")[0].trim().toLowerCase();
|
|
10
19
|
};
|
|
@@ -69,7 +78,7 @@ const matchesAnyOfLinkSelectors = (rel, type, selectors) => {
|
|
|
69
78
|
});
|
|
70
79
|
};
|
|
71
80
|
const processConcurrently = async (items, processFn, options) => {
|
|
72
|
-
if (options.concurrency
|
|
81
|
+
if (!(options.concurrency >= 1)) return;
|
|
73
82
|
const active = /* @__PURE__ */ new Set();
|
|
74
83
|
let index = 0;
|
|
75
84
|
while (index < items.length || active.size > 0) {
|
|
@@ -85,4 +94,4 @@ const processConcurrently = async (items, processFn, options) => {
|
|
|
85
94
|
}
|
|
86
95
|
};
|
|
87
96
|
//#endregion
|
|
88
|
-
export { anyWordMatchesAnyOf, composeHint, endsWithAnyOf, hasMetaContent, includesAnyOf, isAnyOf, isHostOf, isSubdomainOf, matchesAnyOfLinkSelectors, omitEmpty, processConcurrently };
|
|
97
|
+
export { anyWordMatchesAnyOf, composeHint, endsWithAnyOf, hasMetaContent, includesAnyOf, isAnyOf, isHostOf, isObject, isSubdomainOf, isSuccessfulStatus, matchesAnyOfLinkSelectors, omitEmpty, processConcurrently, toPositiveInteger };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSuccessfulStatus } from "../common/utils.js";
|
|
1
2
|
//#region src/favicons/extractors.ts
|
|
2
3
|
const isImageContentType = (headers) => {
|
|
3
4
|
return headers?.get("content-type")?.startsWith("image/") ?? false;
|
|
@@ -8,17 +9,11 @@ const isImageContent = (content) => {
|
|
|
8
9
|
const head = trimmed.slice(0, 200);
|
|
9
10
|
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
|
};
|
|
11
|
-
const isSuccessStatus = (status) => {
|
|
12
|
-
return status !== void 0 && status >= 200 && status < 400;
|
|
13
|
-
};
|
|
14
12
|
const defaultExtractFn = (input) => {
|
|
15
|
-
|
|
16
|
-
url: input.url,
|
|
17
|
-
isValid: true
|
|
18
|
-
};
|
|
13
|
+
const isImage = isImageContentType(input.headers) || isImageContent(input.content);
|
|
19
14
|
return {
|
|
20
15
|
url: input.url,
|
|
21
|
-
isValid:
|
|
16
|
+
isValid: isImage && isSuccessfulStatus(input.status)
|
|
22
17
|
};
|
|
23
18
|
};
|
|
24
19
|
//#endregion
|
package/dist/feeds/extractors.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isSuccessfulStatus } from "../common/utils.js";
|
|
1
2
|
import { getFeedSiteUrl } from "../common/discover/utils.js";
|
|
2
3
|
import { defaultResolveUrlFn } from "../common/discover/defaults.js";
|
|
3
4
|
import { parseFeed } from "feedsmith";
|
|
4
5
|
//#region src/feeds/extractors.ts
|
|
5
|
-
const defaultExtractFn = ({ content, url }) => {
|
|
6
|
-
if (!content) return {
|
|
6
|
+
const defaultExtractFn = ({ content, url, status }) => {
|
|
7
|
+
if (!content || !isSuccessfulStatus(status)) return {
|
|
7
8
|
url,
|
|
8
9
|
isValid: false
|
|
9
10
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { composeHint, isAnyOf, isHostOf, isSubdomainOf } from "../../../common/utils.js";
|
|
2
2
|
//#region src/feeds/platform/handlers/artstation.ts
|
|
3
3
|
const hosts = ["artstation.com", "www.artstation.com"];
|
|
4
|
-
const
|
|
4
|
+
const domainSuffixRegex = /\.artstation\.com$/i;
|
|
5
5
|
const excludedPaths = [
|
|
6
6
|
"blogs",
|
|
7
7
|
"channels",
|
|
@@ -24,7 +24,7 @@ const artstationHandler = {
|
|
|
24
24
|
resolve: (url) => {
|
|
25
25
|
const parsed = new URL(url);
|
|
26
26
|
if (!isHostOf(url, hosts) && isSubdomainOf(url, "artstation.com")) return [{
|
|
27
|
-
uri: `https://www.artstation.com/${parsed.hostname.replace(
|
|
27
|
+
uri: `https://www.artstation.com/${parsed.hostname.replace(domainSuffixRegex, "")}.rss`,
|
|
28
28
|
hint: composeHint("artstation:portfolio")
|
|
29
29
|
}];
|
|
30
30
|
const pathSegments = parsed.pathname.split("/").filter(Boolean);
|
|
@@ -4,7 +4,7 @@ const userRegex = /^\/u\/([^/]+)/;
|
|
|
4
4
|
const categoryRegex = /^\/c\/(.+?)\/?$/;
|
|
5
5
|
const topicRegex = /^\/t\/([^/]+)\/(\d+)/;
|
|
6
6
|
const topRegex = /^\/top(?:\/([^/]+))?\/?$/;
|
|
7
|
-
const validTopPeriods = new Set([
|
|
7
|
+
const validTopPeriods = /* @__PURE__ */ new Set([
|
|
8
8
|
"daily",
|
|
9
9
|
"weekly",
|
|
10
10
|
"monthly",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { composeHint, isSubdomainOf } from "../../../common/utils.js";
|
|
2
2
|
//#region src/feeds/platform/handlers/fireside.ts
|
|
3
|
-
const
|
|
3
|
+
const domainSuffixRegex = /\.fireside\.fm$/i;
|
|
4
4
|
const firesideHandler = {
|
|
5
5
|
match: (url) => {
|
|
6
6
|
return isSubdomainOf(url, "fireside.fm");
|
|
7
7
|
},
|
|
8
8
|
resolve: (url) => {
|
|
9
9
|
const { hostname } = new URL(url);
|
|
10
|
-
const slug = hostname.replace(
|
|
10
|
+
const slug = hostname.replace(domainSuffixRegex, "");
|
|
11
11
|
const uris = [];
|
|
12
12
|
uris.push({
|
|
13
13
|
uri: `https://feeds.fireside.fm/${slug}/rss`,
|
|
@@ -1,7 +1,7 @@
|
|
|
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([
|
|
4
|
+
const validSorts = /* @__PURE__ */ new Set([
|
|
5
5
|
"Active",
|
|
6
6
|
"Hot",
|
|
7
7
|
"New",
|
|
@@ -3,7 +3,7 @@ import { composeHint, isSubdomainOf } from "../../../common/utils.js";
|
|
|
3
3
|
const wwwUsersPathRegex = /^\/(?:users\/|~)([^/]+)/;
|
|
4
4
|
const legacyUserPathRegex = /^\/([^/]+)/;
|
|
5
5
|
const tagRegex = /^\/tag\/([^/]+)/;
|
|
6
|
-
const reservedHosts = new Set([
|
|
6
|
+
const reservedHosts = /* @__PURE__ */ new Set([
|
|
7
7
|
"livejournal.com",
|
|
8
8
|
"www.livejournal.com",
|
|
9
9
|
"users.livejournal.com",
|
|
@@ -34,7 +34,7 @@ const pinterestHandler = {
|
|
|
34
34
|
if (pathSegments.length === 0) return [];
|
|
35
35
|
const username = pathSegments[0];
|
|
36
36
|
if (isAnyOf(username, excludedPaths)) return [];
|
|
37
|
-
const reservedBoardSlugs = new Set([
|
|
37
|
+
const reservedBoardSlugs = /* @__PURE__ */ new Set([
|
|
38
38
|
"pins",
|
|
39
39
|
"boards",
|
|
40
40
|
"_saved",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { composeHint, isSubdomainOf } from "../../../common/utils.js";
|
|
2
2
|
//#region src/feeds/platform/handlers/podbean.ts
|
|
3
|
-
const
|
|
4
|
-
const reservedSlugs = new Set([
|
|
3
|
+
const domainSuffixRegex = /\.podbean\.com$/i;
|
|
4
|
+
const reservedSlugs = /* @__PURE__ */ new Set([
|
|
5
5
|
"www",
|
|
6
6
|
"feed",
|
|
7
7
|
"pbcdn1",
|
|
@@ -14,13 +14,13 @@ const reservedSlugs = new Set([
|
|
|
14
14
|
const podbeanHandler = {
|
|
15
15
|
match: (url) => {
|
|
16
16
|
if (!isSubdomainOf(url, "podbean.com")) return false;
|
|
17
|
-
const slug = new URL(url).hostname.toLowerCase().replace(
|
|
17
|
+
const slug = new URL(url).hostname.toLowerCase().replace(domainSuffixRegex, "");
|
|
18
18
|
return !reservedSlugs.has(slug);
|
|
19
19
|
},
|
|
20
20
|
resolve: (url) => {
|
|
21
21
|
const { hostname } = new URL(url);
|
|
22
22
|
return [{
|
|
23
|
-
uri: `https://feed.podbean.com/${hostname.replace(
|
|
23
|
+
uri: `https://feed.podbean.com/${hostname.replace(domainSuffixRegex, "")}/feed.xml`,
|
|
24
24
|
hint: composeHint("podbean:podcast")
|
|
25
25
|
}];
|
|
26
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { composeHint, isSubdomainOf } from "../../../common/utils.js";
|
|
2
2
|
//#region src/feeds/platform/handlers/podigee.ts
|
|
3
|
-
const
|
|
4
|
-
const reservedSlugs = new Set([
|
|
3
|
+
const domainSuffixRegex = /\.podigee\.io$/i;
|
|
4
|
+
const reservedSlugs = /* @__PURE__ */ new Set([
|
|
5
5
|
"www",
|
|
6
6
|
"app",
|
|
7
7
|
"help",
|
|
@@ -14,7 +14,7 @@ const reservedSlugs = new Set([
|
|
|
14
14
|
const podigeeHandler = {
|
|
15
15
|
match: (url) => {
|
|
16
16
|
if (!isSubdomainOf(url, "podigee.io")) return false;
|
|
17
|
-
const slug = new URL(url).hostname.toLowerCase().replace(
|
|
17
|
+
const slug = new URL(url).hostname.toLowerCase().replace(domainSuffixRegex, "");
|
|
18
18
|
return !reservedSlugs.has(slug);
|
|
19
19
|
},
|
|
20
20
|
resolve: (url) => {
|
|
@@ -22,7 +22,7 @@ const sortOptions = [
|
|
|
22
22
|
"top",
|
|
23
23
|
"best"
|
|
24
24
|
];
|
|
25
|
-
const timeOptions = new Set([
|
|
25
|
+
const timeOptions = /* @__PURE__ */ new Set([
|
|
26
26
|
"hour",
|
|
27
27
|
"day",
|
|
28
28
|
"week",
|
|
@@ -30,7 +30,7 @@ const timeOptions = new Set([
|
|
|
30
30
|
"year",
|
|
31
31
|
"all"
|
|
32
32
|
]);
|
|
33
|
-
const timeFilteredSorts = new Set(["top", "controversial"]);
|
|
33
|
+
const timeFilteredSorts = /* @__PURE__ */ new Set(["top", "controversial"]);
|
|
34
34
|
const getTimeframeSuffix = (sort, searchParams) => {
|
|
35
35
|
if (!timeFilteredSorts.has(sort)) return "";
|
|
36
36
|
const timeframe = searchParams.get("t");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { composeHint, isSubdomainOf } from "../../../common/utils.js";
|
|
2
2
|
//#region src/feeds/platform/handlers/transistor.ts
|
|
3
|
-
const
|
|
4
|
-
const reservedSlugs = new Set([
|
|
3
|
+
const domainSuffixRegex = /\.transistor\.fm$/i;
|
|
4
|
+
const reservedSlugs = /* @__PURE__ */ new Set([
|
|
5
5
|
"www",
|
|
6
6
|
"feeds",
|
|
7
7
|
"share",
|
|
@@ -14,13 +14,13 @@ const reservedSlugs = new Set([
|
|
|
14
14
|
const transistorHandler = {
|
|
15
15
|
match: (url) => {
|
|
16
16
|
if (!isSubdomainOf(url, "transistor.fm")) return false;
|
|
17
|
-
const slug = new URL(url).hostname.toLowerCase().replace(
|
|
17
|
+
const slug = new URL(url).hostname.toLowerCase().replace(domainSuffixRegex, "");
|
|
18
18
|
return !reservedSlugs.has(slug);
|
|
19
19
|
},
|
|
20
20
|
resolve: (url) => {
|
|
21
21
|
const { hostname } = new URL(url);
|
|
22
22
|
return [{
|
|
23
|
-
uri: `https://feeds.transistor.fm/${hostname.replace(
|
|
23
|
+
uri: `https://feeds.transistor.fm/${hostname.replace(domainSuffixRegex, "")}`,
|
|
24
24
|
hint: composeHint("transistor:podcast")
|
|
25
25
|
}];
|
|
26
26
|
}
|
package/package.json
CHANGED
|
@@ -65,17 +65,19 @@
|
|
|
65
65
|
"docs:build": "vitepress build docs"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"htmlparser2": "^12.0.0"
|
|
68
|
+
"htmlparser2": "^12.0.0",
|
|
69
|
+
"http-link-header": "^1.1.3"
|
|
69
70
|
},
|
|
70
71
|
"peerDependencies": {
|
|
71
72
|
"feedsmith": "3.0.0-beta.5"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
|
74
75
|
"@types/bun": "^1.3.13",
|
|
76
|
+
"@types/http-link-header": "^1.0.7",
|
|
75
77
|
"feedsmith": "3.0.0-beta.5",
|
|
76
78
|
"kvalita": "^1.15.1",
|
|
77
79
|
"tsdown": "^0.22.2",
|
|
78
80
|
"vitepress": "^2.0.0-alpha.17"
|
|
79
81
|
},
|
|
80
|
-
"version": "2.0.0-beta.
|
|
82
|
+
"version": "2.0.0-beta.4"
|
|
81
83
|
}
|