dilame / instagram-private-api

NodeJS Instagram private API SDK. Written in TypeScript.
MIT License
5.96k stars 1.14k forks source link

Get custom amount of items #1590

Closed vencho-mdp closed 2 years ago

vencho-mdp commented 2 years ago

Feature Request

Form

Put an [x] if you meet the condition, else leave [ ].

Description

A specific description of your feature, so it's understandable to anyone. You can add pictures.

Now, you have to use .items() method to get 12 items and, as far as I know, there isn't any other way. However, when you want to get 60 items, you have to do

[ await userFeed.items(), await userFeed.items(), await userFeed.items(), await userFeed.items(), await userFeed.items()]

Which isn't really DRY nor readable. This is why it could be a nice feature to allow the items method to have a number param which could be the amount of items the user wants to get. Also, it could be really good to allow the items method to have a condition parameter. It'd work the following way: When the method gets N (default: 12) quantitity of items that satisfy that condition, then the promise resolves.

Nerixyz commented 2 years ago

However, when you want to get 60 items, you have to do ... Which isn't really DRY nor readable.

You could make a function that gets you 60 items. You could even make a function that gets you n items.

Option 1

This is why it could be a nice feature to allow the items method to have a number param which could be the amount of items the user wants to get.

Take a look at Feed#items$. With it you can do something like

feed.items$.pipe(mergeMap(x => x), take(60)) /* .subscribe(item => console.log(item)) */
// or 
await firstValueFrom(feed.items$.pipe(mergeMap(x => x), take(60), toArray()));

The takeaway here is that items$ is an observable. You can even control how it polls the feed: here.

Also, it could be really good to allow the items method to have a condition parameter.

That's really easy now. Just put a filter(fn) in the pipe and you're ready to go.

Option 2

Make a wrapper function to convert the feed into an async iterator:

async function* feedToIterator<T>(feed: Feed<any, T>) {
  do {
    const items = await feed.items();
    yield items;
  } while(feed.isMoreAvailable());
}

Then you can use transformers acting on async iterables (or iterator helpers once they are in V8 and Node).

So for example flatten and filter:

async function* flatten<T>(iter: AsyncIterable<T[]>) {
  for await(const items of iter) {
    yield* items;
  }
}

async function* filter<T>(iter: AsyncIterable<T>, fn: (item: T) => boolean) {
  for await(const item of iter) {
    if(fn(item)) yield item;
  }
}

Then you can do something like:

for await(const item of filter(flatten(feedToIterator(feed)), myFn)) {
  console.log(item);
}
vencho-mdp commented 2 years ago

Thanks for the reply! And how would you combine both? (function that takes the first n number of items that satisfy a condition) @Nerixyz

Nerixyz commented 2 years ago

ANd how would you combine both?

Observables

feed.items$.pipe(mergeMap(x => x), filter(myFn), take(60))

AsyncIterables

You'd need an additional take:

async function *take<T>(iter: AsyncIterable<T>, n: number) {
  if(n <= 0) return;
  let i = 0;
  for await(const item of iter) {
    yield item;
    i++;
    if(i >= n) break;
  }
}

If you want to put it in an array you also need a function toArray:

async function toArray<T>(iter: AsyncIterable<T>) {
  const arr = [];
  for await(const item of iter) {
    arr.push(item);
  }
  return arr;
}

Then it's

const items = await toArray(take(filter(flatten(feedToIterator(feed)), myFn), 60));

I prefer the observables for now since they are easier to read and rxjs is already a dependency of the library.

vencho-mdp commented 2 years ago

Oh, and mergeMap, take, filter are all functions from rxjs?

Nerixyz commented 2 years ago

Oh, and mergeMap, take, filter are all functions from rxjs?

Right all of them are in rxjs/operators.

vencho-mdp commented 2 years ago

I'm getting the following error:

TypeError: iter is not iterable Do you know why it could be? (I'm using the AsyncGenerators solution) @Nerixyz

Nerixyz commented 2 years ago

I guess you're trying to directly pass the feed to flatten/filter/...

You need to "convert" the feed into an async iterater/iterable. So you need to pass it into feedToIterator. If that doesn't help, try to find which object isn't iterable.

vencho-mdp commented 2 years ago

The object which is not iterable is from this function: feedToIterator

Nerixyz commented 2 years ago

Did you put the * after the function keyword?

vencho-mdp commented 2 years ago

Yes, I copy paste the code

Nerixyz commented 2 years ago

I don't see any error. This works for me:

Code (basically copy-pasted from above) ```ts async function* feedToIterator(feed: Feed) { do { const items = await feed.items(); yield items; } while(feed.isMoreAvailable()); } async function* flatten(iter: AsyncIterable) { for await(const items of iter) { yield* items; } } async function* filter(iter: AsyncIterable, fn: (item: T) => boolean) { for await(const item of iter) { if(fn(item)) yield item; } } async function *take(iter: AsyncIterable, n: number) { if(n <= 0) return; let i = 0; for await(const item of iter) { yield item; i++; if(i >= n) break; } } async function toArray(iter: AsyncIterable) { const arr = []; for await(const item of iter) { arr.push(item); } return arr; } const feed = ig.feed.user(await ig.user.getIdByUsername('instagram')); const items = await toArray(take(filter(flatten(feedToIterator(feed)), item => !!item.carousel_media), 10)); console.log(items); ```
vencho-mdp commented 2 years ago

Maybe it's because I'm using JS and I changed it wrongly. Could you provide a javascript Version?

Nerixyz commented 2 years ago
Javascript version ```js async function* feedToIterator(feed) { do { const items = await feed.items(); yield items; } while(feed.isMoreAvailable()); } async function* flatten(iter) { for await(const items of iter) { yield* items; } } async function* filter(iter, fn) { for await(const item of iter) { if(fn(item)) yield item; } } async function *take(iter, n) { if(n <= 0) return; let i = 0; for await(const item of iter) { yield item; i++; if(i >= n) break; } } async function toArray(iter) { const arr = []; for await(const item of iter) { arr.push(item); } return arr; } const feed = ig.feed.user(await ig.user.getIdByUsername('instagram')); const items = await toArray(take(filter(flatten(feedToIterator(feed)), item => !!item.carousel_media), 10)); console.log(items); ```