open-nami 1.0.5 → 1.0.6

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.
Files changed (2) hide show
  1. package/dist/cli.js +39 -21
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -9,8 +9,8 @@ var require_package = __commonJS({
9
9
  "package.json"(exports, module) {
10
10
  module.exports = {
11
11
  name: "open-nami",
12
- version: "1.0.5",
13
- description: "Open Nami \u2014 terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
12
+ version: "1.0.6",
13
+ description: "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
14
14
  type: "module",
15
15
  bin: {
16
16
  "open-nami": "dist/cli.js",
@@ -2153,18 +2153,28 @@ var InstagramExtractor = class {
2153
2153
  return { edges: [], hasNextPage: false };
2154
2154
  }
2155
2155
  async fetchNextPostsPage(userId, username, cursor, options, tokens) {
2156
+ try {
2157
+ const page = await this.fetchPostsTabGraphQL(username, cursor, tokens || {}, options);
2158
+ if (page.edges && page.edges.length > 0) {
2159
+ return page;
2160
+ }
2161
+ } catch (err) {
2162
+ logExtractorDebug("PolarisProfilePostsTabContentQuery_connection", err);
2163
+ }
2156
2164
  if (userId) {
2157
2165
  const variables = JSON.stringify({ id: userId, first: 12, after: cursor });
2158
2166
  const url = `https://www.instagram.com/graphql/query/?query_hash=${IG_TIMELINE_QUERY_HASH}&variables=${encodeURIComponent(variables)}`;
2159
2167
  try {
2168
+ const headers = {
2169
+ "X-IG-App-ID": IG_APP_ID,
2170
+ "Referer": `https://www.instagram.com/${username}/`
2171
+ };
2172
+ if (tokens?.csrfToken) headers["X-CSRFToken"] = tokens.csrfToken;
2160
2173
  const res = await this.httpClient.fetchJson(url, {
2161
2174
  cookiePath: options.cookiePath,
2162
2175
  cookieDomain: "instagram.com",
2163
2176
  signal: options.signal,
2164
- headers: {
2165
- "X-IG-App-ID": IG_APP_ID,
2166
- "Referer": `https://www.instagram.com/${username}/`
2167
- }
2177
+ headers
2168
2178
  });
2169
2179
  const data = res?.data?.user?.edge_owner_to_timeline_media;
2170
2180
  if (data?.edges && data.edges.length > 0) {
@@ -2178,25 +2188,19 @@ var InstagramExtractor = class {
2178
2188
  logExtractorDebug("edge_owner_to_timeline_media", err);
2179
2189
  }
2180
2190
  }
2181
- try {
2182
- const page = await this.fetchPostsTabGraphQL(username, cursor, tokens || {}, options);
2183
- if (page.edges && page.edges.length > 0) {
2184
- return page;
2185
- }
2186
- } catch (err) {
2187
- logExtractorDebug("PolarisProfilePostsTabContentQuery_connection", err);
2188
- }
2189
2191
  if (userId) {
2190
2192
  try {
2191
2193
  const restUrl = `https://www.instagram.com/api/v1/feed/user/${userId}/?count=12&max_id=${encodeURIComponent(cursor)}`;
2194
+ const headers = {
2195
+ "X-IG-App-ID": IG_APP_ID,
2196
+ "Referer": `https://www.instagram.com/${username}/`
2197
+ };
2198
+ if (tokens?.csrfToken) headers["X-CSRFToken"] = tokens.csrfToken;
2192
2199
  const res = await this.httpClient.fetchJson(restUrl, {
2193
2200
  cookiePath: options.cookiePath,
2194
2201
  cookieDomain: "instagram.com",
2195
2202
  signal: options.signal,
2196
- headers: {
2197
- "X-IG-App-ID": IG_APP_ID,
2198
- "Referer": `https://www.instagram.com/${username}/`
2199
- }
2203
+ headers
2200
2204
  });
2201
2205
  if (Array.isArray(res?.items) && res.items.length > 0) {
2202
2206
  const edges = res.items.map((item) => ({ node: item }));
@@ -2283,8 +2287,10 @@ var InstagramExtractor = class {
2283
2287
  logExtractorDebug("fetchUserProfile", err);
2284
2288
  }
2285
2289
  let webTokens = {};
2286
- if (!user) {
2290
+ try {
2287
2291
  webTokens = await this.fetchWebTokens(username, options);
2292
+ } catch (err) {
2293
+ logExtractorDebug("fetchWebTokens", err);
2288
2294
  }
2289
2295
  let userId = user?.id || webTokens.userId;
2290
2296
  if (!userId && (subDir === "Stories" || subDir === "Highlights")) {
@@ -3359,6 +3365,14 @@ function sanitizeFilename(rawFilename, fallbackId, ext) {
3359
3365
  }
3360
3366
  return sanitizedBase.includes(".") ? sanitizedBase : `${sanitizedBase}.${cleanExt}`;
3361
3367
  }
3368
+ function isCdnUrl(rawUrl) {
3369
+ try {
3370
+ const hostname = new URL(rawUrl).hostname.toLowerCase();
3371
+ return hostname.endsWith(".cdninstagram.com") || hostname.endsWith(".fbcdn.net") || hostname.endsWith(".tiktokcdn.com") || hostname.endsWith(".akamaized.net") || hostname === "pbs.twimg.com" || hostname === "video.twimg.com";
3372
+ } catch {
3373
+ return false;
3374
+ }
3375
+ }
3362
3376
  async function downloadMediaStream(items, options) {
3363
3377
  const { jobName, destDir, signal, onProgress } = options;
3364
3378
  const httpClient = options.httpClient ?? new HttpClient();
@@ -3439,10 +3453,13 @@ async function downloadMediaStream(items, options) {
3439
3453
  if (options.referer) {
3440
3454
  reqHeaders["Referer"] = options.referer;
3441
3455
  }
3456
+ const isCdn = isCdnUrl(item.url);
3442
3457
  const response = await httpClient.request(item.url, {
3443
3458
  signal,
3444
- cookiePath: options.cookiePath,
3445
- headers: reqHeaders
3459
+ cookiePath: isCdn ? void 0 : options.cookiePath,
3460
+ headers: reqHeaders,
3461
+ retries: 2,
3462
+ retryDelayMs: 1500
3446
3463
  });
3447
3464
  if (!response.ok || !response.body) {
3448
3465
  try {
@@ -3463,6 +3480,7 @@ async function downloadMediaStream(items, options) {
3463
3480
  await fsPromises2.rename(tmpPath, finalPath);
3464
3481
  archive.add(item.id);
3465
3482
  downloaded++;
3483
+ consecutiveErrors = 0;
3466
3484
  onProgress?.({
3467
3485
  job: jobName,
3468
3486
  downloadedCount: downloaded,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "open-nami",
3
- "version": "1.0.5",
4
- "description": "Open Nami — terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
3
+ "version": "1.0.6",
4
+ "description": "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "open-nami": "dist/cli.js",