@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 +2 -0
- package/dist/index.js +33 -17
- package/package.json +1 -1
- package/src/linkedin.ts +34 -16
- package/src/postfetch.ts +1 -1
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
|
|
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
|
-
|
|
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
|
|
850
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
80
|
-
|
|
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
|
-
|
|
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") {
|