@postfetch/core 0.13.2 → 0.13.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/index.js +13 -7
- package/dist/instagram.d.ts +1 -1
- package/package.json +1 -1
- package/src/instagram.ts +11 -4
- package/src/reddit.ts +9 -4
package/dist/index.js
CHANGED
|
@@ -409,13 +409,13 @@ async function instagramUnavailable(net, code, pageReason) {
|
|
|
409
409
|
url.searchParams.set("url", `https://www.instagram.com/p/${code}/`);
|
|
410
410
|
const response = await net(url.href, { headers: mobileHeaders() }, 1).catch(() => null);
|
|
411
411
|
const body = response ? await response.text().catch(() => "") : "";
|
|
412
|
-
const reason = pageReason ?? instagramUnavailableReason(response?.status ?? 0, body);
|
|
412
|
+
const reason = pageReason ?? instagramUnavailableReason(response?.status ?? 0, body, code);
|
|
413
413
|
const probe = response ? `oembed ${response.status}` : "oembed unreachable";
|
|
414
414
|
return new PostfetchError(reasonStatus(reason), `Instagram post unavailable: ${reason} (${probe})`, reason);
|
|
415
415
|
}
|
|
416
|
-
function instagramUnavailableReason(status, body) {
|
|
416
|
+
function instagramUnavailableReason(status, body, code) {
|
|
417
417
|
if (status === 404) {
|
|
418
|
-
return "notFound";
|
|
418
|
+
return code && isPrivateShortcode(code) ? "private" : "notFound";
|
|
419
419
|
}
|
|
420
420
|
if (status === 429) {
|
|
421
421
|
return "rateLimited";
|
|
@@ -434,6 +434,9 @@ function instagramUnavailableReason(status, body) {
|
|
|
434
434
|
}
|
|
435
435
|
return "unavailable";
|
|
436
436
|
}
|
|
437
|
+
function isPrivateShortcode(code) {
|
|
438
|
+
return code.length > 11;
|
|
439
|
+
}
|
|
437
440
|
function reasonStatus(reason) {
|
|
438
441
|
switch (reason) {
|
|
439
442
|
case "ageRestricted":
|
|
@@ -1642,14 +1645,17 @@ async function appToken(net) {
|
|
|
1642
1645
|
return token;
|
|
1643
1646
|
}
|
|
1644
1647
|
async function fetchPost(net, token, id) {
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
+
const headers = { authorization: `Bearer ${token}`, "user-agent": browserUserAgent() };
|
|
1649
|
+
let response = await net(`https://oauth.reddit.com/comments/${id}?raw_json=1&limit=1`, { headers });
|
|
1650
|
+
if (response.status === 403) {
|
|
1651
|
+
await response.body?.cancel();
|
|
1652
|
+
response = await net(`https://oauth.reddit.com/by_id/t3_${id}?raw_json=1`, { headers });
|
|
1653
|
+
}
|
|
1648
1654
|
if (!response.ok) {
|
|
1649
1655
|
throw new Error(`Reddit post failed: ${response.status}`);
|
|
1650
1656
|
}
|
|
1651
1657
|
const payload = await response.json().catch(() => null);
|
|
1652
|
-
const listing = Array.isArray(payload) ? payload[0] :
|
|
1658
|
+
const listing = Array.isArray(payload) ? payload[0] : payload;
|
|
1653
1659
|
const children = object(listing) && object(listing.data) && Array.isArray(listing.data.children) ? listing.data.children : [];
|
|
1654
1660
|
const first = children[0];
|
|
1655
1661
|
const post = object(first) && object(first.data) ? first.data : null;
|
package/dist/instagram.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type InstagramExtra, type PostMetadata, type PostfetchReason, type ResolveContext, type Json, type PostfetchResult } from "./internal";
|
|
2
2
|
export declare function resolveInstagram(input: ResolveContext): Promise<PostfetchResult>;
|
|
3
|
-
export declare function instagramUnavailableReason(status: number, body: string): PostfetchReason;
|
|
3
|
+
export declare function instagramUnavailableReason(status: number, body: string, code?: string): PostfetchReason;
|
|
4
4
|
export declare function instagramMetadata(media: Json): PostMetadata & {
|
|
5
5
|
extra?: InstagramExtra;
|
|
6
6
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postfetch/core",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
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/instagram.ts
CHANGED
|
@@ -91,7 +91,7 @@ async function instagramUnavailable(net: Net, code: string, pageReason?: Postfet
|
|
|
91
91
|
const body = response ? await response.text().catch(() => "") : "";
|
|
92
92
|
// The page can identify an age gate even when oembed only reports a generic
|
|
93
93
|
// geoblock_required error with no MIN_AGE_ACCOUNT marker.
|
|
94
|
-
const reason = pageReason ?? instagramUnavailableReason(response?.status ?? 0, body);
|
|
94
|
+
const reason = pageReason ?? instagramUnavailableReason(response?.status ?? 0, body, code);
|
|
95
95
|
// Carry the oembed status so an unclassified failure still leaves a raw clue
|
|
96
96
|
// (200 = post is public but no media could be parsed; 429 = throttled; …).
|
|
97
97
|
const probe = response ? `oembed ${response.status}` : "oembed unreachable";
|
|
@@ -101,10 +101,11 @@ async function instagramUnavailable(net: Net, code: string, pageReason?: Postfet
|
|
|
101
101
|
// Classifies why the post is gone from the oembed endpoint, which returns a
|
|
102
102
|
// telling status and body (404 when removed; 429 when throttled; 400 with a
|
|
103
103
|
// MIN_AGE_ACCOUNT block for 18+ accounts) while the media endpoints just come
|
|
104
|
-
// back empty.
|
|
105
|
-
|
|
104
|
+
// back empty. Share links to posts from private accounts carry a long
|
|
105
|
+
// shortcode, and oembed answers those with the same 404 as a removed post.
|
|
106
|
+
export function instagramUnavailableReason(status: number, body: string, code?: string): PostfetchReason {
|
|
106
107
|
if (status === 404) {
|
|
107
|
-
return "notFound";
|
|
108
|
+
return code && isPrivateShortcode(code) ? "private" : "notFound";
|
|
108
109
|
}
|
|
109
110
|
if (status === 429) {
|
|
110
111
|
return "rateLimited";
|
|
@@ -124,6 +125,12 @@ export function instagramUnavailableReason(status: number, body: string): Postfe
|
|
|
124
125
|
return "unavailable";
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
// Public shortcodes are at most 11 characters; private share links append an
|
|
129
|
+
// owner-scoped suffix (for example 39 characters in total).
|
|
130
|
+
function isPrivateShortcode(code: string): boolean {
|
|
131
|
+
return code.length > 11;
|
|
132
|
+
}
|
|
133
|
+
|
|
127
134
|
function reasonStatus(reason: PostfetchReason): number {
|
|
128
135
|
switch (reason) {
|
|
129
136
|
case "ageRestricted":
|
package/src/reddit.ts
CHANGED
|
@@ -100,14 +100,19 @@ async function appToken(net: Net): Promise<string> {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
async function fetchPost(net: Net, token: string, id: string): Promise<Json> {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
const headers = { authorization: `Bearer ${token}`, "user-agent": browserUserAgent() };
|
|
104
|
+
let response = await net(`https://oauth.reddit.com/comments/${id}?raw_json=1&limit=1`, { headers });
|
|
105
|
+
// Reddit can block the comments route while still allowing direct post
|
|
106
|
+
// listings with the same token. This route returns the post, not its replies.
|
|
107
|
+
if (response.status === 403) {
|
|
108
|
+
await response.body?.cancel();
|
|
109
|
+
response = await net(`https://oauth.reddit.com/by_id/t3_${id}?raw_json=1`, { headers });
|
|
110
|
+
}
|
|
106
111
|
if (!response.ok) {
|
|
107
112
|
throw new Error(`Reddit post failed: ${response.status}`);
|
|
108
113
|
}
|
|
109
114
|
const payload = await response.json().catch(() => null);
|
|
110
|
-
const listing = Array.isArray(payload) ? payload[0] :
|
|
115
|
+
const listing = Array.isArray(payload) ? payload[0] : payload;
|
|
111
116
|
const children = object(listing) && object(listing.data) && Array.isArray(listing.data.children) ? listing.data.children : [];
|
|
112
117
|
const first = children[0];
|
|
113
118
|
const post = object(first) && object(first.data) ? first.data : null;
|