Skip to content

Added clips (aka reels) feed#1636

Open
NickCis wants to merge 1 commit into
dilame:masterfrom
NickCis:1308-clips
Open

Added clips (aka reels) feed#1636
NickCis wants to merge 1 commit into
dilame:masterfrom
NickCis:1308-clips

Conversation

@NickCis

@NickCis NickCis commented Jul 19, 2022

Copy link
Copy Markdown

This PR implements the Clips (aka reels) Feed (#1308 )

// ....
const feed = ig.feed.clips(targetUserPk);
const items = await feed.items();

for (const { media: item } of items) {
  console.log('Clip (aka reel)': item);
}

@Nerixyz

Nerixyz commented Jul 19, 2022

Copy link
Copy Markdown
Collaborator

Looks fine to me. Though I'm not sure if we should call it ClipsUserFeed, since there's also a clips explore page which would be ClipsExploreFeed (?).

@NickCis

NickCis commented Jul 19, 2022

Copy link
Copy Markdown
Author

I could change the name, I really don't know how to name it as there is reelsMedia (which is the stories) feed, so using the term reel could also be confusing.

Please tell me what name should it have, so I can change it.

@zakrian07

Copy link
Copy Markdown

import 'dotenv/config';
import { IgApiClient } from '../src';
import { promises as fs } from 'fs';
import * as path from 'path';

const sessionFilePath = path.resolve(__dirname, 'session.json');
console.log(Session file path: ${sessionFilePath});

async function getReelsByUsername(ig, username) {
const targetUser = await ig.user.searchExact(username);
console.log('targetuser', targetUser);
const reelsFeed = ig.feed.clips(targetUser.pk);
const allReels = [];
console.log('reelsFeed', reelsFeed);

let reels = await reelsFeed.items();
allReels.push(...reels);
console.log(Fetched ${reels.length} reels);

while (reelsFeed.isMoreAvailable()) {
console.log('More reels are available, fetching...');
reels = await reelsFeed.items();
allReels.push(...reels);
console.log(Fetched ${reels.length} more reels);
}

// Log detailed information about each reel
allReels.forEach((reel, index) => {
const media = reel.media;
console.log(Reel ${index + 1}:, {
id: media.id,
coverPhotoUrl: media.image_versions2?.candidates?.[0]?.url,
clipUrl: media.video_versions?.[0]?.url,
subtitle: media.caption?.text,
likeCount: media.like_count,
viewCount: media.view_count,
playCount: media.play_count
});
});

// Return the mapped array
return allReels.map(reel => {
const media = reel.media;
const coverPhotoUrl = media.image_versions2?.candidates?.[0]?.url;
const clipUrl = media.video_versions?.[0]?.url;
const subtitle = media.caption?.text;

// Additional information
const likeCount = media.like_count;
const viewCount = media.view_count;
const playCount = media.play_count;

return {
  id: media.id,
  coverPhotoUrl: coverPhotoUrl,
  clipUrl: clipUrl,
  subtitle: subtitle,
  likeCount: likeCount,
  viewCount: viewCount,
  playCount: playCount
};

});
}

async function saveSession(data: object) {
console.log('Saving session to file...');
await fs.writeFile(sessionFilePath, JSON.stringify(data));
console.log('Session saved successfully.');
}

async function sessionExists(): Promise {
try {
await fs.access(sessionFilePath);
console.log('Session file exists.');
return true;
} catch {
console.log('Session file does not exist.');
return false;
}
}

async function loadSession(): Promise {
try {
const data = await fs.readFile(sessionFilePath, { encoding: 'utf8' });
console.log('Session data loaded successfully.');
return data;
} catch (error) {
console.error('Failed to read the session file:', error);
return ''; // Return empty to signify failed loading
}
}

(async () => {
const ig = new IgApiClient();
ig.state.generateDevice(process.env.IG_USERNAME);

if (process.env.IG_PROXY) {
ig.state.proxyUrl = process.env.IG_PROXY;
}

(ig.request.end$ as any).subscribe(async () => {
try {
const serialized = await ig.state.serialize();
delete serialized.constants;
await saveSession(serialized);
} catch (error) {
console.error('Failed to serialize the session:', error);
}
});

if (await sessionExists()) {
const sessionData = await loadSession();
if (sessionData) {
try {
await ig.state.deserialize(sessionData);
} catch (error) {
console.error('Failed to deserialize the session data:', error);
// If deserialization fails, you may want to log in again here
}
} else {
console.log('Session data is empty or invalid.');
// Log in again if there's no session data
}
} else {
console.log('No valid session found, attempting to log in...');
if (!process.env.IG_USERNAME || !process.env.IG_PASSWORD) {
console.error('No credentials provided.');
return;
}
await ig.account.login(process.env.IG_USERNAME, process.env.IG_PASSWORD);
console.log('Login successful.');
}
console.log('Login works.');
// The rest of your Instagram operations
const usernameToFetch = 'mrbeast'; // Replace with the actual username
try {
const reels = await getReelsByUsername(ig, usernameToFetch);
console.log(reels);
} catch (err) {
console.error('Error fetching reels:', err);
}
})();
here is the working example you can put in your doc or make example file

@zakrian07

Copy link
Copy Markdown

@NickCis plz let me know i have logged in user is these calls can be happen with non logged in users ? also can i use proxy with logged in users plz let me know what best practices to use this .Thanks for the work all you did

@nguyenmp

Copy link
Copy Markdown

Any way we can get this merged? It would unblock a side-project of mine a lot!

@Nerixyz

Nerixyz commented Jun 26, 2025

Copy link
Copy Markdown
Collaborator

I can merge this, but I can't release a new version. Note that npm and friends allow you to take file dependencies docs.

@nguyenmp

nguyenmp commented Jul 12, 2025

Copy link
Copy Markdown

I'm unblocked now but wanted to share some things I discovered.

I was having some weird issues installing the branch in alpine docker, it kept saying things like:

npm install https://github.com/nguyenmp/instagram-private-api

12.52 npm error tsconfig.json(14,15): error TS6046: Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext'.
12.52 npm error src/core/feed.ts(18,14): error TS1056: Accessors are only available when targeting ECMAScript 5 and higher.
12.52 npm error src/core/state.ts(136,14): error TS1056: Accessors are only available when targeting ECMAScript 5 and higher.

Turns out I had to change ES2017 to es2017 (lowercase) in tsconfig.json.

I also had to run it as non-root or else I'd get a bunch of errors that boiled down to:

7.820 npm error npm error sh: rimraf: not found

While I was struggling with that, I reimplemented the feature of this PR, but without the feeds API. This might be useful for anyone who's just hacking around and can't get a branch installed. I have to say, the code is incredibly straightforward and very modular. I'm impressed! Here's my hack:

/**
 * Gets the reels page of the given user
 * Optionally provide options: {limit: 30} if you want more or less of the feed
 */
async function get_user_reels(ig, username, options) {
  // Assumes ig is already logged in
  const user =  await ig.user.searchExact(username);
  const userId = user.pk
  const url = '/api/v1/clips/user/';
  const form = {
    target_user_id: userId,
    _csrftoken: ig.state.cookieCsrfToken,
    _uuid: ig.state.uuid,
  }
  return await get_feed_manual(ig, url, form, options);
}

/**
 * Basically an implementation of "feeds" in the instagram-private-api world, but we do it ourselves.
 * It recursively gets more and more items until options.limit is hit or there's no more content.
 * Limit defaults to 50.
 */
async function get_feed_manual(ig, url, form, options) {
  const limit = options?.limit ?? 50
  const items = options?.items ?? [];
  
  const { body } = await ig.request.send({
      url,
      form,
      method: 'POST',
    });

  for (const item of body.items) {
    items.push(item.media);
  }

  if (body.paging_info.more_available && items.length < limit) {
    const newForm = {
      ...form,
      max_id: body.paging_info.max_id,
    }
    const newOptions = {
      ...options,
      items,
    }
    await get_feed_manual(ig, url, newForm, newOptions);
  }
  return items
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants