1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-10-31 20:14:21 +00:00

API: fix errors in message pagination and streamline changes with other pagination methods (#150)

* API: fix faulty pagination behavior

This fix fixes a condition which lead to all messages getting fetched if the limit was a multiple of 100, instead of just the limit.

* API: add NewestMessages

* API: clarify MessageAfter docs

* API: adapt paginating methods for guild, member and message reaction to match the style of message's pagination methods

* API: return nil if no items were fetched

* API: remove Messages and Rename NewestMessages to Messages
This commit is contained in:
Maximilian von Lindern 2020-10-19 16:47:43 +02:00 committed by GitHub
parent dec39c4c2d
commit 397d288927
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 53 deletions

View file

@ -9,6 +9,10 @@ import (
"github.com/diamondburned/arikawa/utils/json/option" "github.com/diamondburned/arikawa/utils/json/option"
) )
// maxGuildFetchLimit is the limit of max guilds per request, as imposed by
// Discord.
const maxGuildFetchLimit = 100
var EndpointGuilds = Endpoint + "guilds/" var EndpointGuilds = Endpoint + "guilds/"
// https://discordapp.com/developers/docs/resources/guild#create-guild-json-params // https://discordapp.com/developers/docs/resources/guild#create-guild-json-params
@ -105,8 +109,7 @@ func (c *Client) GuildWithCount(id discord.GuildID) (*discord.Guild, error) {
// Guilds returns a list of partial guild objects the current user is a member // Guilds returns a list of partial guild objects the current user is a member
// of. This method automatically paginates until it reaches the passed limit, // of. This method automatically paginates until it reaches the passed limit,
// or, if the limit is set to 0, has fetched all guilds within the passed // or, if the limit is set to 0, has fetched all guilds the user has joined.
// range.
// //
// As the underlying endpoint has a maximum of 100 guilds per request, at // As the underlying endpoint has a maximum of 100 guilds per request, at
// maximum a total of limit/100 rounded up requests will be made, although they // maximum a total of limit/100 rounded up requests will be made, although they
@ -125,8 +128,8 @@ func (c *Client) Guilds(limit uint) ([]discord.Guild, error) {
// GuildsBefore returns a list of partial guild objects the current user is a // GuildsBefore returns a list of partial guild objects the current user is a
// member of. This method automatically paginates until it reaches the // member of. This method automatically paginates until it reaches the
// passed limit, or, if the limit is set to 0, has fetched all guilds within // passed limit, or, if the limit is set to 0, has fetched all guilds with an
// the passed range. // id smaller than before.
// //
// As the underlying endpoint has a maximum of 100 guilds per request, at // As the underlying endpoint has a maximum of 100 guilds per request, at
// maximum a total of limit/100 rounded up requests will be made, although they // maximum a total of limit/100 rounded up requests will be made, although they
@ -134,15 +137,16 @@ func (c *Client) Guilds(limit uint) ([]discord.Guild, error) {
// //
// Requires the guilds OAuth2 scope. // Requires the guilds OAuth2 scope.
func (c *Client) GuildsBefore(before discord.GuildID, limit uint) ([]discord.Guild, error) { func (c *Client) GuildsBefore(before discord.GuildID, limit uint) ([]discord.Guild, error) {
var guilds []discord.Guild guilds := make([]discord.Guild, 0, limit)
// this is the limit of max guilds per request,as imposed by Discord fetch := uint(maxGuildFetchLimit)
const hardLimit int = 100
unlimited := limit == 0 unlimited := limit == 0
for fetch := uint(hardLimit); limit > 0 || unlimited; fetch = uint(hardLimit) { for limit > 0 || unlimited {
if limit > 0 { if limit > 0 {
// Only fetch as much as we need. Since limit gradually decreases,
// we only need to fetch min(fetch, limit).
if fetch > limit { if fetch > limit {
fetch = limit fetch = limit
} }
@ -155,20 +159,24 @@ func (c *Client) GuildsBefore(before discord.GuildID, limit uint) ([]discord.Gui
} }
guilds = append(g, guilds...) guilds = append(g, guilds...)
if len(g) < hardLimit { if len(g) < maxGuildFetchLimit {
break break
} }
before = g[0].ID before = g[0].ID
} }
if len(guilds) == 0 {
return nil, nil
}
return guilds, nil return guilds, nil
} }
// GuildsAfter returns a list of partial guild objects the current user is a // GuildsAfter returns a list of partial guild objects the current user is a
// member of. This method automatically paginates until it reaches the // member of. This method automatically paginates until it reaches the
// passed limit, or, if the limit is set to 0, has fetched all guilds within // passed limit, or, if the limit is set to 0, has fetched all guilds with an
// the passed range. // id higher than after.
// //
// As the underlying endpoint has a maximum of 100 guilds per request, at // As the underlying endpoint has a maximum of 100 guilds per request, at
// maximum a total of limit/100 rounded up requests will be made, although they // maximum a total of limit/100 rounded up requests will be made, although they
@ -176,14 +184,15 @@ func (c *Client) GuildsBefore(before discord.GuildID, limit uint) ([]discord.Gui
// //
// Requires the guilds OAuth2 scope. // Requires the guilds OAuth2 scope.
func (c *Client) GuildsAfter(after discord.GuildID, limit uint) ([]discord.Guild, error) { func (c *Client) GuildsAfter(after discord.GuildID, limit uint) ([]discord.Guild, error) {
var guilds []discord.Guild guilds := make([]discord.Guild, 0, limit)
// this is the limit of max guilds per request, as imposed by Discord fetch := uint(maxGuildFetchLimit)
const hardLimit int = 100
unlimited := limit == 0 unlimited := limit == 0
for fetch := uint(hardLimit); limit > 0 || unlimited; fetch = uint(hardLimit) { for limit > 0 || unlimited {
// Only fetch as much as we need. Since limit gradually decreases,
// we only need to fetch min(fetch, limit).
if limit > 0 { if limit > 0 {
if fetch > limit { if fetch > limit {
fetch = limit fetch = limit
@ -197,13 +206,17 @@ func (c *Client) GuildsAfter(after discord.GuildID, limit uint) ([]discord.Guild
} }
guilds = append(guilds, g...) guilds = append(guilds, g...)
if len(g) < hardLimit { if len(g) < maxGuildFetchLimit {
break break
} }
after = g[len(g)-1].ID after = g[len(g)-1].ID
} }
if len(guilds) == 0 {
return nil, nil
}
return guilds, nil return guilds, nil
} }

View file

@ -6,7 +6,9 @@ import (
"github.com/diamondburned/arikawa/utils/json/option" "github.com/diamondburned/arikawa/utils/json/option"
) )
// Member returns a guild member object for the specified user.. const maxMemberFetchLimit = 1000
// Member returns a guild member object for the specified user.
func (c *Client) Member(guildID discord.GuildID, userID discord.UserID) (*discord.Member, error) { func (c *Client) Member(guildID discord.GuildID, userID discord.UserID) (*discord.Member, error) {
var m *discord.Member var m *discord.Member
return m, c.RequestJSON(&m, "GET", EndpointGuilds+guildID.String()+"/members/"+userID.String()) return m, c.RequestJSON(&m, "GET", EndpointGuilds+guildID.String()+"/members/"+userID.String())
@ -14,11 +16,11 @@ func (c *Client) Member(guildID discord.GuildID, userID discord.UserID) (*discor
// Members returns a list of members of the guild with the passed id. This // Members returns a list of members of the guild with the passed id. This
// method automatically paginates until it reaches the passed limit, or, if the // method automatically paginates until it reaches the passed limit, or, if the
// limit is set to 0, has fetched all members within the passed range. // limit is set to 0, has fetched all members in the guild.
// //
// As the underlying endpoint has a maximum of 1000 members per request, at // As the underlying endpoint has a maximum of 1000 members per request, at
// maximum a total of limit/1000 rounded up requests will be made, although // maximum a total of limit/1000 rounded up requests will be made, although
// they may be less, if no more members are available. // they may be less if no more members are available.
// //
// When fetching the members, those with the smallest ID will be fetched first. // When fetching the members, those with the smallest ID will be fetched first.
func (c *Client) Members(guildID discord.GuildID, limit uint) ([]discord.Member, error) { func (c *Client) Members(guildID discord.GuildID, limit uint) ([]discord.Member, error) {
@ -27,7 +29,7 @@ func (c *Client) Members(guildID discord.GuildID, limit uint) ([]discord.Member,
// MembersAfter returns a list of members of the guild with the passed id. This // MembersAfter returns a list of members of the guild with the passed id. This
// method automatically paginates until it reaches the passed limit, or, if the // method automatically paginates until it reaches the passed limit, or, if the
// limit is set to 0, has fetched all members within the passed range. // limit is set to 0, has fetched all members with an id higher than after.
// //
// As the underlying endpoint has a maximum of 1000 members per request, at // As the underlying endpoint has a maximum of 1000 members per request, at
// maximum a total of limit/1000 rounded up requests will be made, although // maximum a total of limit/1000 rounded up requests will be made, although
@ -35,13 +37,15 @@ func (c *Client) Members(guildID discord.GuildID, limit uint) ([]discord.Member,
func (c *Client) MembersAfter( func (c *Client) MembersAfter(
guildID discord.GuildID, after discord.UserID, limit uint) ([]discord.Member, error) { guildID discord.GuildID, after discord.UserID, limit uint) ([]discord.Member, error) {
var mems []discord.Member mems := make([]discord.Member, 0, limit)
const hardLimit int = 1000 fetch := uint(maxMemberFetchLimit)
unlimited := limit == 0 unlimited := limit == 0
for fetch := uint(hardLimit); limit > 0 || unlimited; fetch = uint(hardLimit) { for limit > 0 || unlimited {
// Only fetch as much as we need. Since limit gradually decreases,
// we only need to fetch min(fetch, limit).
if limit > 0 { if limit > 0 {
if fetch > limit { if fetch > limit {
fetch = limit fetch = limit
@ -56,13 +60,17 @@ func (c *Client) MembersAfter(
mems = append(mems, m...) mems = append(mems, m...)
// There aren't any to fetch, even if this is less than limit. // There aren't any to fetch, even if this is less than limit.
if len(m) < hardLimit { if len(m) < maxMemberFetchLimit {
break break
} }
after = mems[len(mems)-1].User.ID after = mems[len(mems)-1].User.ID
} }
if len(mems) == 0 {
return nil, nil
}
return mems, nil return mems, nil
} }

View file

@ -11,18 +11,23 @@ import (
// the limit of max messages per request, as imposed by Discord // the limit of max messages per request, as imposed by Discord
const maxMessageFetchLimit = 100 const maxMessageFetchLimit = 100
// Messages returns a list of messages sent in the channel with the passed ID. // Messages returns a slice filled with the most recent messages sent in the
// This method automatically paginates until it reaches the passed limit, or, // channel with the passed ID. The method automatically paginates until it
// if the limit is set to 0, has fetched all guilds within the passed ange. // reaches the passed limit, or, if the limit is set to 0, has fetched all
// messages in the channel.
// //
// As the underlying endpoint has a maximum of 100 messages per request, at // As the underlying endpoint is capped at a maximum of 100 messages per
// maximum a total of limit/100 rounded up requests will be made, although they // request, at maximum a total of limit/100 rounded up requests will be made,
// may be less, if no more messages are available. // although they may be less, if no more messages are available.
// //
// When fetching the messages, those with the smallest ID will be fetched // When fetching the messages, those with the highest ID, will be fetched
// first. // first.
// The returned slice will be sorted from latest to oldest.
func (c *Client) Messages(channelID discord.ChannelID, limit uint) ([]discord.Message, error) { func (c *Client) Messages(channelID discord.ChannelID, limit uint) ([]discord.Message, error) {
return c.MessagesAfter(channelID, 0, limit) // Since before is 0 it will be omitted by the http lib, which in turn
// will lead discord to send us the most recent messages without having to
// specify a Snowflake.
return c.MessagesBefore(channelID, 0, limit)
} }
// MessagesAround returns messages around the ID, with a limit of 100. // MessagesAround returns messages around the ID, with a limit of 100.
@ -32,22 +37,28 @@ func (c *Client) MessagesAround(
return c.messagesRange(channelID, 0, 0, around, limit) return c.messagesRange(channelID, 0, 0, around, limit)
} }
// MessagesBefore returns a list messages sent in the channel with the passed // MessagesBefore returns a slice filled with the messages sent in the channel
// ID. This method automatically paginates until it reaches the passed limit, // with the passed id. The method automatically paginates until it reaches the
// or, if the limit is set to 0, has fetched all guilds within the passed // passed limit, or, if the limit is set to 0, has fetched all messages in the
// range. // channel with an id smaller than before.
// //
// As the underlying endpoint has a maximum of 100 messages per request, at // As the underlying endpoint has a maximum of 100 messages per request, at
// maximum a total of limit/100 rounded up requests will be made, although they // maximum a total of limit/100 rounded up requests will be made, although they
// may be less, if no more messages are available. // may be less, if no more messages are available.
//
// The returned slice will be sorted from latest to oldest.
func (c *Client) MessagesBefore( func (c *Client) MessagesBefore(
channelID discord.ChannelID, before discord.MessageID, limit uint) ([]discord.Message, error) { channelID discord.ChannelID, before discord.MessageID, limit uint) ([]discord.Message, error) {
var msgs []discord.Message msgs := make([]discord.Message, 0, limit)
fetch := uint(maxMessageFetchLimit) fetch := uint(maxMessageFetchLimit)
for limit >= 0 { // Check if we are truly fetching unlimited messages to avoid confusion
// later on, if the limit reaches 0.
unlimited := limit == 0
for limit > 0 || unlimited {
if limit > 0 { if limit > 0 {
// Only fetch as much as we need. Since limit gradually decreases, // Only fetch as much as we need. Since limit gradually decreases,
// we only need to fetch min(fetch, limit). // we only need to fetch min(fetch, limit).
@ -71,25 +82,42 @@ func (c *Client) MessagesBefore(
before = m[len(m)-1].ID before = m[len(m)-1].ID
} }
if len(msgs) == 0 {
return nil, nil
}
return msgs, nil return msgs, nil
} }
// MessagesAfter returns a list messages sent in the channel with the passed // MessagesAfter returns a slice filled with the messages sent in the channel
// ID. This method automatically paginates until it reaches the passed limit, // with the passed ID. The method automatically paginates until it reaches the
// or, if the limit is set to 0, has fetched all guilds within the passed // passed limit, or, if the limit is set to 0, has fetched all messages in the
// range. // channel with an id higher than after.
// //
// As the underlying endpoint has a maximum of 100 messages per request, at // As the underlying endpoint has a maximum of 100 messages per request, at
// maximum a total of limit/100 rounded up requests will be made, although they // maximum a total of limit/100 rounded up requests will be made, although they
// may be less, if no more messages are available. // may be less, if no more messages are available.
//
// The returned slice will be sorted from latest to oldest.
func (c *Client) MessagesAfter( func (c *Client) MessagesAfter(
channelID discord.ChannelID, after discord.MessageID, limit uint) ([]discord.Message, error) { channelID discord.ChannelID, after discord.MessageID, limit uint) ([]discord.Message, error) {
// 0 is uint's zero value and will lead to the after param getting omitted,
// which in turn will lead to the most recent messages being returned.
// Setting this to 1 will prevent that.
if after == 0 {
after = 1
}
var msgs []discord.Message var msgs []discord.Message
fetch := uint(maxMessageFetchLimit) fetch := uint(maxMessageFetchLimit)
for limit >= 0 { // Check if we are truly fetching unlimited messages to avoid confusion
// later on, if the limit reaches 0.
unlimited := limit == 0
for limit > 0 || unlimited {
if limit > 0 { if limit > 0 {
// Only fetch as much as we need. Since limit gradually decreases, // Only fetch as much as we need. Since limit gradually decreases,
// we only need to fetch min(fetch, limit). // we only need to fetch min(fetch, limit).
@ -113,6 +141,10 @@ func (c *Client) MessagesAfter(
after = m[0].ID after = m[0].ID
} }
if len(msgs) == 0 {
return nil, nil
}
return msgs, nil return msgs, nil
} }

View file

@ -7,6 +7,8 @@ import (
"github.com/diamondburned/arikawa/utils/httputil" "github.com/diamondburned/arikawa/utils/httputil"
) )
const maxMessageReactionFetchLimit = 100
// React creates a reaction for the message. // React creates a reaction for the message.
// //
// This endpoint requires the READ_MESSAGE_HISTORY permission to be present on // This endpoint requires the READ_MESSAGE_HISTORY permission to be present on
@ -42,7 +44,8 @@ func (c *Client) Reactions(
// ReactionsBefore returns a list of users that reacted with the passed Emoji. // ReactionsBefore returns a list of users that reacted with the passed Emoji.
// This method automatically paginates until it reaches the passed limit, or, // This method automatically paginates until it reaches the passed limit, or,
// if the limit is set to 0, has fetched all users within the passed range. // if the limit is set to 0, has fetched all users with an id smaller than
// before.
// //
// As the underlying endpoint has a maximum of 100 users per request, at // As the underlying endpoint has a maximum of 100 users per request, at
// maximum a total of limit/100 rounded up requests will be made, although they // maximum a total of limit/100 rounded up requests will be made, although they
@ -51,13 +54,15 @@ func (c *Client) ReactionsBefore(
channelID discord.ChannelID, messageID discord.MessageID, before discord.UserID, emoji Emoji, channelID discord.ChannelID, messageID discord.MessageID, before discord.UserID, emoji Emoji,
limit uint) ([]discord.User, error) { limit uint) ([]discord.User, error) {
var users []discord.User users := make([]discord.User, 0, limit)
const hardLimit int = 100 fetch := uint(maxMessageReactionFetchLimit)
unlimited := limit == 0 unlimited := limit == 0
for fetch := uint(hardLimit); limit > 0 || unlimited; fetch = uint(hardLimit) { for limit > 0 || unlimited {
// Only fetch as much as we need. Since limit gradually decreases,
// we only need to fetch min(fetch, limit).
if limit > 0 { if limit > 0 {
if fetch > limit { if fetch > limit {
fetch = limit fetch = limit
@ -71,19 +76,24 @@ func (c *Client) ReactionsBefore(
} }
users = append(r, users...) users = append(r, users...)
if len(r) < hardLimit { if len(r) < maxMessageReactionFetchLimit {
break break
} }
before = r[0].ID before = r[0].ID
} }
if len(users) == 0 {
return nil, nil
}
return users, nil return users, nil
} }
// ReactionsAfter returns a list of users that reacted with the passed Emoji. // ReactionsAfter returns a list of users that reacted with the passed Emoji.
// This method automatically paginates until it reaches the passed limit, or, // This method automatically paginates until it reaches the passed limit, or,
// if the limit is set to 0, has fetched all users within the passed range. // if the limit is set to 0, has fetched all users with an id higher than
// after.
// //
// As the underlying endpoint has a maximum of 100 users per request, at // As the underlying endpoint has a maximum of 100 users per request, at
// maximum a total of limit/100 rounded up requests will be made, although they // maximum a total of limit/100 rounded up requests will be made, although they
@ -92,13 +102,15 @@ func (c *Client) ReactionsAfter(
channelID discord.ChannelID, messageID discord.MessageID, after discord.UserID, emoji Emoji, channelID discord.ChannelID, messageID discord.MessageID, after discord.UserID, emoji Emoji,
limit uint) ([]discord.User, error) { limit uint) ([]discord.User, error) {
var users []discord.User users := make([]discord.User, 0, limit)
const hardLimit int = 100 fetch := uint(maxMessageReactionFetchLimit)
unlimited := limit == 0 unlimited := limit == 0
for fetch := uint(hardLimit); limit > 0 || unlimited; fetch = uint(hardLimit) { for limit > 0 || unlimited {
// Only fetch as much as we need. Since limit gradually decreases,
// we only need to fetch min(fetch, limit).
if limit > 0 { if limit > 0 {
if fetch > limit { if fetch > limit {
fetch = limit fetch = limit
@ -112,13 +124,17 @@ func (c *Client) ReactionsAfter(
} }
users = append(users, r...) users = append(users, r...)
if len(r) < hardLimit { if len(r) < maxMessageReactionFetchLimit {
break break
} }
after = r[len(r)-1].ID after = r[len(r)-1].ID
} }
if len(users) == 0 {
return nil, nil
}
return users, nil return users, nil
} }