Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Elixir's Stream for API endpoints that require pagination #359

Open
sinni800 opened this issue Nov 7, 2021 · 1 comment
Open

Using Elixir's Stream for API endpoints that require pagination #359

sinni800 opened this issue Nov 7, 2021 · 1 comment

Comments

@sinni800
Copy link

sinni800 commented Nov 7, 2021

I have tried to get all reactions to a message, but Api.get_reactions only returns the first 25.

So I thought, yeah, this needs pagination.

I have implemented a way to get all reactions to a message and have used Elixir's own Streams for this.

Here's my example code, it's a dirty "proof of concept" that is copypasted together a little, but here:

defmodule Bot.Library.ReactionsStream do
    require Logger

    def handle_request_with_decode(response)
    def handle_request_with_decode({:ok, body}), do: {:ok, Jason.decode!(body, keys: :atoms)}
    def handle_request_with_decode({:error, _} = error), do: error
  
    def handle_request_with_decode(response, type)
    # add_guild_member/3 can return both a 201 and a 204
    def handle_request_with_decode({:ok}, _type), do: {:ok}
    def handle_request_with_decode({:error, _} = error, _type), do: error
  
    def handle_request_with_decode({:ok, body}, type) do
      convert =
        body
        |> Jason.decode!(keys: :atoms)
        |> Nostrum.Util.cast(type)
  
      {:ok, convert}
    end

    def request(channel_id, message_id, emoji, after_id \\ 0) do
        
        params = 
            case after_id do
                   0 -> [limit: 100]
                   num -> [after: num, limit: 100]
            end
            
            
        route = Nostrum.Constants.channel_reactions_get(channel_id, message_id, emoji)
        Nostrum.Api.request(:get, route, "", params: params)
        |> handle_request_with_decode
    end

    defp start(channel_id, message_id, emoji) do
        fn -> 
            {[], channel_id, message_id, emoji, 0} 
        end
    end

    defp next_item({items, channel_id, message_id, emoji, after_id}) when length(items) == 0 do
        {:ok, list} = request(channel_id, message_id, emoji, after_id)
        Logger.info("fetched new items" <> inspect length(list))
        case Enum.count(list) do
            0 -> {:halt, {[], channel_id, message_id, emoji, after_id}}
            _ -> next_item({list, channel_id, message_id, emoji, List.last(list).id})
        end
    end

    defp next_item({items, channel_id, message_id, emoji, after_id}) when length(items) > 0 do
        {item, items} = List.pop_at(items, 0)
        {[item], {items, channel_id, message_id, emoji, after_id}}
    end

    defp stop(_acc) do 

    end

    def get_reactions_stream(channel_id, message_id, emoji) do
        Stream.resource(start(channel_id, message_id, emoji), &next_item/1, &stop/1)
    end
end

Usage is like this, for example:

Bot.Library.ReactionsStream.get_reactions_stream(channel_id, message_id, "emoji") |> Enum.take(10)

Should we add enumerable streams to the API package for paginated queries?

@jchristgit
Copy link
Collaborator

I like this idea a lot.

@Kraigie @jb3 what are your thoughts on this?

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

No branches or pull requests

3 participants