@adriangalilea/utils 2.10.0 → 2.12.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 +45 -15
- package/dist/universal/url/index.d.ts +46 -3
- package/dist/universal/url/index.d.ts.map +1 -1
- package/dist/universal/url/index.js +110 -24
- package/dist/universal/url/index.js.map +1 -1
- package/dist/universal/url/sites.d.ts +42 -0
- package/dist/universal/url/sites.d.ts.map +1 -0
- package/dist/universal/url/sites.js +130 -0
- package/dist/universal/url/sites.js.map +1 -0
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -101,28 +101,58 @@ money(100, 'EUR') // "€100.00"
|
|
|
101
101
|
|
|
102
102
|
### URL (`url`)
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
URLs, from user-typed text to cache identity. The whole funnel — *is there a
|
|
105
|
+
URL? which resource? whose content? what identity?* — answered once, on one
|
|
106
|
+
object. Each concern rides its state of the art instead of hand-rolls:
|
|
107
|
+
detection is [linkifyjs](https://linkify.js.org) (scanner-based, TLD-aware);
|
|
108
|
+
parsing is the WHATWG URL API, never regexes; the knowledge of WHICH params
|
|
109
|
+
are tracking is vendored from
|
|
106
110
|
[@protontech/tidy-url](https://www.npmjs.com/package/@protontech/tidy-url)
|
|
107
111
|
(Proton's maintained fork of DrKain/tidy-url, MIT; refresh with
|
|
108
|
-
`pnpm update-url-rules`) plus a small tested overlay
|
|
109
|
-
|
|
112
|
+
`pnpm update-url-rules`) plus a small tested overlay; per-site content
|
|
113
|
+
identity is an adapter registry (YouTube built in). Global rules only for
|
|
114
|
+
unambiguous trackers (utm_*, fbclid, gclid, …); ambiguous names stay per-host —
|
|
110
115
|
`si` is junk on YouTube/Spotify but `ref` on GitHub names a branch and
|
|
111
|
-
survives.
|
|
116
|
+
survives.
|
|
112
117
|
|
|
113
118
|
```typescript
|
|
114
|
-
import { cleanUrl, urlKey
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
+
import { urlsIn, urlOf, cleanUrl, urlKey, hostOf, hostMatches,
|
|
120
|
+
youtubeVideoId, youtubeUrl, youtubeThumbnailUrl } from '@adriangalilea/utils/url'
|
|
121
|
+
|
|
122
|
+
// Every http(s) URL in free text, in order, fully resolved:
|
|
123
|
+
urlsIn('watch youtu.be/dQw4w9WgXcQ?si=junk and https://a.com/x?utm_source=t')
|
|
124
|
+
// [{ raw: 'youtu.be/dQw4w9WgXcQ?si=junk', start: 6, end: 34, hadScheme: false,
|
|
125
|
+
// href: 'https://youtu.be/dQw4w9WgXcQ', // cleaned — fetch/share this
|
|
126
|
+
// host: 'youtu.be', site: 'youtube', id: 'dQw4w9WgXcQ',
|
|
127
|
+
// key: 'youtube:dQw4w9WgXcQ' }, // THE cache/dedupe identity
|
|
128
|
+
// { …, href: 'https://a.com/x', site: null, id: null, key: 'a.com/x' }]
|
|
129
|
+
|
|
130
|
+
// One pasted token → one resolved Url (or null). Punctuation, brackets,
|
|
131
|
+
// emails-are-not-URLs: the scanner's problem, not yours.
|
|
132
|
+
urlOf('<https://example.com/a?utm_source=x>,')?.href // 'https://example.com/a'
|
|
133
|
+
urlOf('example.com/a', { requireScheme: true }) // null (bare domains opt-out)
|
|
134
|
+
|
|
135
|
+
// `key` is the point: every spelling of one resource collides.
|
|
136
|
+
// Recognized sites collapse to their native content id —
|
|
137
|
+
urlKey('https://music.youtube.com/watch?v=dQw4w9WgXcQ&si=x') // 'youtube:dQw4w9WgXcQ'
|
|
138
|
+
urlKey('https://www.youtube.com/shorts/dQw4w9WgXcQ') // 'youtube:dQw4w9WgXcQ'
|
|
139
|
+
// — everything else canonicalizes generically:
|
|
140
|
+
urlKey('https://www.theverge.com/2026/story/?utm_source=x#c') // 'theverge.com/2026/story'
|
|
141
|
+
|
|
142
|
+
// The SAME resource minus tracking — keeps scheme, www., order, fragment.
|
|
143
|
+
cleanUrl('https://www.youtube.com/watch?v=abc&t=120s&si=junk')
|
|
119
144
|
// 'https://www.youtube.com/watch?v=abc&t=120s'
|
|
120
145
|
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// '
|
|
146
|
+
// YouTube identity from ANY spelling (watch, youtu.be, shorts, live, embed,
|
|
147
|
+
// music., nocookie, redirect wrappers, bare 11-char id):
|
|
148
|
+
youtubeVideoId('https://www.youtube.com/attribution_link?u=%2Fwatch%3Fv%3DdQw4w9WgXcQ')
|
|
149
|
+
// 'dQw4w9WgXcQ'
|
|
150
|
+
youtubeUrl('dQw4w9WgXcQ') // 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
|
|
151
|
+
youtubeThumbnailUrl('dQw4w9WgXcQ') // 'https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg'
|
|
152
|
+
|
|
153
|
+
hostOf('https://www.theverge.com/a') // 'theverge.com'
|
|
154
|
+
hostMatches('music.youtube.com', 'youtube.com') // true (RFC 6265 domain-match)
|
|
155
|
+
hostMatches('notyoutube.com', 'youtube.com') // false
|
|
126
156
|
|
|
127
157
|
// App-specific junk rides along per call
|
|
128
158
|
urlKey(url, { strip: ['session_id'] })
|
|
@@ -1,18 +1,61 @@
|
|
|
1
1
|
export { TIDY_RULES, TIDY_VERSION, type TrackingProvider } from "./tidy-rules.js";
|
|
2
2
|
export { OVERLAY_RULES } from "./overlay.js";
|
|
3
|
+
export { SITES, siteFor, youtubeThumbnailUrl, youtubeUrl, youtubeVideoId, type SiteAdapter } from "./sites.js";
|
|
3
4
|
export interface StripOptions {
|
|
4
5
|
/** Extra param names to strip (compared lowercased), for app-specific junk. */
|
|
5
6
|
strip?: readonly string[];
|
|
6
7
|
}
|
|
8
|
+
export interface UrlsInOptions extends StripOptions {
|
|
9
|
+
/** Drop scheme-less matches (`example.com/x`) instead of coercing them to https://. */
|
|
10
|
+
requireScheme?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** One URL resolved from text: every layer of the funnel, answered once. */
|
|
13
|
+
export interface Url {
|
|
14
|
+
/** Exactly the text that matched (punctuation and tracking still on it). */
|
|
15
|
+
raw: string;
|
|
16
|
+
/** Span of the raw match in the input text (cut links out of a message). */
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
/** False when the scanner inferred the link from a bare domain (`example.com/x`). */
|
|
20
|
+
hadScheme: boolean;
|
|
21
|
+
/** The cleaned absolute URL — fetch, share, and display this. */
|
|
22
|
+
href: string;
|
|
23
|
+
/** The www-less host. */
|
|
24
|
+
host: string;
|
|
25
|
+
/** The recognized service owning the host ("youtube"), or null. */
|
|
26
|
+
site: string | null;
|
|
27
|
+
/** The service's native content id (a YouTube video id), or null. */
|
|
28
|
+
id: string | null;
|
|
29
|
+
/** Canonical identity for caches/dedupe: every spelling of one resource collides. */
|
|
30
|
+
key: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Every http(s) URL in free text, in order, fully resolved. Duplicate
|
|
34
|
+
* spellings are kept (spans matter); dedupe is one line on `key`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function urlsIn(text: string, opts?: UrlsInOptions): Url[];
|
|
37
|
+
/** One pasted token / URL string as a resolved {@link Url}, or null. */
|
|
38
|
+
export declare function urlOf(token: string, opts?: UrlsInOptions): Url | null;
|
|
39
|
+
/** A URL's host without a leading www., or the input unchanged if it won't parse. */
|
|
40
|
+
export declare function hostOf(url: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* True if `host` IS `domain` or a subdomain of it — never a lookalike suffix
|
|
43
|
+
* (`notyoutube.com` doesn't match `youtube.com`). RFC 6265 §5.1.3 domain-
|
|
44
|
+
* matching semantics, the same rule cookies use.
|
|
45
|
+
*/
|
|
46
|
+
export declare function hostMatches(host: string, domain: string): boolean;
|
|
7
47
|
/**
|
|
8
48
|
* The same URL minus tracking params: safe to fetch, share, and display.
|
|
9
49
|
* Unparseable or non-http(s) input is returned unchanged.
|
|
10
50
|
*/
|
|
11
51
|
export declare function cleanUrl(url: string, opts?: StripOptions): string;
|
|
12
52
|
/**
|
|
13
|
-
* A canonical identity for the resource behind `url
|
|
14
|
-
*
|
|
15
|
-
*
|
|
53
|
+
* A canonical identity for the resource behind `url`, for cache keys and
|
|
54
|
+
* dedupe. A recognized site's content collapses to its native id
|
|
55
|
+
* ("youtube:dQw4w9WgXcQ" — watch, youtu.be, shorts, live, embed, all one
|
|
56
|
+
* key); everything else canonicalizes to `host/path?sortedQuery`,
|
|
57
|
+
* scheme-agnostic, www-less, tracking-stripped, fragment-free. Unparseable
|
|
58
|
+
* or non-http(s) input is returned unchanged so it still keys consistently.
|
|
16
59
|
*/
|
|
17
60
|
export declare function urlKey(url: string, opts?: StripOptions): string;
|
|
18
61
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/universal/url/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/universal/url/index.ts"],"names":[],"mappings":"AA4CA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE/G,MAAM,WAAW,YAAY;IAC5B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IAClD,uFAAuF;IACvF,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,4EAA4E;AAC5E,MAAM,WAAW,GAAG;IACnB,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,qEAAqE;IACrE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,GAAG,EAAE,CAahE;AAED,wEAAwE;AACxE,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,GAAG,GAAG,IAAI,CAErE;AAED,qFAAqF;AACrF,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM1C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIjE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM,CAKjE;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,MAAM,CAY/D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,OAAO,CAUhE"}
|
|
@@ -1,31 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* URLs, from user-typed text to cache identity. Safe in Node, Workers, and
|
|
3
|
+
* browsers. The whole funnel every consumer walks — is there a URL? which
|
|
4
|
+
* resource? whose content? what identity? — answered ONCE, on one object:
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* Url {
|
|
7
|
+
* raw, start, end what they typed, and where in the text
|
|
8
|
+
* hadScheme typed https:// vs inferred from a bare domain
|
|
9
|
+
* href cleaned absolute URL — fetch, share, display this
|
|
10
|
+
* host www-less host
|
|
11
|
+
* site recognized service ("youtube") or null
|
|
12
|
+
* id the service's native content id, or null
|
|
13
|
+
* key canonical identity for caches/dedupe:
|
|
14
|
+
* "youtube:dQw4w9WgXcQ" | "theverge.com/2026/story"
|
|
15
|
+
* }
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* overlay for gaps (overlay.ts). URLs are parsed with the WHATWG URL API,
|
|
19
|
-
* never regexes; the dataset's per-provider matchers select which rules apply
|
|
20
|
-
* to a host, and param names are compared literally (lowercased).
|
|
17
|
+
* Two verbs, one per input shape:
|
|
18
|
+
* urlsIn(text) → Url[] every URL in free text, in order, with spans
|
|
19
|
+
* urlOf(token) → Url | null one pasted token / URL string
|
|
21
20
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* The `key` is the point: every spelling of one resource collides (scheme,
|
|
22
|
+
* www., trailing slash, tracking params, and for recognized sites every URL
|
|
23
|
+
* shape of the same content id), and two different resources never do.
|
|
24
|
+
*
|
|
25
|
+
* Each concern rides its state of the art instead of hand-rolls:
|
|
26
|
+
* - detection in free text: linkifyjs (scanner-based, TLD-aware, MIT) — it
|
|
27
|
+
* owns the messy boundaries (trailing punctuation, brackets, emails);
|
|
28
|
+
* - parsing/serialization: the WHATWG URL API, never regexes;
|
|
29
|
+
* - which params are tracking: vendored from @protontech/tidy-url (Proton's
|
|
30
|
+
* maintained fork of DrKain/tidy-url, MIT; see tidy-rules.ts, refresh with
|
|
31
|
+
* `pnpm update-url-rules`) plus a small tested overlay (overlay.ts) —
|
|
32
|
+
* param names compare literally (lowercased), per host;
|
|
33
|
+
* - per-site content identity: the adapter registry (sites.ts).
|
|
34
|
+
*
|
|
35
|
+
* The low-level verbs (`cleanUrl`, `urlKey`, `hostOf`, `hostMatches`,
|
|
36
|
+
* `isTrackingParam`, the youtube helpers) stay exported for surgical use.
|
|
37
|
+
* Unparseable or non-http(s) input passes through unchanged: these are
|
|
38
|
+
* hygiene functions over user-pasted text, not validators.
|
|
24
39
|
*/
|
|
40
|
+
import { find } from "linkifyjs";
|
|
25
41
|
import { OVERLAY_RULES } from "./overlay.js";
|
|
42
|
+
import { siteFor } from "./sites.js";
|
|
26
43
|
import { TIDY_RULES } from "./tidy-rules.js";
|
|
27
44
|
export { TIDY_RULES, TIDY_VERSION } from "./tidy-rules.js";
|
|
28
45
|
export { OVERLAY_RULES } from "./overlay.js";
|
|
46
|
+
export { SITES, siteFor, youtubeThumbnailUrl, youtubeUrl, youtubeVideoId } from "./sites.js";
|
|
47
|
+
/**
|
|
48
|
+
* Every http(s) URL in free text, in order, fully resolved. Duplicate
|
|
49
|
+
* spellings are kept (spans matter); dedupe is one line on `key`.
|
|
50
|
+
*/
|
|
51
|
+
export function urlsIn(text, opts) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const link of find(text, "url", { defaultProtocol: "https" })) {
|
|
54
|
+
// href === value exactly when the text already carried a scheme; otherwise
|
|
55
|
+
// the scanner built href by prepending defaultProtocol onto the bare match.
|
|
56
|
+
const hadScheme = link.href === link.value;
|
|
57
|
+
if (!hadScheme && opts?.requireScheme)
|
|
58
|
+
continue;
|
|
59
|
+
const u = parseHttp(link.href);
|
|
60
|
+
if (!u)
|
|
61
|
+
continue; // a non-http scheme the scanner knows; not our job
|
|
62
|
+
deleteTracking(u, opts);
|
|
63
|
+
out.push(resolveUrl(u, { raw: link.value, start: link.start, end: link.end, hadScheme }));
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** One pasted token / URL string as a resolved {@link Url}, or null. */
|
|
68
|
+
export function urlOf(token, opts) {
|
|
69
|
+
return urlsIn(token, opts)[0] ?? null;
|
|
70
|
+
}
|
|
71
|
+
/** A URL's host without a leading www., or the input unchanged if it won't parse. */
|
|
72
|
+
export function hostOf(url) {
|
|
73
|
+
try {
|
|
74
|
+
return new URL(url).hostname.replace(/^www\./, "");
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return url;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* True if `host` IS `domain` or a subdomain of it — never a lookalike suffix
|
|
82
|
+
* (`notyoutube.com` doesn't match `youtube.com`). RFC 6265 §5.1.3 domain-
|
|
83
|
+
* matching semantics, the same rule cookies use.
|
|
84
|
+
*/
|
|
85
|
+
export function hostMatches(host, domain) {
|
|
86
|
+
const h = host.toLowerCase().replace(/^www\./, "").replace(/\.$/, "");
|
|
87
|
+
const d = domain.toLowerCase();
|
|
88
|
+
return h === d || h.endsWith(`.${d}`);
|
|
89
|
+
}
|
|
29
90
|
/**
|
|
30
91
|
* The same URL minus tracking params: safe to fetch, share, and display.
|
|
31
92
|
* Unparseable or non-http(s) input is returned unchanged.
|
|
@@ -38,21 +99,27 @@ export function cleanUrl(url, opts) {
|
|
|
38
99
|
return u.toString();
|
|
39
100
|
}
|
|
40
101
|
/**
|
|
41
|
-
* A canonical identity for the resource behind `url
|
|
42
|
-
*
|
|
43
|
-
*
|
|
102
|
+
* A canonical identity for the resource behind `url`, for cache keys and
|
|
103
|
+
* dedupe. A recognized site's content collapses to its native id
|
|
104
|
+
* ("youtube:dQw4w9WgXcQ" — watch, youtu.be, shorts, live, embed, all one
|
|
105
|
+
* key); everything else canonicalizes to `host/path?sortedQuery`,
|
|
106
|
+
* scheme-agnostic, www-less, tracking-stripped, fragment-free. Unparseable
|
|
107
|
+
* or non-http(s) input is returned unchanged so it still keys consistently.
|
|
44
108
|
*/
|
|
45
109
|
export function urlKey(url, opts) {
|
|
46
110
|
const u = parseHttp(url);
|
|
47
111
|
if (!u)
|
|
48
112
|
return url;
|
|
113
|
+
const site = siteFor(normalizedHost(u));
|
|
114
|
+
const id = site?.id(u);
|
|
115
|
+
if (site && id)
|
|
116
|
+
return site.key(id);
|
|
49
117
|
deleteTracking(u, opts);
|
|
50
118
|
u.searchParams.sort();
|
|
51
|
-
const host = u.hostname.toLowerCase().replace(/^www\./, "").replace(/\.$/, "");
|
|
52
119
|
const port = u.port ? `:${u.port}` : ""; // default ports never survive URL parsing
|
|
53
120
|
const path = u.pathname.replace(/\/+$/, "") || "/";
|
|
54
121
|
const query = u.searchParams.toString();
|
|
55
|
-
return `${
|
|
122
|
+
return `${normalizedHost(u)}${port}${path}${query ? `?${query}` : ""}`;
|
|
56
123
|
}
|
|
57
124
|
/**
|
|
58
125
|
* True if `name` is a tracking param — globally, or on `host` specifically.
|
|
@@ -72,6 +139,25 @@ export function isTrackingParam(name, host = "") {
|
|
|
72
139
|
}
|
|
73
140
|
return doomed.has(n) && !allowed.has(n);
|
|
74
141
|
}
|
|
142
|
+
// ── internals ───────────────────────────────────────────────────────────────
|
|
143
|
+
/** Assemble a {@link Url} from an already-cleaned parsed URL + its text facts. */
|
|
144
|
+
function resolveUrl(u, text) {
|
|
145
|
+
const host = normalizedHost(u);
|
|
146
|
+
const site = siteFor(host);
|
|
147
|
+
const id = site?.id(u) ?? null;
|
|
148
|
+
return {
|
|
149
|
+
...text,
|
|
150
|
+
href: u.toString(),
|
|
151
|
+
host,
|
|
152
|
+
site: site?.name ?? null,
|
|
153
|
+
id,
|
|
154
|
+
key: site && id ? site.key(id) : urlKey(u.toString()),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Lowercased hostname without a leading www. or a trailing dot. */
|
|
158
|
+
function normalizedHost(u) {
|
|
159
|
+
return u.hostname.toLowerCase().replace(/^www\./, "").replace(/\.$/, "");
|
|
160
|
+
}
|
|
75
161
|
let cache = null;
|
|
76
162
|
/** All providers (vendored + overlay), matchers compiled once per process. */
|
|
77
163
|
function compiled() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/universal/url/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/universal/url/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The site registry: per-service identity knowledge, one adapter per service
|
|
3
|
+
* (the same knowledge-as-data pattern as tidy-rules.ts). An adapter answers
|
|
4
|
+
* "is this host yours?" and "what's the native content id in this URL?", and
|
|
5
|
+
* from the id derives the canonical URL and the cache-identity key — so every
|
|
6
|
+
* spelling of one video (watch, youtu.be, shorts, live, embed, music.,
|
|
7
|
+
* nocookie, nested redirects) collapses to ONE identity.
|
|
8
|
+
*
|
|
9
|
+
* Extraction is WHATWG-URL based, nested-redirect aware, and percent-decode
|
|
10
|
+
* tolerant — no regex URL parsing. Adding a service (spotify, vimeo, …) is
|
|
11
|
+
* one more adapter entry.
|
|
12
|
+
*/
|
|
13
|
+
export interface SiteAdapter {
|
|
14
|
+
/** The service name, and the prefix of its identity keys ("youtube"). */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Whether this (lowercased, www-less) host belongs to the service. */
|
|
17
|
+
matches(host: string): boolean;
|
|
18
|
+
/** The service's native content id in this URL, or null. */
|
|
19
|
+
id(url: URL): string | null;
|
|
20
|
+
/** The canonical URL for a content id. */
|
|
21
|
+
canonicalUrl(id: string): string;
|
|
22
|
+
/** The cache-identity key for a content id ("youtube:dQw4w9WgXcQ"). */
|
|
23
|
+
key(id: string): string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The 11-char video id from ANY YouTube URL spelling or a bare id; null if
|
|
27
|
+
* the input isn't one. Percent-encoded input gets one decode attempt so a
|
|
28
|
+
* copied-from-HTML link still resolves.
|
|
29
|
+
*/
|
|
30
|
+
export declare function youtubeVideoId(input: string): string | null;
|
|
31
|
+
/** The canonical watch URL for a video id or any YouTube URL spelling; null if neither. */
|
|
32
|
+
export declare function youtubeUrl(idOrUrl: string): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* A public thumbnail URL for a video id or any YouTube URL spelling; null if
|
|
35
|
+
* neither. Uses `hqdefault`, which exists for every valid video (unlike
|
|
36
|
+
* `maxresdefault`, which 404s for many).
|
|
37
|
+
*/
|
|
38
|
+
export declare function youtubeThumbnailUrl(idOrUrl: string): string | null;
|
|
39
|
+
export declare const SITES: readonly SiteAdapter[];
|
|
40
|
+
/** The adapter owning this (lowercased, www-less) host, or null. */
|
|
41
|
+
export declare function siteFor(host: string): SiteAdapter | null;
|
|
42
|
+
//# sourceMappingURL=sites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sites.d.ts","sourceRoot":"","sources":["../../../src/universal/url/sites.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,WAAW;IAC3B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,EAAE,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,uEAAuE;IACvE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAgED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY3D;AAED,2FAA2F;AAC3F,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAID,eAAO,MAAM,KAAK,EAAE,SAAS,WAAW,EAAc,CAAC;AAEvD,oEAAoE;AACpE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAExD"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The site registry: per-service identity knowledge, one adapter per service
|
|
3
|
+
* (the same knowledge-as-data pattern as tidy-rules.ts). An adapter answers
|
|
4
|
+
* "is this host yours?" and "what's the native content id in this URL?", and
|
|
5
|
+
* from the id derives the canonical URL and the cache-identity key — so every
|
|
6
|
+
* spelling of one video (watch, youtu.be, shorts, live, embed, music.,
|
|
7
|
+
* nocookie, nested redirects) collapses to ONE identity.
|
|
8
|
+
*
|
|
9
|
+
* Extraction is WHATWG-URL based, nested-redirect aware, and percent-decode
|
|
10
|
+
* tolerant — no regex URL parsing. Adding a service (spotify, vimeo, …) is
|
|
11
|
+
* one more adapter entry.
|
|
12
|
+
*/
|
|
13
|
+
// ── youtube ─────────────────────────────────────────────────────────────────
|
|
14
|
+
const YOUTUBE_ID = /^[a-zA-Z0-9_-]{11}$/;
|
|
15
|
+
const YOUTUBE_PATH_KINDS = new Set(["embed", "v", "shorts", "live"]);
|
|
16
|
+
function isYouTubeHost(host) {
|
|
17
|
+
return (host === "youtube.com" ||
|
|
18
|
+
host.endsWith(".youtube.com") ||
|
|
19
|
+
host === "youtube-nocookie.com" ||
|
|
20
|
+
host.endsWith(".youtube-nocookie.com"));
|
|
21
|
+
}
|
|
22
|
+
function validYouTubeId(value) {
|
|
23
|
+
return value && YOUTUBE_ID.test(value) ? value : null;
|
|
24
|
+
}
|
|
25
|
+
function youtubeIdFromUrl(url) {
|
|
26
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
27
|
+
if (host === "youtu.be") {
|
|
28
|
+
return validYouTubeId(url.pathname.split("/").filter(Boolean)[0]);
|
|
29
|
+
}
|
|
30
|
+
if (!isYouTubeHost(host))
|
|
31
|
+
return null;
|
|
32
|
+
const queryId = validYouTubeId(url.searchParams.get("v") ?? url.searchParams.get("video_id"));
|
|
33
|
+
if (queryId)
|
|
34
|
+
return queryId;
|
|
35
|
+
const [kind, id] = url.pathname.split("/").filter(Boolean);
|
|
36
|
+
if (YOUTUBE_PATH_KINDS.has(kind)) {
|
|
37
|
+
const pathId = validYouTubeId(id);
|
|
38
|
+
if (pathId)
|
|
39
|
+
return pathId;
|
|
40
|
+
}
|
|
41
|
+
// Redirect wrappers (attribution_link?u=/watch%3Fv%3D…, redirect?q=…): the
|
|
42
|
+
// nested value may be relative, so resolve it against the outer URL.
|
|
43
|
+
for (const param of ["u", "url", "q"]) {
|
|
44
|
+
const nested = url.searchParams.get(param);
|
|
45
|
+
if (!nested)
|
|
46
|
+
continue;
|
|
47
|
+
let nestedUrl;
|
|
48
|
+
try {
|
|
49
|
+
nestedUrl = new URL(nested, url);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (nestedUrl.href === url.href)
|
|
55
|
+
continue;
|
|
56
|
+
const nestedId = youtubeIdFromUrl(nestedUrl);
|
|
57
|
+
if (nestedId)
|
|
58
|
+
return nestedId;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const youtube = {
|
|
63
|
+
name: "youtube",
|
|
64
|
+
matches: (host) => host === "youtu.be" || isYouTubeHost(host),
|
|
65
|
+
id: youtubeIdFromUrl,
|
|
66
|
+
canonicalUrl: (id) => `https://www.youtube.com/watch?v=${id}`,
|
|
67
|
+
key: (id) => `youtube:${id}`,
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The 11-char video id from ANY YouTube URL spelling or a bare id; null if
|
|
71
|
+
* the input isn't one. Percent-encoded input gets one decode attempt so a
|
|
72
|
+
* copied-from-HTML link still resolves.
|
|
73
|
+
*/
|
|
74
|
+
export function youtubeVideoId(input) {
|
|
75
|
+
const trimmed = input.trim();
|
|
76
|
+
if (YOUTUBE_ID.test(trimmed))
|
|
77
|
+
return trimmed;
|
|
78
|
+
for (const candidate of [trimmed, safeDecode(trimmed)]) {
|
|
79
|
+
const url = parseLoose(candidate);
|
|
80
|
+
if (url) {
|
|
81
|
+
const id = youtubeIdFromUrl(url);
|
|
82
|
+
if (id)
|
|
83
|
+
return id;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/** The canonical watch URL for a video id or any YouTube URL spelling; null if neither. */
|
|
89
|
+
export function youtubeUrl(idOrUrl) {
|
|
90
|
+
const id = youtubeVideoId(idOrUrl);
|
|
91
|
+
return id ? youtube.canonicalUrl(id) : null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A public thumbnail URL for a video id or any YouTube URL spelling; null if
|
|
95
|
+
* neither. Uses `hqdefault`, which exists for every valid video (unlike
|
|
96
|
+
* `maxresdefault`, which 404s for many).
|
|
97
|
+
*/
|
|
98
|
+
export function youtubeThumbnailUrl(idOrUrl) {
|
|
99
|
+
const id = youtubeVideoId(idOrUrl);
|
|
100
|
+
return id ? `https://i.ytimg.com/vi/${id}/hqdefault.jpg` : null;
|
|
101
|
+
}
|
|
102
|
+
// ── the registry ────────────────────────────────────────────────────────────
|
|
103
|
+
export const SITES = [youtube];
|
|
104
|
+
/** The adapter owning this (lowercased, www-less) host, or null. */
|
|
105
|
+
export function siteFor(host) {
|
|
106
|
+
return SITES.find((s) => s.matches(host)) ?? null;
|
|
107
|
+
}
|
|
108
|
+
// ── internals ───────────────────────────────────────────────────────────────
|
|
109
|
+
function parseLoose(input) {
|
|
110
|
+
try {
|
|
111
|
+
return new URL(input);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
try {
|
|
115
|
+
return new URL(`https://${input}`);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function safeDecode(input) {
|
|
123
|
+
try {
|
|
124
|
+
return decodeURIComponent(input);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return input;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=sites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sites.js","sourceRoot":"","sources":["../../../src/universal/url/sites.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adriangalilea/utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "TypeScript utilities - logger, currency, formatter, GramIO bot plugins, and more",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -220,17 +220,17 @@
|
|
|
220
220
|
"zod": "^4.4.3"
|
|
221
221
|
},
|
|
222
222
|
"peerDependencies": {
|
|
223
|
+
"@ai-sdk/anthropic": "^4.0.0",
|
|
224
|
+
"@ai-sdk/openai-compatible": "^3.0.0",
|
|
223
225
|
"@gramio/format": ">=0.8.0",
|
|
224
226
|
"@gramio/session": ">=0.2.0",
|
|
227
|
+
"@gramio/split": "^0.0.1",
|
|
225
228
|
"@gramio/storage": ">=2.0.0",
|
|
229
|
+
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
230
|
+
"ai": "^7.0.0",
|
|
226
231
|
"gramio": "^0.12.0",
|
|
227
232
|
"marked": ">=12",
|
|
228
|
-
"zod": ">=3.22"
|
|
229
|
-
"ai": "^7.0.0",
|
|
230
|
-
"@ai-sdk/anthropic": "^4.0.0",
|
|
231
|
-
"@ai-sdk/openai-compatible": "^3.0.0",
|
|
232
|
-
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
233
|
-
"@gramio/split": "^0.0.1"
|
|
233
|
+
"zod": ">=3.22"
|
|
234
234
|
},
|
|
235
235
|
"peerDependenciesMeta": {
|
|
236
236
|
"@gramio/format": {
|
|
@@ -279,5 +279,8 @@
|
|
|
279
279
|
"url": "https://github.com/adriangalilea/ts-utils/issues"
|
|
280
280
|
},
|
|
281
281
|
"homepage": "https://github.com/adriangalilea/ts-utils#readme",
|
|
282
|
-
"sideEffects": false
|
|
282
|
+
"sideEffects": false,
|
|
283
|
+
"dependencies": {
|
|
284
|
+
"linkifyjs": "^4.3.3"
|
|
285
|
+
}
|
|
283
286
|
}
|