@postfetch/core 0.13.1 → 0.13.2

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
@@ -13,6 +13,8 @@ for (const item of result.items) {
13
13
 
14
14
  `postfetch(url, options?)` detects the platform and resolves its media into a typed `PostfetchResult` (URLs plus the headers needed to fetch them). It performs no side effects beyond the lookup — `download`, `downloadBlob`, `archive` and `toResponse` materialize the result.
15
15
 
16
+ LinkedIn public posts also accept `lnkd.in` shortlinks, including `https://lnkd.in/p/…`.
17
+
16
18
  Spotify track URLs are matched to YouTube by artist, title and duration, preferring
17
19
  artist uploads and rejecting recognizable alternate versions unless requested.
18
20
  Results have `platform: "spotify"`, Spotify metadata and
package/dist/index.js CHANGED
@@ -796,21 +796,10 @@ function randomToken(length = 8) {
796
796
 
797
797
  // src/linkedin.ts
798
798
  async function resolveLinkedin(input) {
799
- const reference = postReference(input.url);
799
+ const reference = await postReference(input);
800
800
  const pageUrl = `https://www.linkedin.com/feed/update/urn:li:${reference.kind}:${reference.id}`;
801
801
  const response = await input.net(pageUrl, { headers: navigationHeaders() });
802
- if (response.status === 404) {
803
- throw new PostfetchError(404, "LinkedIn post not found", "notFound");
804
- }
805
- if (response.status === 429 || response.status === 999) {
806
- throw new PostfetchError(429, "LinkedIn rate limit reached", "rateLimited");
807
- }
808
- if (!response.ok) {
809
- throw new PostfetchError(response.status, "LinkedIn post unavailable", "unavailable");
810
- }
811
- if (response.url.includes("/signup/") || response.url.includes("/login")) {
812
- throw new PostfetchError(401, "LinkedIn login required", "loginRequired");
813
- }
802
+ checkResponse(response);
814
803
  const html = await response.text();
815
804
  const post = linkedinPost(html);
816
805
  if (!post) {
@@ -846,8 +835,31 @@ function linkedinMetadata(post) {
846
835
  commentCount: count(post.commentCount) ?? interactionCount(post.interactionStatistic, "CommentAction")
847
836
  };
848
837
  }
849
- function postReference(input) {
850
- const reference = postReferenceOrNull(input);
838
+ function checkResponse(response) {
839
+ if (response.status === 404) {
840
+ throw new PostfetchError(404, "LinkedIn post not found", "notFound");
841
+ }
842
+ if (response.status === 429 || response.status === 999) {
843
+ throw new PostfetchError(429, "LinkedIn rate limit reached", "rateLimited");
844
+ }
845
+ if (!response.ok) {
846
+ throw new PostfetchError(response.status, "LinkedIn post unavailable", "unavailable");
847
+ }
848
+ if (response.url.includes("/signup/") || response.url.includes("/login")) {
849
+ throw new PostfetchError(401, "LinkedIn login required", "loginRequired");
850
+ }
851
+ }
852
+ async function postReference(input) {
853
+ let url = input.url;
854
+ if (new URL(url).hostname === "lnkd.in") {
855
+ const response = await input.net(url, { headers: navigationHeaders(), redirect: "manual" });
856
+ await response.body?.cancel();
857
+ if (![301, 302, 303, 307, 308].includes(response.status)) {
858
+ checkResponse(response);
859
+ }
860
+ url = response.headers.get("location") ?? "";
861
+ }
862
+ const reference = postReferenceOrNull(url);
851
863
  if (!reference) {
852
864
  throw new PostfetchError(400, "LinkedIn post id not found");
853
865
  }
@@ -856,7 +868,11 @@ function postReference(input) {
856
868
  function postReferenceOrNull(input) {
857
869
  let pathname;
858
870
  try {
859
- pathname = new URL(input).pathname.replace(/%3A/gi, ":");
871
+ const url = new URL(input);
872
+ if (url.hostname !== "linkedin.com" && !url.hostname.endsWith(".linkedin.com")) {
873
+ return null;
874
+ }
875
+ pathname = url.pathname.replace(/%3A/gi, ":");
860
876
  } catch {
861
877
  return null;
862
878
  }
@@ -3348,7 +3364,7 @@ function detect(url) {
3348
3364
  if (host.includes("instagram.com")) {
3349
3365
  return "instagram";
3350
3366
  }
3351
- if (host === "linkedin.com" || host.endsWith(".linkedin.com")) {
3367
+ if (host === "linkedin.com" || host.endsWith(".linkedin.com") || host === "lnkd.in") {
3352
3368
  return "linkedin";
3353
3369
  }
3354
3370
  if (host.includes("pinterest.") || host === "pin.it") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postfetch/core",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "description": "Zero-dependency typed core that turns Facebook, Instagram, LinkedIn, Pinterest, Reddit, SoundCloud, Spotify, TikTok, X and YouTube post URLs into media files.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/linkedin.ts CHANGED
@@ -21,21 +21,10 @@ type LinkedinPostReference = {
21
21
  };
22
22
 
23
23
  export async function resolveLinkedin(input: ResolveContext): Promise<PostfetchResult> {
24
- const reference = postReference(input.url);
24
+ const reference = await postReference(input);
25
25
  const pageUrl = `https://www.linkedin.com/feed/update/urn:li:${reference.kind}:${reference.id}`;
26
26
  const response = await input.net(pageUrl, { headers: navigationHeaders() });
27
- if (response.status === 404) {
28
- throw new PostfetchError(404, "LinkedIn post not found", "notFound");
29
- }
30
- if (response.status === 429 || response.status === 999) {
31
- throw new PostfetchError(429, "LinkedIn rate limit reached", "rateLimited");
32
- }
33
- if (!response.ok) {
34
- throw new PostfetchError(response.status, "LinkedIn post unavailable", "unavailable");
35
- }
36
- if (response.url.includes("/signup/") || response.url.includes("/login")) {
37
- throw new PostfetchError(401, "LinkedIn login required", "loginRequired");
38
- }
27
+ checkResponse(response);
39
28
 
40
29
  const html = await response.text();
41
30
  const post = linkedinPost(html);
@@ -76,8 +65,33 @@ export function linkedinMetadata(post: Json): PostMetadata {
76
65
  };
77
66
  }
78
67
 
79
- function postReference(input: string): LinkedinPostReference {
80
- const reference = postReferenceOrNull(input);
68
+ function checkResponse(response: Response): void {
69
+ if (response.status === 404) {
70
+ throw new PostfetchError(404, "LinkedIn post not found", "notFound");
71
+ }
72
+ if (response.status === 429 || response.status === 999) {
73
+ throw new PostfetchError(429, "LinkedIn rate limit reached", "rateLimited");
74
+ }
75
+ if (!response.ok) {
76
+ throw new PostfetchError(response.status, "LinkedIn post unavailable", "unavailable");
77
+ }
78
+ if (response.url.includes("/signup/") || response.url.includes("/login")) {
79
+ throw new PostfetchError(401, "LinkedIn login required", "loginRequired");
80
+ }
81
+ }
82
+
83
+ async function postReference(input: ResolveContext): Promise<LinkedinPostReference> {
84
+ let url = input.url;
85
+ if (new URL(url).hostname === "lnkd.in") {
86
+ // Read the destination without fetching the post twice or following outbound links.
87
+ const response = await input.net(url, { headers: navigationHeaders(), redirect: "manual" });
88
+ await response.body?.cancel();
89
+ if (![301, 302, 303, 307, 308].includes(response.status)) {
90
+ checkResponse(response);
91
+ }
92
+ url = response.headers.get("location") ?? "";
93
+ }
94
+ const reference = postReferenceOrNull(url);
81
95
  if (!reference) {
82
96
  throw new PostfetchError(400, "LinkedIn post id not found");
83
97
  }
@@ -87,7 +101,11 @@ function postReference(input: string): LinkedinPostReference {
87
101
  function postReferenceOrNull(input: string): LinkedinPostReference | null {
88
102
  let pathname: string;
89
103
  try {
90
- pathname = new URL(input).pathname.replace(/%3A/gi, ":");
104
+ const url = new URL(input);
105
+ if (url.hostname !== "linkedin.com" && !url.hostname.endsWith(".linkedin.com")) {
106
+ return null;
107
+ }
108
+ pathname = url.pathname.replace(/%3A/gi, ":");
91
109
  } catch {
92
110
  return null;
93
111
  }
package/src/postfetch.ts CHANGED
@@ -171,7 +171,7 @@ export function detect(url: string): Platform {
171
171
  if (host.includes("instagram.com")) {
172
172
  return "instagram";
173
173
  }
174
- if (host === "linkedin.com" || host.endsWith(".linkedin.com")) {
174
+ if (host === "linkedin.com" || host.endsWith(".linkedin.com") || host === "lnkd.in") {
175
175
  return "linkedin";
176
176
  }
177
177
  if (host.includes("pinterest.") || host === "pin.it") {