feedcanon 1.3.0 → 1.4.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 CHANGED
@@ -54,7 +54,7 @@ Feedcanon is designed to be flexible. Every major component can be replaced or e
54
54
  - **Custom fetch** — use your own HTTP client (Axios, Got, Ky, etc.)
55
55
  - **Custom parser** — bring your own parser (Feedsmith by default).
56
56
  - **Custom tiers** — define your own URL normalization tiers.
57
- - **Custom platforms** — add handlers to normalize domain aliases (like FeedBurner).
57
+ - **Custom rewrites** — add rewrites to normalize domain aliases (like FeedBurner).
58
58
 
59
59
  ## Quick Start
60
60
 
package/dist/defaults.cjs CHANGED
@@ -1,6 +1,6 @@
1
- const require_utils = require('./utils.cjs');
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_utils = require("./utils.cjs");
2
3
  let feedsmith = require("feedsmith");
3
-
4
4
  //#region src/defaults.ts
5
5
  const defaultStrippedParams = [
6
6
  "utm_source",
@@ -168,6 +168,7 @@ const defaultNormalizeOptions = {
168
168
  stripQueryParams: defaultStrippedParams,
169
169
  stripQuery: false,
170
170
  stripEmptyQuery: true,
171
+ lowercaseQuery: false,
171
172
  normalizeEncoding: true,
172
173
  normalizeUnicode: true,
173
174
  convertToPunycode: true
@@ -289,10 +290,9 @@ const defaultTiers = [
289
290
  convertToPunycode: true
290
291
  }
291
292
  ];
292
-
293
293
  //#endregion
294
294
  exports.defaultFetch = defaultFetch;
295
295
  exports.defaultNormalizeOptions = defaultNormalizeOptions;
296
296
  exports.defaultParser = defaultParser;
297
297
  exports.defaultStrippedParams = defaultStrippedParams;
298
- exports.defaultTiers = defaultTiers;
298
+ exports.defaultTiers = defaultTiers;
package/dist/defaults.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { createSignature, neutralizeUrls } from "./utils.js";
2
2
  import { parseFeed } from "feedsmith";
3
-
4
3
  //#region src/defaults.ts
5
4
  const defaultStrippedParams = [
6
5
  "utm_source",
@@ -168,6 +167,7 @@ const defaultNormalizeOptions = {
168
167
  stripQueryParams: defaultStrippedParams,
169
168
  stripQuery: false,
170
169
  stripEmptyQuery: true,
170
+ lowercaseQuery: false,
171
171
  normalizeEncoding: true,
172
172
  normalizeUnicode: true,
173
173
  convertToPunycode: true
@@ -289,6 +289,5 @@ const defaultTiers = [
289
289
  convertToPunycode: true
290
290
  }
291
291
  ];
292
-
293
292
  //#endregion
294
- export { defaultFetch, defaultNormalizeOptions, defaultParser, defaultStrippedParams, defaultTiers };
293
+ export { defaultFetch, defaultNormalizeOptions, defaultParser, defaultStrippedParams, defaultTiers };
package/dist/exports.cjs CHANGED
@@ -1,10 +1,10 @@
1
- const require_utils = require('./utils.cjs');
2
- const require_defaults = require('./defaults.cjs');
3
- const require_index = require('./index.cjs');
4
- const require_wordpress = require('./probes/wordpress.cjs');
5
- const require_blogger = require('./rewrites/blogger.cjs');
6
- const require_feedburner = require('./rewrites/feedburner.cjs');
7
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_utils = require("./utils.cjs");
3
+ const require_defaults = require("./defaults.cjs");
4
+ const require_index = require("./index.cjs");
5
+ const require_wordpress = require("./probes/wordpress.cjs");
6
+ const require_blogger = require("./rewrites/blogger.cjs");
7
+ const require_feedburner = require("./rewrites/feedburner.cjs");
8
8
  exports.addMissingProtocol = require_utils.addMissingProtocol;
9
9
  exports.bloggerRewrite = require_blogger.bloggerRewrite;
10
10
  exports.defaultFetch = require_defaults.defaultFetch;
@@ -17,4 +17,4 @@ exports.fixMalformedProtocol = require_utils.fixMalformedProtocol;
17
17
  exports.normalizeUrl = require_utils.normalizeUrl;
18
18
  exports.resolveFeedProtocol = require_utils.resolveFeedProtocol;
19
19
  exports.resolveUrl = require_utils.resolveUrl;
20
- exports.wordpressProbe = require_wordpress.wordpressProbe;
20
+ exports.wordpressProbe = require_wordpress.wordpressProbe;
package/dist/exports.js CHANGED
@@ -4,5 +4,4 @@ import { findCanonical } from "./index.js";
4
4
  import { wordpressProbe } from "./probes/wordpress.js";
5
5
  import { bloggerRewrite } from "./rewrites/blogger.js";
6
6
  import { feedburnerRewrite } from "./rewrites/feedburner.js";
7
-
8
- export { addMissingProtocol, bloggerRewrite, defaultFetch, defaultParser, defaultStrippedParams, defaultTiers, feedburnerRewrite, findCanonical, fixMalformedProtocol, normalizeUrl, resolveFeedProtocol, resolveUrl, wordpressProbe };
7
+ export { addMissingProtocol, bloggerRewrite, defaultFetch, defaultParser, defaultStrippedParams, defaultTiers, feedburnerRewrite, findCanonical, fixMalformedProtocol, normalizeUrl, resolveFeedProtocol, resolveUrl, wordpressProbe };
package/dist/index.cjs CHANGED
@@ -1,6 +1,5 @@
1
- const require_utils = require('./utils.cjs');
2
- const require_defaults = require('./defaults.cjs');
3
-
1
+ const require_utils = require("./utils.cjs");
2
+ const require_defaults = require("./defaults.cjs");
4
3
  //#region src/index.ts
5
4
  async function findCanonical(inputUrl, options) {
6
5
  const { parser = require_defaults.defaultParser, fetchFn = require_defaults.defaultFetch, existsFn, tiers = require_defaults.defaultTiers, rewrites, probes, stripQueryParams = require_defaults.defaultStrippedParams, onFetch, onMatch, onExists } = options ?? {};
@@ -150,6 +149,5 @@ async function findCanonical(inputUrl, options) {
150
149
  }
151
150
  return winningUrl;
152
151
  }
153
-
154
152
  //#endregion
155
- exports.findCanonical = findCanonical;
153
+ exports.findCanonical = findCanonical;
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { applyProbes, applyRewrites, normalizeUrl, resolveUrl } from "./utils.js";
2
2
  import { defaultFetch, defaultParser, defaultStrippedParams, defaultTiers } from "./defaults.js";
3
-
4
3
  //#region src/index.ts
5
4
  async function findCanonical(inputUrl, options) {
6
5
  const { parser = defaultParser, fetchFn = defaultFetch, existsFn, tiers = defaultTiers, rewrites, probes, stripQueryParams = defaultStrippedParams, onFetch, onMatch, onExists } = options ?? {};
@@ -150,6 +149,5 @@ async function findCanonical(inputUrl, options) {
150
149
  }
151
150
  return winningUrl;
152
151
  }
153
-
154
152
  //#endregion
155
- export { findCanonical };
153
+ export { findCanonical };
@@ -1,4 +1,3 @@
1
-
2
1
  //#region src/probes/wordpress.ts
3
2
  const feedTypes = [
4
3
  "atom",
@@ -24,10 +23,10 @@ const wordpressProbe = {
24
23
  withoutSlash.pathname = url.pathname.replace(/\/$/, "");
25
24
  withoutSlash.searchParams.delete("feed");
26
25
  candidates.push(withoutSlash.href);
27
- const withSlash$1 = new URL(url);
28
- withSlash$1.pathname = url.pathname.replace(/\/?$/, "/");
29
- withSlash$1.searchParams.delete("feed");
30
- candidates.push(withSlash$1.href);
26
+ const withSlash = new URL(url);
27
+ withSlash.pathname = url.pathname.replace(/\/?$/, "/");
28
+ withSlash.searchParams.delete("feed");
29
+ candidates.push(withSlash.href);
31
30
  return candidates;
32
31
  }
33
32
  const basePath = url.pathname.replace(/\/$/, "");
@@ -44,6 +43,5 @@ const wordpressProbe = {
44
43
  return candidates;
45
44
  }
46
45
  };
47
-
48
46
  //#endregion
49
- exports.wordpressProbe = wordpressProbe;
47
+ exports.wordpressProbe = wordpressProbe;
@@ -23,10 +23,10 @@ const wordpressProbe = {
23
23
  withoutSlash.pathname = url.pathname.replace(/\/$/, "");
24
24
  withoutSlash.searchParams.delete("feed");
25
25
  candidates.push(withoutSlash.href);
26
- const withSlash$1 = new URL(url);
27
- withSlash$1.pathname = url.pathname.replace(/\/?$/, "/");
28
- withSlash$1.searchParams.delete("feed");
29
- candidates.push(withSlash$1.href);
26
+ const withSlash = new URL(url);
27
+ withSlash.pathname = url.pathname.replace(/\/?$/, "/");
28
+ withSlash.searchParams.delete("feed");
29
+ candidates.push(withSlash.href);
30
30
  return candidates;
31
31
  }
32
32
  const basePath = url.pathname.replace(/\/$/, "");
@@ -43,6 +43,5 @@ const wordpressProbe = {
43
43
  return candidates;
44
44
  }
45
45
  };
46
-
47
46
  //#endregion
48
- export { wordpressProbe };
47
+ export { wordpressProbe };
@@ -1,5 +1,4 @@
1
- const require_utils = require('../utils.cjs');
2
-
1
+ const require_utils = require("../utils.cjs");
3
2
  //#region src/rewrites/blogger.ts
4
3
  const bloggerPattern = /^(www\.|beta\.)?blogger\.com$/;
5
4
  const blogspotPattern = /\.blogspot\.[a-z]{2,3}(\.[a-z]{2})?$/i;
@@ -44,6 +43,5 @@ const bloggerRewrite = {
44
43
  return new URL(normalized);
45
44
  }
46
45
  };
47
-
48
46
  //#endregion
49
- exports.bloggerRewrite = bloggerRewrite;
47
+ exports.bloggerRewrite = bloggerRewrite;
@@ -1,5 +1,4 @@
1
1
  import { normalizeUrl } from "../utils.js";
2
-
3
2
  //#region src/rewrites/blogger.ts
4
3
  const bloggerPattern = /^(www\.|beta\.)?blogger\.com$/;
5
4
  const blogspotPattern = /\.blogspot\.[a-z]{2,3}(\.[a-z]{2})?$/i;
@@ -44,6 +43,5 @@ const bloggerRewrite = {
44
43
  return new URL(normalized);
45
44
  }
46
45
  };
47
-
48
46
  //#endregion
49
- export { bloggerRewrite };
47
+ export { bloggerRewrite };
@@ -1,5 +1,4 @@
1
- const require_utils = require('../utils.cjs');
2
-
1
+ const require_utils = require("../utils.cjs");
3
2
  //#region src/rewrites/feedburner.ts
4
3
  const hosts = [
5
4
  "feeds.feedburner.com",
@@ -24,6 +23,5 @@ const feedburnerRewrite = {
24
23
  return new URL(normalized);
25
24
  }
26
25
  };
27
-
28
26
  //#endregion
29
- exports.feedburnerRewrite = feedburnerRewrite;
27
+ exports.feedburnerRewrite = feedburnerRewrite;
@@ -1,5 +1,4 @@
1
1
  import { normalizeUrl } from "../utils.js";
2
-
3
2
  //#region src/rewrites/feedburner.ts
4
3
  const hosts = [
5
4
  "feeds.feedburner.com",
@@ -24,6 +23,5 @@ const feedburnerRewrite = {
24
23
  return new URL(normalized);
25
24
  }
26
25
  };
27
-
28
26
  //#endregion
29
- export { feedburnerRewrite };
27
+ export { feedburnerRewrite };
package/dist/types.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import * as feedsmith0 from "feedsmith";
1
+ import * as feedsmith from "feedsmith";
2
2
 
3
3
  //#region src/types.d.ts
4
- type DefaultParserResult = ReturnType<typeof feedsmith0.parseFeed>;
4
+ type DefaultParserResult = ReturnType<typeof feedsmith.parseFeed>;
5
5
  type ParserAdapter<T> = {
6
6
  parse: (body: string) => Promise<T | undefined> | T | undefined;
7
7
  getSelfUrl: (parsed: T) => string | undefined;
@@ -27,6 +27,7 @@ type NormalizeOptions = {
27
27
  stripQueryParams?: Array<string>;
28
28
  stripQuery?: boolean;
29
29
  stripEmptyQuery?: boolean;
30
+ lowercaseQuery?: boolean;
30
31
  normalizeEncoding?: boolean;
31
32
  normalizeUnicode?: boolean;
32
33
  convertToPunycode?: boolean;
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import * as feedsmith0 from "feedsmith";
1
+ import * as feedsmith from "feedsmith";
2
2
 
3
3
  //#region src/types.d.ts
4
- type DefaultParserResult = ReturnType<typeof feedsmith0.parseFeed>;
4
+ type DefaultParserResult = ReturnType<typeof feedsmith.parseFeed>;
5
5
  type ParserAdapter<T> = {
6
6
  parse: (body: string) => Promise<T | undefined> | T | undefined;
7
7
  getSelfUrl: (parsed: T) => string | undefined;
@@ -27,6 +27,7 @@ type NormalizeOptions = {
27
27
  stripQueryParams?: Array<string>;
28
28
  stripQuery?: boolean;
29
29
  stripEmptyQuery?: boolean;
30
+ lowercaseQuery?: boolean;
30
31
  normalizeEncoding?: boolean;
31
32
  normalizeUnicode?: boolean;
32
33
  convertToPunycode?: boolean;
package/dist/utils.cjs CHANGED
@@ -1,7 +1,6 @@
1
- const require_defaults = require('./defaults.cjs');
1
+ const require_defaults = require("./defaults.cjs");
2
2
  let node_url = require("node:url");
3
3
  let entities = require("entities");
4
-
5
4
  //#region src/utils.ts
6
5
  const strippedParamsCache = /* @__PURE__ */ new WeakMap();
7
6
  const getStrippedParamsSet = (params) => {
@@ -76,6 +75,7 @@ const addMissingProtocol = (url, protocol = "https") => {
76
75
  return `${protocol}://${url}`;
77
76
  };
78
77
  const resolveUrl = (url, base) => {
78
+ if (url.startsWith("#")) return;
79
79
  let resolvedUrl;
80
80
  resolvedUrl = url.includes("&") ? (0, entities.decodeHTML)(url) : url;
81
81
  resolvedUrl = resolveFeedProtocol(resolvedUrl);
@@ -133,6 +133,11 @@ const normalizeUrl = (url, options = require_defaults.defaultNormalizeOptions) =
133
133
  for (const [key] of parsed.searchParams) if (strippedSet.has(key.toLowerCase())) paramsToDelete.push(key);
134
134
  for (const param of paramsToDelete) parsed.searchParams.delete(param);
135
135
  }
136
+ if (options.lowercaseQuery && parsed.search) {
137
+ const entries = [...parsed.searchParams.entries()];
138
+ parsed.search = "";
139
+ for (const [key, value] of entries) parsed.searchParams.append(key.toLowerCase(), value.toLowerCase());
140
+ }
136
141
  if (options.sortQueryParams) parsed.searchParams.sort();
137
142
  if (options.stripEmptyQuery && parsed.href.endsWith("?")) parsed.search = "";
138
143
  let result = parsed.href;
@@ -190,9 +195,8 @@ const neutralizeUrls = (text, urls) => {
190
195
  const hosts = urls.map(escapeHost).filter(Boolean);
191
196
  if (hosts.length === 0) return text;
192
197
  const hostPattern = hosts.length === 1 ? hosts[0] : `(?:${hosts.join("|")})`;
193
- return text.replace(new RegExp(`https?://(?:www\\.)?${hostPattern}(?=[/"])(/)?`, "g"), "/").replace(trailingSlashPattern, "$1$2");
198
+ return text.replace(new RegExp(`https?://(?:www\\.)?${hostPattern}(?=[/"]|\\\\")(/)?`, "g"), "/").replace(trailingSlashPattern, "$1$2");
194
199
  };
195
-
196
200
  //#endregion
197
201
  exports.addMissingProtocol = addMissingProtocol;
198
202
  exports.applyProbes = applyProbes;
@@ -202,4 +206,4 @@ exports.fixMalformedProtocol = fixMalformedProtocol;
202
206
  exports.neutralizeUrls = neutralizeUrls;
203
207
  exports.normalizeUrl = normalizeUrl;
204
208
  exports.resolveFeedProtocol = resolveFeedProtocol;
205
- exports.resolveUrl = resolveUrl;
209
+ exports.resolveUrl = resolveUrl;
package/dist/utils.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { defaultNormalizeOptions } from "./defaults.js";
2
2
  import { domainToASCII } from "node:url";
3
3
  import { decodeHTML } from "entities";
4
-
5
4
  //#region src/utils.ts
6
5
  const strippedParamsCache = /* @__PURE__ */ new WeakMap();
7
6
  const getStrippedParamsSet = (params) => {
@@ -76,6 +75,7 @@ const addMissingProtocol = (url, protocol = "https") => {
76
75
  return `${protocol}://${url}`;
77
76
  };
78
77
  const resolveUrl = (url, base) => {
78
+ if (url.startsWith("#")) return;
79
79
  let resolvedUrl;
80
80
  resolvedUrl = url.includes("&") ? decodeHTML(url) : url;
81
81
  resolvedUrl = resolveFeedProtocol(resolvedUrl);
@@ -133,6 +133,11 @@ const normalizeUrl = (url, options = defaultNormalizeOptions) => {
133
133
  for (const [key] of parsed.searchParams) if (strippedSet.has(key.toLowerCase())) paramsToDelete.push(key);
134
134
  for (const param of paramsToDelete) parsed.searchParams.delete(param);
135
135
  }
136
+ if (options.lowercaseQuery && parsed.search) {
137
+ const entries = [...parsed.searchParams.entries()];
138
+ parsed.search = "";
139
+ for (const [key, value] of entries) parsed.searchParams.append(key.toLowerCase(), value.toLowerCase());
140
+ }
136
141
  if (options.sortQueryParams) parsed.searchParams.sort();
137
142
  if (options.stripEmptyQuery && parsed.href.endsWith("?")) parsed.search = "";
138
143
  let result = parsed.href;
@@ -190,8 +195,7 @@ const neutralizeUrls = (text, urls) => {
190
195
  const hosts = urls.map(escapeHost).filter(Boolean);
191
196
  if (hosts.length === 0) return text;
192
197
  const hostPattern = hosts.length === 1 ? hosts[0] : `(?:${hosts.join("|")})`;
193
- return text.replace(new RegExp(`https?://(?:www\\.)?${hostPattern}(?=[/"])(/)?`, "g"), "/").replace(trailingSlashPattern, "$1$2");
198
+ return text.replace(new RegExp(`https?://(?:www\\.)?${hostPattern}(?=[/"]|\\\\")(/)?`, "g"), "/").replace(trailingSlashPattern, "$1$2");
194
199
  };
195
-
196
200
  //#endregion
197
- export { addMissingProtocol, applyProbes, applyRewrites, createSignature, fixMalformedProtocol, neutralizeUrls, normalizeUrl, resolveFeedProtocol, resolveUrl };
201
+ export { addMissingProtocol, applyProbes, applyRewrites, createSignature, fixMalformedProtocol, neutralizeUrls, normalizeUrl, resolveFeedProtocol, resolveUrl };
package/package.json CHANGED
@@ -54,14 +54,14 @@
54
54
  "docs:build": "vitepress build docs"
55
55
  },
56
56
  "dependencies": {
57
- "entities": "^7.0.0",
58
- "feedsmith": "^2.8.0"
57
+ "entities": "^7.0.1",
58
+ "feedsmith": "^2.9.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@types/bun": "^1.3.5",
62
- "kvalita": "1.9.0",
63
- "tsdown": "^0.18.4",
61
+ "@types/bun": "^1.3.10",
62
+ "kvalita": "1.10.0",
63
+ "tsdown": "^0.21.0",
64
64
  "vitepress": "^1.6.4"
65
65
  },
66
- "version": "1.3.0"
66
+ "version": "1.4.0"
67
67
  }