From a6bbc4648b07d94b998cc3d3d54888132d757c25 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Wed, 12 Jan 2022 21:34:52 +0100 Subject: [PATCH 01/23] Fix AuditLogGetRequestBuilder docs --- .../builder/auditlog/AuditLogGetRequestBuilder.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt b/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt index 067724d20e1a..80e3250082b8 100644 --- a/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt +++ b/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt @@ -8,12 +8,12 @@ import dev.kord.rest.json.request.AuditLogGetRequest class AuditLogGetRequestBuilder : RequestBuilder { /** - * The id of the user whose actions should be filtered for. `null` be default. + * The id of the user whose actions should be filtered for. `null` by default. */ var userId: Snowflake? = null /** - * The type of [AuditLogEvent] which should be filtered for. `null` be default. + * The type of [AuditLogEvent] which should be filtered for. `null` by default. */ var action: AuditLogEvent? = null @@ -23,9 +23,12 @@ class AuditLogGetRequestBuilder : RequestBuilder { var before: Snowflake? = null /** - * How many entries are returned (default 50, minimum 1, maximum 100). + * How many entries are returned (default 50, minimum 1, maximum 100) + * ([see Discord's docs](https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log)). + * + * Set to `100` by default. */ var limit: Int = 100 override fun toRequest(): AuditLogGetRequest = AuditLogGetRequest(userId, action, before, limit) -} \ No newline at end of file +} From f14ee099aa99fb0c14f33e67c4ec10192de34a99 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 17:35:01 +0100 Subject: [PATCH 02/23] AuditLogGetRequestBuilder.limit is 50 by default --- .../kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt b/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt index 80e3250082b8..f676def19a98 100644 --- a/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt +++ b/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt @@ -23,12 +23,9 @@ class AuditLogGetRequestBuilder : RequestBuilder { var before: Snowflake? = null /** - * How many entries are returned (default 50, minimum 1, maximum 100) - * ([see Discord's docs](https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log)). - * - * Set to `100` by default. + * How many entries are returned (default 50, minimum 1, maximum 100). */ - var limit: Int = 100 + var limit: Int = 50 override fun toRequest(): AuditLogGetRequest = AuditLogGetRequest(userId, action, before, limit) } From 882174026906d4a27fb89ac662bba080558a92c6 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 17:47:01 +0100 Subject: [PATCH 03/23] Better types in pagination --- core/src/main/kotlin/Util.kt | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index d75a5016d263..67ba9d4f6231 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -120,13 +120,13 @@ internal suspend fun Flow.indexOfFirstOrNull(predicate: suspend (T) -> Bo .singleOrNull()?.first } -internal fun , T> paginate( +internal fun , T, P : Position> paginate( start: Snowflake, batchSize: Int, idSelector: (T) -> Snowflake, - itemSelector: (Collection) -> T?, - directionSelector: (Snowflake) -> Position, - request: suspend (position: Position) -> C, + itemSelector: (C) -> T?, + directionSelector: (Snowflake) -> P, + request: suspend (position: P) -> C, ): Flow = flow { var position = directionSelector(start) var size = batchSize @@ -180,42 +180,42 @@ internal fun oldestItem(idSelector: (T) -> Snowflake): (Collection) -> T? /** * Selects the [Position.After] the youngest item in the batch. */ -internal fun , T> paginateForwards( - start: Snowflake = Snowflake("0"), +internal fun paginateForwards( + start: Snowflake = Snowflake.min, batchSize: Int, idSelector: (T) -> Snowflake, - request: suspend (position: Position) -> C + request: suspend (after: Position.After) -> Collection, ): Flow = paginate(start, batchSize, idSelector, youngestItem(idSelector), Position::After, request) /** * Selects the [Position.After] the youngest item in the batch. */ -internal fun , T : KordEntity> paginateForwards( - start: Snowflake = Snowflake("0"), +internal fun paginateForwards( + start: Snowflake = Snowflake.min, batchSize: Int, - request: suspend (position: Position) -> C + request: suspend (after: Position.After) -> Collection, ): Flow = paginate(start, batchSize, { it.id }, youngestItem { it.id }, Position::After, request) /** * Selects the [Position.Before] the oldest item in the batch. */ -internal fun , T> paginateBackwards( +internal fun paginateBackwards( start: Snowflake = Snowflake.max, batchSize: Int, idSelector: (T) -> Snowflake, - request: suspend (position: Position) -> C + request: suspend (before: Position.Before) -> Collection, ): Flow = paginate(start, batchSize, idSelector, oldestItem(idSelector), Position::Before, request) /** * Selects the [Position.Before] the oldest item in the batch. */ -internal fun , T : KordEntity> paginateBackwards( +internal fun paginateBackwards( start: Snowflake = Snowflake.max, batchSize: Int, - request: suspend (position: Position) -> C + request: suspend (before: Position.Before) -> Collection, ): Flow = paginate(start, batchSize, { it.id }, oldestItem { it.id }, Position::Before, request) @@ -230,8 +230,8 @@ internal fun , T : KordEntity> paginateBackwards( internal fun , T> paginateByDate( start: Instant = Clock.System.now(), batchSize: Int, - instantSelector: (Collection) -> Instant?, - request: suspend (Instant) -> C + instantSelector: (C) -> Instant?, + request: suspend (Instant) -> C, ): Flow = flow { var currentTimestamp = start From df8ed3865bd7dea90bff8ef2dc96213cff651200 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 18:08:11 +0100 Subject: [PATCH 04/23] Always use service getters in RestEntitySupplier --- .../kotlin/supplier/RestEntitySupplier.kt | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 6f4ceeeaacf8..0e60d9bc3f2c 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -49,8 +49,10 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { private val voice: VoiceService get() = kord.rest.voice private val webhook: WebhookService get() = kord.rest.webhook private val application: ApplicationService get() = kord.rest.application - private val template: TemplateService = kord.rest.template - private val interaction: InteractionService = kord.rest.interaction + private val template: TemplateService get() = kord.rest.template + private val interaction: InteractionService get() = kord.rest.interaction + private val stageInstance: StageInstanceService get() = kord.rest.stageInstance + private val sticker: StickerService get() = kord.rest.sticker override val guilds: Flow get() = paginateForwards( @@ -124,7 +126,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { val batchSize = min(100, limit) val flow = paginateForwards(messageId, batchSize, idSelector = { it.id }) { position -> - kord.rest.channel.getMessages(channelId, position, batchSize) + channel.getMessages(channelId, position, batchSize) }.map { val data = MessageData.from(it) Message(data, kord) @@ -138,7 +140,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { val batchSize = min(100, limit) val flow = paginateBackwards(messageId, batchSize, idSelector = { it.id }) { position -> - kord.rest.channel.getMessages(channelId, position, batchSize) + channel.getMessages(channelId, position, batchSize) }.map { val data = MessageData.from(it) Message(data, kord) @@ -148,7 +150,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getMessagesAround(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = flow { - val responses = kord.rest.channel.getMessages(channelId, Position.Around(messageId)) + val responses = channel.getMessages(channelId, Position.Around(messageId)) for (response in responses) { val data = MessageData.from(response) @@ -190,7 +192,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { val batchSize = min(1000, limit) val flow = paginateForwards(idSelector = { it.user.value!!.id }, batchSize = batchSize) { position -> - kord.rest.guild.getGuildMembers(guildId = guildId, position = position, limit = batchSize) + guild.getGuildMembers(guildId = guildId, position = position, limit = batchSize) }.map { val userData = it.user.value!!.toData() val memberData = it.toData(guildId = guildId, userId = it.user.value!!.id) @@ -212,7 +214,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { public fun getReactors(channelId: Snowflake, messageId: Snowflake, emoji: ReactionEmoji): Flow = paginateForwards(batchSize = 100, idSelector = { it.id }) { position -> - kord.rest.channel.getReactions( + channel.getReactions( channelId = channelId, messageId = messageId, emoji = emoji.urlFormat, @@ -334,21 +336,21 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override suspend fun getStageInstanceOrNull(channelId: Snowflake): StageInstance? = catchNotFound { - val instance = kord.rest.stageInstance.getStageInstance(channelId) + val instance = stageInstance.getStageInstance(channelId) val data = StageInstanceData.from(instance) StageInstance(data, kord, this) } override fun getThreadMembers(channelId: Snowflake): Flow = flow { - kord.rest.channel.listThreadMembers(channelId).onEach { + channel.listThreadMembers(channelId).onEach { val data = ThreadMemberData.from(it) emit(ThreadMember(data, kord)) } } override fun getActiveThreads(guildId: Snowflake): Flow = flow { - kord.rest.guild.listActiveThreads(guildId).threads.onEach { + guild.listActiveThreads(guildId).threads.onEach { val data = ChannelData.from(it) val channel = Channel.from(data, kord) if (channel is ThreadChannel) emit(channel) @@ -361,7 +363,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { val batchSize = min(100, limit) val flow = paginateThreads(100, before) { - kord.rest.channel.listPublicArchivedThreads( + channel.listPublicArchivedThreads( channelId, ListThreadsByTimestampRequest(before, batchSize) ).threads.mapNotNull { @@ -378,7 +380,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { val batchSize = min(100, limit) val flow = paginateThreads(100, before) { - kord.rest.channel.listPrivateArchivedThreads( + channel.listPrivateArchivedThreads( channelId, ListThreadsByTimestampRequest(before, batchSize) ).threads.mapNotNull { @@ -399,7 +401,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { val batchSize = min(100, limit) val flow = paginateBackwards(batchSize = batchSize, idSelector = { it.id }) { - kord.rest.channel.listJoinedPrivateArchivedThreads( + channel.listJoinedPrivateArchivedThreads( channelId, ListThreadsBySnowflakeRequest(before, batchSize) ).threads.mapNotNull { @@ -460,7 +462,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getGuildScheduledEvents(guildId: Snowflake): Flow = flow { - kord.rest.guild.listScheduledEvents(guildId).forEach { + guild.listScheduledEvents(guildId).forEach { val data = GuildScheduledEventData.from(it) emit(GuildScheduledEvent(data, kord)) @@ -469,26 +471,26 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { override suspend fun getGuildScheduledEventOrNull(guildId: Snowflake, eventId: Snowflake): GuildScheduledEvent? = catchNotFound { - val event = kord.rest.guild.getScheduledEvent(guildId, eventId) + val event = guild.getScheduledEvent(guildId, eventId) val data = GuildScheduledEventData.from(event) GuildScheduledEvent(data, kord) } override suspend fun getStickerOrNull(id: Snowflake): Sticker? = catchNotFound { - val response = kord.rest.sticker.getSticker(id) + val response = sticker.getSticker(id) val data = StickerData.from(response) Sticker(data, kord) } override suspend fun getGuildStickerOrNull(guildId: Snowflake, id: Snowflake): GuildSticker? = catchNotFound { - val response = kord.rest.sticker.getGuildSticker(guildId, id) + val response = sticker.getGuildSticker(guildId, id) val data = StickerData.from(response) GuildSticker(data, kord) } override fun getNitroStickerPacks(): Flow = flow { - val responses = kord.rest.sticker.getNitroStickerPacks() + val responses = sticker.getNitroStickerPacks() responses.forEach { response -> val data = StickerPackData.from(response) @@ -497,7 +499,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getGuildStickers(guildId: Snowflake): Flow = flow { - val responses = kord.rest.sticker.getGuildStickers(guildId) + val responses = sticker.getGuildStickers(guildId) responses.forEach { response -> val data = StickerData.from(response) From 724931c247d70796a025a38226075213bc39e5c4 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 18:23:33 +0100 Subject: [PATCH 05/23] Default limit for getCurrentUserGuilds, see https://discord.com/developers/docs/resources/user#get-current-user-guilds --- rest/src/main/kotlin/service/UserService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest/src/main/kotlin/service/UserService.kt b/rest/src/main/kotlin/service/UserService.kt index 9e0a1670846f..0bdfb0c2908b 100644 --- a/rest/src/main/kotlin/service/UserService.kt +++ b/rest/src/main/kotlin/service/UserService.kt @@ -22,7 +22,7 @@ class UserService(requestHandler: RequestHandler) : RestService(requestHandler) keys[Route.UserId] = userId } - suspend fun getCurrentUserGuilds(position: Position? = null, limit: Int = 100) = call(Route.CurrentUsersGuildsGet) { + suspend fun getCurrentUserGuilds(position: Position? = null, limit: Int = 200) = call(Route.CurrentUsersGuildsGet) { if (position != null) { parameter(position.key, position.value) } From 30f5546fb24afca6b83499a1a7723a28f324a762 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 18:26:50 +0100 Subject: [PATCH 06/23] Add checkLimitAndGetBatchSize function --- .../kotlin/supplier/RestEntitySupplier.kt | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 0e60d9bc3f2c..ed42a161ab2b 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -122,8 +122,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - val batchSize = min(100, limit) + // max: see https://discord.com/developers/docs/resources/channel#get-channel-messages + val batchSize = checkLimitAndGetBatchSize(limit, max = 100) val flow = paginateForwards(messageId, batchSize, idSelector = { it.id }) { position -> channel.getMessages(channelId, position, batchSize) @@ -136,8 +136,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - val batchSize = min(100, limit) + // max: see https://discord.com/developers/docs/resources/channel#get-channel-messages + val batchSize = checkLimitAndGetBatchSize(limit, max = 100) val flow = paginateBackwards(messageId, batchSize, idSelector = { it.id }) { position -> channel.getMessages(channelId, position, batchSize) @@ -188,8 +188,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - val batchSize = min(1000, limit) + // max: see https://discord.com/developers/docs/resources/guild#list-guild-members + val batchSize = checkLimitAndGetBatchSize(limit, max = 1000) val flow = paginateForwards(idSelector = { it.user.value!!.id }, batchSize = batchSize) { position -> guild.getGuildMembers(guildId = guildId, position = position, limit = batchSize) @@ -239,8 +239,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getCurrentUserGuilds(limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - val batchSize = min(100, limit) + // max: see https://discord.com/developers/docs/resources/user#get-current-user-guilds + val batchSize = checkLimitAndGetBatchSize(limit, max = 200) val flow = paginateForwards(batchSize = batchSize, idSelector = { it.id }) { position -> user.getCurrentUserGuilds(position, batchSize).map { Guild(guild.getGuild(it.id).toData(), kord) } @@ -358,9 +358,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - - val batchSize = min(100, limit) + // no max documented: see https://discord.com/developers/docs/resources/channel#list-public-archived-threads + val batchSize = checkLimitAndGetBatchSize(limit, max = 100) val flow = paginateThreads(100, before) { channel.listPublicArchivedThreads( @@ -375,9 +374,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - - val batchSize = min(100, limit) + // no max documented: see https://discord.com/developers/docs/resources/channel#list-private-archived-threads + val batchSize = checkLimitAndGetBatchSize(limit, max = 100) val flow = paginateThreads(100, before) { channel.listPrivateArchivedThreads( @@ -396,9 +394,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { before: Snowflake, limit: Int ): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - - val batchSize = min(100, limit) + // no max documented: see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads + val batchSize = checkLimitAndGetBatchSize(limit, max = 100) val flow = paginateBackwards(batchSize = batchSize, idSelector = { it.id }) { channel.listJoinedPrivateArchivedThreads( @@ -532,5 +529,10 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { override fun toString(): String { return "RestEntitySupplier(rest=${kord.rest})" } +} + +private fun checkLimitAndGetBatchSize(limit: Int, max: Int): Int { + require(limit > 0) { "At least 1 item should be requested, but got $limit." } + return min(limit, max) } From fb8bba8ddafd970860943887d5494db9b9d32cd7 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 18:35:21 +0100 Subject: [PATCH 07/23] Add limitPagination function --- .../kotlin/supplier/RestEntitySupplier.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index ed42a161ab2b..606dbf451b1d 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -132,7 +132,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Message(data, kord) } - return if (limit != Int.MAX_VALUE) flow.take(limit) else flow + return flow.limitPagination(limit) } override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { @@ -146,7 +146,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Message(data, kord) } - return if (limit != Int.MAX_VALUE) flow.take(limit) else flow + return flow.limitPagination(limit) } override fun getMessagesAround(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = flow { @@ -199,9 +199,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Member(memberData, userData, kord) } - - return if (limit != Int.MAX_VALUE) flow.take(limit) - else flow + return flow.limitPagination(limit) } @@ -246,8 +244,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { user.getCurrentUserGuilds(position, batchSize).map { Guild(guild.getGuild(it.id).toData(), kord) } } - return if (limit != Int.MAX_VALUE) flow.take(limit) - else flow + return flow.limitPagination(limit) } override fun getChannelWebhooks(channelId: Snowflake): Flow = flow { @@ -370,7 +367,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Channel.from(data, kord) as? ThreadChannel } } - return if (limit != Int.MAX_VALUE) flow.take(limit) else flow + + return flow.limitPagination(limit) } override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { @@ -386,7 +384,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Channel.from(data, kord) as? ThreadChannel } } - return if (limit != Int.MAX_VALUE) flow.take(limit) else flow + + return flow.limitPagination(limit) } override fun getJoinedPrivateArchivedThreads( @@ -406,7 +405,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Channel.from(data, kord) as? ThreadChannel } } - return if (limit != Int.MAX_VALUE) flow.take(limit) else flow + + return flow.limitPagination(limit) } override fun getGuildApplicationCommands( @@ -536,3 +536,5 @@ private fun checkLimitAndGetBatchSize(limit: Int, max: Int): Int { require(limit > 0) { "At least 1 item should be requested, but got $limit." } return min(limit, max) } + +private fun Flow.limitPagination(limit: Int): Flow = if (limit == Int.MAX_VALUE) this else take(limit) From 4a465e35eff6a73ce52fba66cd0b09de984a7876 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 18:37:10 +0100 Subject: [PATCH 08/23] RestEntitySupplier.getMessagesAround did not use limit parameter --- core/src/main/kotlin/supplier/RestEntitySupplier.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 606dbf451b1d..3dd53683954e 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -150,7 +150,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } override fun getMessagesAround(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = flow { - val responses = channel.getMessages(channelId, Position.Around(messageId)) + val responses = channel.getMessages(channelId, Position.Around(messageId), limit) for (response in responses) { val data = MessageData.from(response) From 5b421ff6ad5ae486122377e88aca6115549d692b Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 18:39:13 +0100 Subject: [PATCH 09/23] Max limit/batchSize for RestEntitySupplier.guilds, see https://discord.com/developers/docs/resources/user#get-current-user-guilds --- core/src/main/kotlin/supplier/RestEntitySupplier.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 3dd53683954e..9c46fe4af75c 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -57,8 +57,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { override val guilds: Flow get() = paginateForwards( idSelector = DiscordPartialGuild::id, - batchSize = 100 - ) { position -> user.getCurrentUserGuilds(position, 100) } + batchSize = 200 // max batchSize/limit: see https://discord.com/developers/docs/resources/user#get-current-user-guilds + ) { position -> user.getCurrentUserGuilds(position, limit = 200) } .map { val guild = guild.getGuild(it.id) val data = GuildData.from(guild) From 9c5ff5dc43e7c468b1cabc3cb4c6d0022c997477 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 19:08:25 +0100 Subject: [PATCH 10/23] Use more specific position types where possible --- .../kotlin/supplier/RestEntitySupplier.kt | 42 +++++++++---------- .../src/main/kotlin/service/ChannelService.kt | 6 +-- rest/src/main/kotlin/service/GuildService.kt | 6 +-- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 9c46fe4af75c..5aeb8f6155b7 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -58,12 +58,12 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { get() = paginateForwards( idSelector = DiscordPartialGuild::id, batchSize = 200 // max batchSize/limit: see https://discord.com/developers/docs/resources/user#get-current-user-guilds - ) { position -> user.getCurrentUserGuilds(position, limit = 200) } - .map { - val guild = guild.getGuild(it.id) - val data = GuildData.from(guild) - Guild(data, kord) - } + ) { after -> + user.getCurrentUserGuilds(position = after, limit = 200) + }.map { + val data = guild.getGuild(it.id).toData() + Guild(data, kord) + } override val regions: Flow get() = flow { @@ -125,8 +125,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // max: see https://discord.com/developers/docs/resources/channel#get-channel-messages val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - val flow = paginateForwards(messageId, batchSize, idSelector = { it.id }) { position -> - channel.getMessages(channelId, position, batchSize) + val flow = paginateForwards(start = messageId, batchSize, idSelector = { it.id }) { after -> + channel.getMessages(channelId, position = after, limit = batchSize) }.map { val data = MessageData.from(it) Message(data, kord) @@ -139,8 +139,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // max: see https://discord.com/developers/docs/resources/channel#get-channel-messages val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - val flow = paginateBackwards(messageId, batchSize, idSelector = { it.id }) { position -> - channel.getMessages(channelId, position, batchSize) + val flow = paginateBackwards(start = messageId, batchSize, idSelector = { it.id }) { before -> + channel.getMessages(channelId, position = before, limit = batchSize) }.map { val data = MessageData.from(it) Message(data, kord) @@ -191,8 +191,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // max: see https://discord.com/developers/docs/resources/guild#list-guild-members val batchSize = checkLimitAndGetBatchSize(limit, max = 1000) - val flow = paginateForwards(idSelector = { it.user.value!!.id }, batchSize = batchSize) { position -> - guild.getGuildMembers(guildId = guildId, position = position, limit = batchSize) + val flow = paginateForwards(idSelector = { it.user.value!!.id }, batchSize = batchSize) { after -> + guild.getGuildMembers(guildId, after, limit = batchSize) }.map { val userData = it.user.value!!.toData() val memberData = it.toData(guildId = guildId, userId = it.user.value!!.id) @@ -211,14 +211,9 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } public fun getReactors(channelId: Snowflake, messageId: Snowflake, emoji: ReactionEmoji): Flow = - paginateForwards(batchSize = 100, idSelector = { it.id }) { position -> - channel.getReactions( - channelId = channelId, - messageId = messageId, - emoji = emoji.urlFormat, - limit = 100, - position = position - ) + // max batchSize/limit: see https://discord.com/developers/docs/resources/channel#get-reactions + paginateForwards(batchSize = 100, idSelector = { it.id }) { after -> + channel.getReactions(channelId, messageId, emoji = emoji.urlFormat, after, limit = 100) }.map { val data = UserData.from(it) User(data, kord) @@ -240,8 +235,11 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // max: see https://discord.com/developers/docs/resources/user#get-current-user-guilds val batchSize = checkLimitAndGetBatchSize(limit, max = 200) - val flow = paginateForwards(batchSize = batchSize, idSelector = { it.id }) { position -> - user.getCurrentUserGuilds(position, batchSize).map { Guild(guild.getGuild(it.id).toData(), kord) } + val flow = paginateForwards(batchSize = batchSize, idSelector = { it.id }) { after -> + user.getCurrentUserGuilds(position = after, limit = batchSize) + }.map { + val data = guild.getGuild(it.id).toData() + Guild(data, kord) } return flow.limitPagination(limit) diff --git a/rest/src/main/kotlin/service/ChannelService.kt b/rest/src/main/kotlin/service/ChannelService.kt index 1daf1d26e069..cf8f7116b4ff 100644 --- a/rest/src/main/kotlin/service/ChannelService.kt +++ b/rest/src/main/kotlin/service/ChannelService.kt @@ -147,15 +147,15 @@ class ChannelService(requestHandler: RequestHandler) : RestService(requestHandle channelId: Snowflake, messageId: Snowflake, emoji: String, - position: Position? = null, + after: Position.After? = null, limit: Int = 25 ) = call(Route.ReactionsGet) { keys[Route.ChannelId] = channelId keys[Route.MessageId] = messageId keys[Route.Emoji] = emoji - if (position != null) { - parameter(position.key, position.value) + if (after != null) { + parameter(after.key, after.value) } parameter("limit", "$limit") } diff --git a/rest/src/main/kotlin/service/GuildService.kt b/rest/src/main/kotlin/service/GuildService.kt index 2d0533759bb6..12d0adf23d4b 100644 --- a/rest/src/main/kotlin/service/GuildService.kt +++ b/rest/src/main/kotlin/service/GuildService.kt @@ -136,11 +136,11 @@ class GuildService(requestHandler: RequestHandler) : RestService(requestHandler) keys[Route.UserId] = userId } - suspend fun getGuildMembers(guildId: Snowflake, position: Position? = null, limit: Int = 1) = + suspend fun getGuildMembers(guildId: Snowflake, after: Position.After? = null, limit: Int = 1) = call(Route.GuildMembersGet) { keys[Route.GuildId] = guildId - if (position != null) { - parameter(position.key, position.value) + if (after != null) { + parameter(after.key, after.value) } parameter("limit", "$limit") } From f4e5e6c4db5533f5dfaf045c57e329a89140fd9d Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 20:39:19 +0100 Subject: [PATCH 11/23] Fixes for archived threads --- .../main/kotlin/supplier/EntitySupplier.kt | 35 ++++++++++++------- .../kotlin/supplier/RestEntitySupplier.kt | 30 +++++++--------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/core/src/main/kotlin/supplier/EntitySupplier.kt b/core/src/main/kotlin/supplier/EntitySupplier.kt index fc246e85e48e..86d3b752452a 100644 --- a/core/src/main/kotlin/supplier/EntitySupplier.kt +++ b/core/src/main/kotlin/supplier/EntitySupplier.kt @@ -14,6 +14,7 @@ import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.entity.channel.thread.ThreadMember import dev.kord.core.exception.EntityNotFoundException import kotlinx.coroutines.flow.Flow +import kotlinx.datetime.Clock import kotlinx.datetime.Instant /** @@ -61,7 +62,7 @@ public interface EntitySupplier { getGuildPreviewOrNull(guildId) ?: EntityNotFoundException.entityNotFound("Guild Preview", guildId) /** - * Requests to get the widget of this guild through the [strategy], + * Requests to get the widget of a [Guild] with the given [id][guildId], * returns null if the [GuildWidget] isn't present. * * @throws [RequestException] if anything went wrong during the request. @@ -70,7 +71,7 @@ public interface EntitySupplier { /** - * Requests to get the widget of this [guildId]. + * Requests to get the widget of a [Guild] with the given [id][guildId]. * * @throws [RequestException] if anything went wrong during the request. * @throws [EntityNotFoundException] if the [GuildWidget] wasn't present. @@ -314,9 +315,9 @@ public interface EntitySupplier { public fun getEmojis(guildId: Snowflake): Flow /** - * Requests the [guild emojis][GuildEmoji] of the [Guild] with the given [guildId]. + * Requests [guilds][Guild] this bot is known to be part of. * - * The flow may use paginated requests to supply guilds, [limit] will limit the maximum number of guilds + * The flow may use paginated requests to supply guilds, [limit] will limit the maximum number of guilds * supplied and may optimize the batch size accordingly. A value of [Int.MAX_VALUE] means no limit. * * The returned flow is lazily executed, any [RequestException] will be thrown on @@ -377,12 +378,10 @@ public interface EntitySupplier { /** * Requests the [Template] with the given [code]. - * returns null when the webhook isn't present. + * returns null when the template isn't present. * * @throws RequestException if something went wrong while retrieving the template. */ - - public suspend fun getTemplateOrNull(code: String): Template? /** @@ -406,11 +405,23 @@ public interface EntitySupplier { public fun getActiveThreads(guildId: Snowflake): Flow - public fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow - - public fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow - - public fun getJoinedPrivateArchivedThreads(channelId: Snowflake, before: Snowflake, limit: Int): Flow + public fun getPublicArchivedThreads( + channelId: Snowflake, + before: Instant = Clock.System.now(), + limit: Int = Int.MAX_VALUE, + ): Flow + + public fun getPrivateArchivedThreads( + channelId: Snowflake, + before: Instant = Clock.System.now(), + limit: Int = Int.MAX_VALUE, + ): Flow + + public fun getJoinedPrivateArchivedThreads( + channelId: Snowflake, + before: Snowflake = Snowflake.max, + limit: Int = Int.MAX_VALUE, + ): Flow public fun getGuildApplicationCommands(applicationId: Snowflake, guildId: Snowflake): Flow diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 5aeb8f6155b7..c8fdcda492ed 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -356,11 +356,9 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // no max documented: see https://discord.com/developers/docs/resources/channel#list-public-archived-threads val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - val flow = paginateThreads(100, before) { - channel.listPublicArchivedThreads( - channelId, - ListThreadsByTimestampRequest(before, batchSize) - ).threads.mapNotNull { + val flow = paginateThreads(batchSize, start = before) { beforeTimestamp -> + val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) + channel.listPublicArchivedThreads(channelId, request).threads.mapNotNull { val data = ChannelData.from(it) Channel.from(data, kord) as? ThreadChannel } @@ -373,11 +371,9 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // no max documented: see https://discord.com/developers/docs/resources/channel#list-private-archived-threads val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - val flow = paginateThreads(100, before) { - channel.listPrivateArchivedThreads( - channelId, - ListThreadsByTimestampRequest(before, batchSize) - ).threads.mapNotNull { + val flow = paginateThreads(batchSize, start = before) { beforeTimestamp -> + val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) + channel.listPrivateArchivedThreads(channelId, request).threads.mapNotNull { val data = ChannelData.from(it) Channel.from(data, kord) as? ThreadChannel } @@ -394,14 +390,12 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // no max documented: see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - val flow = paginateBackwards(batchSize = batchSize, idSelector = { it.id }) { - channel.listJoinedPrivateArchivedThreads( - channelId, - ListThreadsBySnowflakeRequest(before, batchSize) - ).threads.mapNotNull { - val data = ChannelData.from(it) - Channel.from(data, kord) as? ThreadChannel - } + val flow = paginateBackwards(start = before, batchSize, idSelector = { it.id }) { beforePosition -> + val request = ListThreadsBySnowflakeRequest(before = beforePosition.value, limit = batchSize) + channel.listJoinedPrivateArchivedThreads(channelId, request).threads + }.mapNotNull { + val data = ChannelData.from(it) + Channel.from(data, kord) as? ThreadChannel } return flow.limitPagination(limit) From 7cdc3f23a0b04bc2578783f5c3f4cd431e32cad2 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 21:10:19 +0100 Subject: [PATCH 12/23] Lambdas for guilds idSelector --- core/src/main/kotlin/supplier/RestEntitySupplier.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index c8fdcda492ed..704185cf308b 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -1,7 +1,6 @@ package dev.kord.core.supplier import dev.kord.common.entity.DiscordAuditLogEntry -import dev.kord.common.entity.DiscordPartialGuild import dev.kord.common.entity.Snowflake import dev.kord.common.entity.optional.OptionalSnowflake import dev.kord.common.entity.optional.optionalSnowflake @@ -55,10 +54,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { private val sticker: StickerService get() = kord.rest.sticker override val guilds: Flow - get() = paginateForwards( - idSelector = DiscordPartialGuild::id, - batchSize = 200 // max batchSize/limit: see https://discord.com/developers/docs/resources/user#get-current-user-guilds - ) { after -> + // max batchSize/limit: see https://discord.com/developers/docs/resources/user#get-current-user-guilds + get() = paginateForwards(batchSize = 200, idSelector = { it.id }) { after -> user.getCurrentUserGuilds(position = after, limit = 200) }.map { val data = guild.getGuild(it.id).toData() From d38a7017537f32960c0cdffe1e22e34e555baeed Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 21:26:58 +0100 Subject: [PATCH 13/23] Fix pagination for audit log --- .../src/main/kotlin/supplier/RestEntitySupplier.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 704185cf308b..64b85403b6a9 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -322,9 +322,17 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { public fun getAuditLogEntries( guildId: Snowflake, - request: AuditLogGetRequest = AuditLogGetRequest() - ): Flow = paginateBackwards(batchSize = 100, idSelector = DiscordAuditLogEntry::id) { - auditLog.getAuditLogs(guildId, request.copy(before = it.value)).auditLogEntries + request: AuditLogGetRequest = AuditLogGetRequest(), + ): Flow { + // max: see https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log + val batchSize = checkLimitAndGetBatchSize(request.limit, max = 100) + + val flow = paginateBackwards(batchSize = batchSize, idSelector = { it.id }) { beforePosition -> + val r = request.copy(before = beforePosition.value, limit = batchSize) + auditLog.getAuditLogs(guildId, request = r).auditLogEntries + } + + return flow.limitPagination(request.limit) } override suspend fun getStageInstanceOrNull(channelId: Snowflake): StageInstance? = catchNotFound { From 1f145285749270b3f70ba2d7b2e1d98b960d7631 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 21:59:04 +0100 Subject: [PATCH 14/23] Higher order function for limited pagination --- .../kotlin/supplier/RestEntitySupplier.kt | 138 ++++++++---------- 1 file changed, 61 insertions(+), 77 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 64b85403b6a9..b3a04dbcaa54 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -118,34 +118,28 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Message(channel.getMessage(channelId = channelId, messageId = messageId).toData(), kord) } - override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { - // max: see https://discord.com/developers/docs/resources/channel#get-channel-messages - val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - - val flow = paginateForwards(start = messageId, batchSize, idSelector = { it.id }) { after -> - channel.getMessages(channelId, position = after, limit = batchSize) + // maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages + override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = + limitedPagination(limit, maxBatchSize = 100) { batchSize -> + paginateForwards(start = messageId, batchSize, idSelector = { it.id }) { after -> + channel.getMessages(channelId, position = after, limit = batchSize) + } }.map { val data = MessageData.from(it) Message(data, kord) } - return flow.limitPagination(limit) - } - - override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { - // max: see https://discord.com/developers/docs/resources/channel#get-channel-messages - val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - - val flow = paginateBackwards(start = messageId, batchSize, idSelector = { it.id }) { before -> - channel.getMessages(channelId, position = before, limit = batchSize) + // maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages + override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = + limitedPagination(limit, maxBatchSize = 100) { batchSize -> + paginateBackwards(start = messageId, batchSize, idSelector = { it.id }) { before -> + channel.getMessages(channelId, position = before, limit = batchSize) + } }.map { val data = MessageData.from(it) Message(data, kord) } - return flow.limitPagination(limit) - } - override fun getMessagesAround(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = flow { val responses = channel.getMessages(channelId, Position.Around(messageId), limit) @@ -184,21 +178,18 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { emit(Ban(BanData.from(guildId, banData), kord)) } - override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow { - // max: see https://discord.com/developers/docs/resources/guild#list-guild-members - val batchSize = checkLimitAndGetBatchSize(limit, max = 1000) - - val flow = paginateForwards(idSelector = { it.user.value!!.id }, batchSize = batchSize) { after -> - guild.getGuildMembers(guildId, after, limit = batchSize) + // maxBatchSize: see https://discord.com/developers/docs/resources/guild#list-guild-members + override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow = + limitedPagination(limit, maxBatchSize = 1000) { batchSize -> + paginateForwards(idSelector = { it.user.value!!.id }, batchSize = batchSize) { after -> + guild.getGuildMembers(guildId, after, limit = batchSize) + } }.map { val userData = it.user.value!!.toData() val memberData = it.toData(guildId = guildId, userId = it.user.value!!.id) Member(memberData, userData, kord) } - return flow.limitPagination(limit) - } - override fun getGuildVoiceRegions(guildId: Snowflake): Flow = flow { for (region in guild.getGuildVoiceRegions(guildId)) { @@ -228,20 +219,17 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } } - override fun getCurrentUserGuilds(limit: Int): Flow { - // max: see https://discord.com/developers/docs/resources/user#get-current-user-guilds - val batchSize = checkLimitAndGetBatchSize(limit, max = 200) - - val flow = paginateForwards(batchSize = batchSize, idSelector = { it.id }) { after -> - user.getCurrentUserGuilds(position = after, limit = batchSize) + // maxBatchSize: see https://discord.com/developers/docs/resources/user#get-current-user-guilds + override fun getCurrentUserGuilds(limit: Int): Flow = + limitedPagination(limit, maxBatchSize = 200) { batchSize -> + paginateForwards(batchSize = batchSize, idSelector = { it.id }) { after -> + user.getCurrentUserGuilds(position = after, limit = batchSize) + } }.map { val data = guild.getGuild(it.id).toData() Guild(data, kord) } - return flow.limitPagination(limit) - } - override fun getChannelWebhooks(channelId: Snowflake): Flow = flow { for (webhook in webhook.getChannelWebhooks(channelId)) { val data = WebhookData.from(webhook) @@ -320,19 +308,15 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { getGuildWelcomeScreenOrNull(guildId) ?: EntityNotFoundException.welcomeScreenNotFound(guildId) + // maxBatchSize: see https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log public fun getAuditLogEntries( guildId: Snowflake, request: AuditLogGetRequest = AuditLogGetRequest(), - ): Flow { - // max: see https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log - val batchSize = checkLimitAndGetBatchSize(request.limit, max = 100) - - val flow = paginateBackwards(batchSize = batchSize, idSelector = { it.id }) { beforePosition -> + ): Flow = limitedPagination(request.limit, maxBatchSize = 100) { batchSize -> + paginateBackwards(batchSize = batchSize, idSelector = { it.id }) { beforePosition -> val r = request.copy(before = beforePosition.value, limit = batchSize) auditLog.getAuditLogs(guildId, request = r).auditLogEntries } - - return flow.limitPagination(request.limit) } override suspend fun getStageInstanceOrNull(channelId: Snowflake): StageInstance? = catchNotFound { @@ -357,53 +341,43 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } } - override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { - // no max documented: see https://discord.com/developers/docs/resources/channel#list-public-archived-threads - val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - - val flow = paginateThreads(batchSize, start = before) { beforeTimestamp -> - val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) - channel.listPublicArchivedThreads(channelId, request).threads.mapNotNull { - val data = ChannelData.from(it) - Channel.from(data, kord) as? ThreadChannel + // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-public-archived-threads + override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow = + limitedPagination(limit, maxBatchSize = 100) { batchSize -> + paginateThreads(batchSize, start = before) { beforeTimestamp -> + val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) + channel.listPublicArchivedThreads(channelId, request).threads.mapNotNull { + val data = ChannelData.from(it) + Channel.from(data, kord) as? ThreadChannel + } } } - return flow.limitPagination(limit) - } - - override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { - // no max documented: see https://discord.com/developers/docs/resources/channel#list-private-archived-threads - val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - - val flow = paginateThreads(batchSize, start = before) { beforeTimestamp -> - val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) - channel.listPrivateArchivedThreads(channelId, request).threads.mapNotNull { - val data = ChannelData.from(it) - Channel.from(data, kord) as? ThreadChannel + // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-private-archived-threads + override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow = + limitedPagination(limit, maxBatchSize = 100) { batchSize -> + paginateThreads(batchSize, start = before) { beforeTimestamp -> + val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) + channel.listPrivateArchivedThreads(channelId, request).threads.mapNotNull { + val data = ChannelData.from(it) + Channel.from(data, kord) as? ThreadChannel + } } } - return flow.limitPagination(limit) - } - + // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads override fun getJoinedPrivateArchivedThreads( channelId: Snowflake, before: Snowflake, - limit: Int - ): Flow { - // no max documented: see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads - val batchSize = checkLimitAndGetBatchSize(limit, max = 100) - - val flow = paginateBackwards(start = before, batchSize, idSelector = { it.id }) { beforePosition -> + limit: Int, + ): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> + paginateBackwards(start = before, batchSize, idSelector = { it.id }) { beforePosition -> val request = ListThreadsBySnowflakeRequest(before = beforePosition.value, limit = batchSize) channel.listJoinedPrivateArchivedThreads(channelId, request).threads }.mapNotNull { val data = ChannelData.from(it) Channel.from(data, kord) as? ThreadChannel } - - return flow.limitPagination(limit) } override fun getGuildApplicationCommands( @@ -529,9 +503,19 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } -private fun checkLimitAndGetBatchSize(limit: Int, max: Int): Int { +private fun checkLimitAndGetBatchSize(limit: Int, maxBatchSize: Int): Int { require(limit > 0) { "At least 1 item should be requested, but got $limit." } - return min(limit, max) + return min(limit, maxBatchSize) } private fun Flow.limitPagination(limit: Int): Flow = if (limit == Int.MAX_VALUE) this else take(limit) + +private inline fun limitedPagination( + limit: Int, + maxBatchSize: Int, + paginationCreator: (batchSize: Int) -> Flow, +): Flow { + val batchSize = checkLimitAndGetBatchSize(limit, maxBatchSize) + val flow = paginationCreator(batchSize) + return flow.limitPagination(limit) +} From 26c4ef643cdad7875f50b0199875093be732e0e3 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 22:11:41 +0100 Subject: [PATCH 15/23] unexpected bigger page will not stop pagination --- core/src/main/kotlin/Util.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index 67ba9d4f6231..297a99646dfe 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -129,7 +129,6 @@ internal fun , T, P : Position> paginate( request: suspend (position: P) -> C, ): Flow = flow { var position = directionSelector(start) - var size = batchSize while (true) { val response = request(position) @@ -138,8 +137,7 @@ internal fun , T, P : Position> paginate( val id = itemSelector(response)?.let(idSelector) ?: break position = directionSelector(id) - if (response.size < size) break - size = response.size + if (response.size < batchSize) break } } From 9e929734fbc7f9311332e3eb47dae9807948c1fb Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 22:23:58 +0100 Subject: [PATCH 16/23] batchSize as first param in pagination --- core/src/main/kotlin/Util.kt | 65 +++++++++++++------ .../kotlin/supplier/RestEntitySupplier.kt | 12 ++-- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index 297a99646dfe..e3023eea78b8 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -179,43 +179,67 @@ internal fun oldestItem(idSelector: (T) -> Snowflake): (Collection) -> T? * Selects the [Position.After] the youngest item in the batch. */ internal fun paginateForwards( - start: Snowflake = Snowflake.min, batchSize: Int, + start: Snowflake = Snowflake.min, idSelector: (T) -> Snowflake, request: suspend (after: Position.After) -> Collection, -): Flow = - paginate(start, batchSize, idSelector, youngestItem(idSelector), Position::After, request) +): Flow = paginate( + start, + batchSize, + idSelector, + itemSelector = youngestItem(idSelector), + directionSelector = Position::After, + request, +) /** * Selects the [Position.After] the youngest item in the batch. */ internal fun paginateForwards( - start: Snowflake = Snowflake.min, batchSize: Int, + start: Snowflake = Snowflake.min, request: suspend (after: Position.After) -> Collection, -): Flow = - paginate(start, batchSize, { it.id }, youngestItem { it.id }, Position::After, request) +): Flow = paginate( + start, + batchSize, + idSelector = { it.id }, + itemSelector = youngestItem { it.id }, + directionSelector = Position::After, + request, +) /** * Selects the [Position.Before] the oldest item in the batch. */ internal fun paginateBackwards( - start: Snowflake = Snowflake.max, batchSize: Int, + start: Snowflake = Snowflake.max, idSelector: (T) -> Snowflake, request: suspend (before: Position.Before) -> Collection, -): Flow = - paginate(start, batchSize, idSelector, oldestItem(idSelector), Position::Before, request) +): Flow = paginate( + start, + batchSize, + idSelector, + itemSelector = oldestItem(idSelector), + directionSelector = Position::Before, + request, +) /** * Selects the [Position.Before] the oldest item in the batch. */ internal fun paginateBackwards( - start: Snowflake = Snowflake.max, batchSize: Int, + start: Snowflake = Snowflake.max, request: suspend (before: Position.Before) -> Collection, -): Flow = - paginate(start, batchSize, { it.id }, oldestItem { it.id }, Position::Before, request) +): Flow = paginate( + start, + batchSize, + idSelector = { it.id }, + itemSelector = oldestItem { it.id }, + directionSelector = Position::Before, + request, +) /** * Paginates the [Collection] returned by [request] with [start] as a initial reference in time. @@ -226,8 +250,8 @@ internal fun paginateBackwards( * * [instantSelector] returns null. */ internal fun , T> paginateByDate( - start: Instant = Clock.System.now(), batchSize: Int, + start: Instant = Clock.System.now(), instantSelector: (C) -> Instant?, request: suspend (Instant) -> C, ): Flow = flow { @@ -251,14 +275,13 @@ internal fun , T> paginateByDate( internal fun paginateThreads( batchSize: Int, start: Instant = Clock.System.now(), - request: suspend (Instant) -> Collection -) = - paginateByDate( - start, - batchSize, - { threads -> threads.minOfOrNull { it.archiveTimestamp } }, - request - ) + request: suspend (Instant) -> Collection, +) = paginateByDate( + batchSize, + start, + instantSelector = { threads -> threads.minOfOrNull { it.archiveTimestamp } }, + request, +) public inline fun Intents.IntentsBuilder.enableEvent(): Unit = enableEvent(T::class) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index b3a04dbcaa54..750b9e8e782f 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -121,7 +121,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> - paginateForwards(start = messageId, batchSize, idSelector = { it.id }) { after -> + paginateForwards(batchSize, start = messageId, idSelector = { it.id }) { after -> channel.getMessages(channelId, position = after, limit = batchSize) } }.map { @@ -132,7 +132,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> - paginateBackwards(start = messageId, batchSize, idSelector = { it.id }) { before -> + paginateBackwards(batchSize, start = messageId, idSelector = { it.id }) { before -> channel.getMessages(channelId, position = before, limit = batchSize) } }.map { @@ -181,7 +181,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // maxBatchSize: see https://discord.com/developers/docs/resources/guild#list-guild-members override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow = limitedPagination(limit, maxBatchSize = 1000) { batchSize -> - paginateForwards(idSelector = { it.user.value!!.id }, batchSize = batchSize) { after -> + paginateForwards(batchSize, idSelector = { it.user.value!!.id }) { after -> guild.getGuildMembers(guildId, after, limit = batchSize) } }.map { @@ -222,7 +222,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // maxBatchSize: see https://discord.com/developers/docs/resources/user#get-current-user-guilds override fun getCurrentUserGuilds(limit: Int): Flow = limitedPagination(limit, maxBatchSize = 200) { batchSize -> - paginateForwards(batchSize = batchSize, idSelector = { it.id }) { after -> + paginateForwards(batchSize, idSelector = { it.id }) { after -> user.getCurrentUserGuilds(position = after, limit = batchSize) } }.map { @@ -313,7 +313,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { guildId: Snowflake, request: AuditLogGetRequest = AuditLogGetRequest(), ): Flow = limitedPagination(request.limit, maxBatchSize = 100) { batchSize -> - paginateBackwards(batchSize = batchSize, idSelector = { it.id }) { beforePosition -> + paginateBackwards(batchSize, idSelector = { it.id }) { beforePosition -> val r = request.copy(before = beforePosition.value, limit = batchSize) auditLog.getAuditLogs(guildId, request = r).auditLogEntries } @@ -371,7 +371,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { before: Snowflake, limit: Int, ): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> - paginateBackwards(start = before, batchSize, idSelector = { it.id }) { beforePosition -> + paginateBackwards(batchSize, start = before, idSelector = { it.id }) { beforePosition -> val request = ListThreadsBySnowflakeRequest(before = beforePosition.value, limit = batchSize) channel.listJoinedPrivateArchivedThreads(channelId, request).threads }.mapNotNull { From c1b885a2cbb865b6d0a93424c89ccff3f134c201 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sat, 15 Jan 2022 22:59:22 +0100 Subject: [PATCH 17/23] Make sure Position.Around can not be used in UserService.getCurrentUserGuilds --- .../kotlin/supplier/RestEntitySupplier.kt | 2 +- rest/src/main/kotlin/route/Position.kt | 26 +++++++++++++++---- rest/src/main/kotlin/service/UserService.kt | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 750b9e8e782f..1a27a517a3bf 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -53,8 +53,8 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { private val stageInstance: StageInstanceService get() = kord.rest.stageInstance private val sticker: StickerService get() = kord.rest.sticker + // max batchSize/limit: see https://discord.com/developers/docs/resources/user#get-current-user-guilds override val guilds: Flow - // max batchSize/limit: see https://discord.com/developers/docs/resources/user#get-current-user-guilds get() = paginateForwards(batchSize = 200, idSelector = { it.id }) { after -> user.getCurrentUserGuilds(position = after, limit = 200) }.map { diff --git a/rest/src/main/kotlin/route/Position.kt b/rest/src/main/kotlin/route/Position.kt index 3a0f0785683d..bb2680064284 100644 --- a/rest/src/main/kotlin/route/Position.kt +++ b/rest/src/main/kotlin/route/Position.kt @@ -2,8 +2,24 @@ package dev.kord.rest.route import dev.kord.common.entity.Snowflake -sealed class Position(val key: String, val value: Snowflake) { - class Before(id: Snowflake) : Position("before", id) - class After(id: Snowflake) : Position("after", id) - class Around(id: Snowflake) : Position("around", id) -} \ No newline at end of file +sealed interface Position { + val key: String + val value: Snowflake + + sealed interface BeforeOrAfter : Position + + class Before(id: Snowflake) : BeforeOrAfter { + override val key get() = "before" + override val value = id + } + + class After(id: Snowflake) : BeforeOrAfter { + override val key get() = "after" + override val value = id + } + + class Around(id: Snowflake) : Position { + override val key get() = "around" + override val value = id + } +} diff --git a/rest/src/main/kotlin/service/UserService.kt b/rest/src/main/kotlin/service/UserService.kt index 0bdfb0c2908b..03edfba0bf15 100644 --- a/rest/src/main/kotlin/service/UserService.kt +++ b/rest/src/main/kotlin/service/UserService.kt @@ -22,7 +22,7 @@ class UserService(requestHandler: RequestHandler) : RestService(requestHandler) keys[Route.UserId] = userId } - suspend fun getCurrentUserGuilds(position: Position? = null, limit: Int = 200) = call(Route.CurrentUsersGuildsGet) { + suspend fun getCurrentUserGuilds(position: Position.BeforeOrAfter? = null, limit: Int = 200) = call(Route.CurrentUsersGuildsGet) { if (position != null) { parameter(position.key, position.value) } From 68d589adec5361d8d2e3f4e37884095443566027 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sun, 16 Jan 2022 02:47:24 +0100 Subject: [PATCH 18/23] Nullable limit and position parameters to use Discord's defaults --- core/src/main/kotlin/Util.kt | 8 +- .../src/main/kotlin/behavior/GuildBehavior.kt | 12 +- .../channel/MessageChannelBehavior.kt | 39 +++--- .../behavior/channel/NewsChannelBehavior.kt | 2 +- .../behavior/channel/TextChannelBehavior.kt | 8 +- .../threads/ThreadParentChannelBehavior.kt | 16 +-- .../kotlin/supplier/CacheEntitySupplier.kt | 127 ++++++++++-------- .../main/kotlin/supplier/EntitySupplier.kt | 32 ++--- .../kotlin/supplier/EntitySupplyStrategy.kt | 4 +- .../kotlin/supplier/FallbackEntitySupplier.kt | 17 ++- .../kotlin/supplier/RestEntitySupplier.kt | 44 +++--- ...RestSupplier.kt => StoreEntitySupplier.kt} | 24 ++-- .../auditlog/AuditLogGetRequestBuilder.kt | 9 +- .../kotlin/json/request/AuditLogRequests.kt | 2 +- .../main/kotlin/service/AuditLogService.kt | 2 +- .../src/main/kotlin/service/ChannelService.kt | 17 +-- rest/src/main/kotlin/service/GuildService.kt | 14 +- rest/src/main/kotlin/service/UserService.kt | 10 +- 18 files changed, 199 insertions(+), 188 deletions(-) rename core/src/main/kotlin/supplier/{CacheAwareRestSupplier.kt => StoreEntitySupplier.kt} (93%) diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index e3023eea78b8..b9fde57f892c 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -242,7 +242,7 @@ internal fun paginateBackwards( ) /** - * Paginates the [Collection] returned by [request] with [start] as a initial reference in time. + * Paginates the [Collection] returned by [request] with [start] as an initial reference in time. * [instantSelector] is used to select the new reference to fetch from. * * Termination scenarios: @@ -251,14 +251,14 @@ internal fun paginateBackwards( */ internal fun , T> paginateByDate( batchSize: Int, - start: Instant = Clock.System.now(), + start: Instant?, instantSelector: (C) -> Instant?, request: suspend (Instant) -> C, ): Flow = flow { var currentTimestamp = start while (true) { - val response = request(currentTimestamp) + val response = request(currentTimestamp ?: Clock.System.now()) // get default current time as late as possible for (item in response) emit(item) @@ -274,7 +274,7 @@ internal fun , T> paginateByDate( */ internal fun paginateThreads( batchSize: Int, - start: Instant = Clock.System.now(), + start: Instant?, request: suspend (Instant) -> Collection, ) = paginateByDate( batchSize, diff --git a/core/src/main/kotlin/behavior/GuildBehavior.kt b/core/src/main/kotlin/behavior/GuildBehavior.kt index 5777c7bdfd0e..2d26421b10c7 100644 --- a/core/src/main/kotlin/behavior/GuildBehavior.kt +++ b/core/src/main/kotlin/behavior/GuildBehavior.kt @@ -161,9 +161,9 @@ public interface GuildBehavior : KordEntity, Strategizable { /** * Requests to get all present members in this guild. * - * Unrestricted consumption of the returned [Flow] is a potentially performance intensive operation, it is thus recommended - * to limit the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions that limit the amount - * of messages requested. + * Unrestricted consumption of the returned [Flow] is a potentially performance-intensive operation, it is thus + * recommended limiting the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions + * that limit the amount of messages requested. * * ```kotlin * guild.members.first { it.displayName == targetName } @@ -288,7 +288,7 @@ public interface GuildBehavior : KordEntity, Strategizable { supplier.getGuildApplicationCommandOrNull(kord.resources.applicationId, id, commandId) /** - * Requests to get the this behavior as a [Guild]. + * Requests to get this behavior as a [Guild]. * * @throws [RequestException] if anything went wrong during the request. * @throws [EntityNotFoundException] if the guild wasn't present. @@ -350,11 +350,9 @@ public interface GuildBehavior : KordEntity, Strategizable { * * The returned flow is lazily executed, any [RequestException] will be thrown on * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. - * - * This function is not part of the officially documented Discord API and may be removed/altered/stop working in the future. */ @KordExperimental - public suspend fun getMembers(query: String, limit: Int = 1000): Flow = flow { + public fun getMembers(query: String, limit: Int = 1000): Flow = flow { kord.rest.guild.getGuildMembers(id, query, limit).forEach { emit( Member( diff --git a/core/src/main/kotlin/behavior/channel/MessageChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/MessageChannelBehavior.kt index 5bfe77063ffc..dc6f62d07f72 100644 --- a/core/src/main/kotlin/behavior/channel/MessageChannelBehavior.kt +++ b/core/src/main/kotlin/behavior/channel/MessageChannelBehavior.kt @@ -37,7 +37,7 @@ import kotlin.time.TimeMark public interface MessageChannelBehavior : ChannelBehavior, Strategizable { /** - * Requests to get the this behavior as a [MessageChannel]. + * Requests to get this behavior as a [MessageChannel]. * * @throws [RequestException] if something went wrong during the request. * @throws [EntityNotFoundException] if the channel wasn't present. @@ -74,9 +74,9 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable { * Requests to get all messages in this channel. * * Messages retrieved by this function will be emitted in chronological order (oldest -> newest). - * Unrestricted consumption of the returned [Flow] is a potentially performance intensive operation, it is thus recommended - * to limit the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions that limit the amount - * of messages requested. + * Unrestricted consumption of the returned [Flow] is a potentially performance-intensive operation, it is thus + * recommended limiting the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions + * that limit the amount of messages requested. * * ```kotlin * channel.getMessagesBefore(newer.id).takeWhile { it.id > older.id } @@ -127,11 +127,11 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable { * Messages retrieved by this function will be emitted in reverse-chronological older (newest -> oldest). * * The flow may use paginated requests to supply messages, [limit] will limit the maximum number of messages - * supplied and may optimize the batch size accordingly. A value of [Int.MAX_VALUE] means no limit. + * supplied and may optimize the batch size accordingly. `null` means no limit. * - * Unrestricted consumption of the returned [Flow] is a potentially performance intensive operation, it is thus recommended - * to limit the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions that limit the amount - * of messages requested. + * Unrestricted consumption of the returned [Flow] is a potentially performance-intensive operation, it is thus + * recommended limiting the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions + * that limit the amount of messages requested. * * ```kotlin * channel.getMessagesBefore(newer.id).takeWhile { it.id > older.id } @@ -142,7 +142,7 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable { * * @throws IllegalArgumentException if a [limit] < 1 was supplied. */ - public fun getMessagesBefore(messageId: Snowflake, limit: Int = Int.MAX_VALUE): Flow = + public fun getMessagesBefore(messageId: Snowflake, limit: Int? = null): Flow = supplier.getMessagesBefore(channelId = id, messageId = messageId, limit = limit) /** @@ -151,11 +151,11 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable { * Messages retrieved by this function will be emitted in chronological older (oldest -> newest). * * The flow may use paginated requests to supply messages, [limit] will limit the maximum number of messages - * supplied and may optimize the batch size accordingly. A value of [Int.MAX_VALUE] means no limit. + * supplied and may optimize the batch size accordingly. `null` means no limit. * - * Unrestricted consumption of the returned [Flow] is a potentially performance intensive operation, it is thus recommended - * to limit the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions that limit the amount - * of messages requested. + * Unrestricted consumption of the returned [Flow] is a potentially performance-intensive operation, it is thus + * recommended limiting the amount of messages requested by using [Flow.take], [Flow.takeWhile] or other functions + * that limit the amount of messages requested. * * ```kotlin * channel.getMessagesAfter(older.id).takeWhile { it.id < newer.id } @@ -165,19 +165,22 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable { * * @throws IllegalArgumentException if a [limit] < 1 was supplied. */ - public fun getMessagesAfter(messageId: Snowflake, limit: Int = Int.MAX_VALUE): Flow = + public fun getMessagesAfter(messageId: Snowflake, limit: Int? = null): Flow = supplier.getMessagesAfter(channelId = id, messageId = messageId, limit = limit) /** - * Requests to get messages around (both older and newer) the [messageId]. + * Requests to get [Message]s around (both older and newer) the [messageId]. * * Messages retrieved by this function will be emitted in chronological older (oldest -> newest). * * Unlike [getMessagesAfter] and [getMessagesBefore], this flow can return **a maximum of 100 messages**. * As such, the accepted range of [limit] is reduced to 1..100. * - * Supplied messages will be equally distributed before and after the [messageId]. - * The remaining message for an odd [limit] is undefined and may appear on either side. + * Supplied messages will be equally distributed before and after the [messageId]. + * The remaining message for an odd [limit] is undefined and may appear on either side or no side at all. + * + * If a message with the given [messageId] exists, the flow might also contain it, so it **could have one more + * element than the given [limit]**. * * The returned flow is lazily executed, any [RequestException] will be thrown on * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. @@ -185,7 +188,7 @@ public interface MessageChannelBehavior : ChannelBehavior, Strategizable { * @throws IllegalArgumentException if the [limit] is outside the range of 1..100. */ public fun getMessagesAround(messageId: Snowflake, limit: Int = 100): Flow = - supplier.getMessagesAround(channelId = id, messageId = messageId, limit = 100) + supplier.getMessagesAround(channelId = id, messageId = messageId, limit = limit) /** * Requests to get a message with the given [messageId]. diff --git a/core/src/main/kotlin/behavior/channel/NewsChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/NewsChannelBehavior.kt index 47c5f3aa1adb..3f37230a1cc3 100644 --- a/core/src/main/kotlin/behavior/channel/NewsChannelBehavior.kt +++ b/core/src/main/kotlin/behavior/channel/NewsChannelBehavior.kt @@ -100,7 +100,7 @@ public interface NewsChannelBehavior : ThreadParentChannelBehavior { } - override fun getPublicArchivedThreads(before: Instant, limit: Int): Flow { + override fun getPublicArchivedThreads(before: Instant?, limit: Int?): Flow { return super.getPublicArchivedThreads(before, limit).filterIsInstance() } diff --git a/core/src/main/kotlin/behavior/channel/TextChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/TextChannelBehavior.kt index 268403853c6b..94f7024e6425 100644 --- a/core/src/main/kotlin/behavior/channel/TextChannelBehavior.kt +++ b/core/src/main/kotlin/behavior/channel/TextChannelBehavior.kt @@ -32,7 +32,7 @@ public interface TextChannelBehavior : PrivateThreadParentChannelBehavior { get() = super.activeThreads.filterIsInstance() /** - * Requests to get the this behavior as a [TextChannel]. + * Requests to get this behavior as a [TextChannel]. * * @throws [RequestException] if anything went wrong during the request. * @throws [EntityNotFoundException] if the channel wasn't present. @@ -96,15 +96,15 @@ public interface TextChannelBehavior : PrivateThreadParentChannelBehavior { return unsafeStartPublicThreadWithMessage(messageId, name, archiveDuration, reason) as TextChannelThread } - override fun getPublicArchivedThreads(before: Instant, limit: Int): Flow { + override fun getPublicArchivedThreads(before: Instant?, limit: Int?): Flow { return super.getPublicArchivedThreads(before, limit).filterIsInstance() } - override fun getPrivateArchivedThreads(before: Instant, limit: Int): Flow { + override fun getPrivateArchivedThreads(before: Instant?, limit: Int?): Flow { return super.getPrivateArchivedThreads(before, limit).filterIsInstance() } - override fun getJoinedPrivateArchivedThreads(before: Snowflake, limit: Int): Flow { + override fun getJoinedPrivateArchivedThreads(before: Snowflake?, limit: Int?): Flow { return super.getJoinedPrivateArchivedThreads(before, limit).filterIsInstance() } diff --git a/core/src/main/kotlin/behavior/channel/threads/ThreadParentChannelBehavior.kt b/core/src/main/kotlin/behavior/channel/threads/ThreadParentChannelBehavior.kt index 84def7d9ea3c..e0e1ebe1632b 100644 --- a/core/src/main/kotlin/behavior/channel/threads/ThreadParentChannelBehavior.kt +++ b/core/src/main/kotlin/behavior/channel/threads/ThreadParentChannelBehavior.kt @@ -3,9 +3,6 @@ package dev.kord.core.behavior.channel.threads import dev.kord.common.entity.ArchiveDuration import dev.kord.common.entity.ChannelType import dev.kord.common.entity.Snowflake -import dev.kord.common.entity.optional.Optional -import dev.kord.common.entity.optional.OptionalBoolean -import dev.kord.common.entity.optional.optional import dev.kord.common.exception.RequestException import dev.kord.core.Kord import dev.kord.core.behavior.channel.ChannelBehavior @@ -22,7 +19,6 @@ import dev.kord.rest.builder.channel.thread.StartThreadBuilder import dev.kord.rest.json.request.StartThreadRequest import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter -import kotlinx.datetime.Clock import kotlinx.datetime.Instant import java.util.* @@ -49,8 +45,8 @@ public interface ThreadParentChannelBehavior : TopGuildMessageChannelBehavior { * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. */ public fun getPublicArchivedThreads( - before: Instant = Clock.System.now(), - limit: Int = Int.MAX_VALUE + before: Instant? = null, + limit: Int? = null, ): Flow { return supplier.getPublicArchivedThreads(id, before, limit) } @@ -103,8 +99,8 @@ public interface PrivateThreadParentChannelBehavior : ThreadParentChannelBehavio * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. */ public fun getPrivateArchivedThreads( - before: Instant = Clock.System.now(), - limit: Int = Int.MAX_VALUE + before: Instant? = null, + limit: Int? = null, ): Flow { return supplier.getPrivateArchivedThreads(id, before, limit) } @@ -118,8 +114,8 @@ public interface PrivateThreadParentChannelBehavior : ThreadParentChannelBehavio * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. */ public fun getJoinedPrivateArchivedThreads( - before: Snowflake = Snowflake.max, - limit: Int = Int.MAX_VALUE + before: Snowflake? = null, + limit: Int? = null, ): Flow { return supplier.getJoinedPrivateArchivedThreads(id, before, limit) } diff --git a/core/src/main/kotlin/supplier/CacheEntitySupplier.kt b/core/src/main/kotlin/supplier/CacheEntitySupplier.kt index a222d2709efd..2a685ede6d12 100644 --- a/core/src/main/kotlin/supplier/CacheEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/CacheEntitySupplier.kt @@ -1,7 +1,6 @@ package dev.kord.core.supplier import dev.kord.cache.api.DataCache -import dev.kord.cache.api.count import dev.kord.cache.api.query import dev.kord.common.entity.ChannelType import dev.kord.common.entity.Snowflake @@ -11,7 +10,6 @@ import dev.kord.core.Kord import dev.kord.core.any import dev.kord.core.cache.data.* import dev.kord.core.cache.idEq -import dev.kord.core.cache.idGt import dev.kord.core.entity.* import dev.kord.core.entity.application.ApplicationCommandPermissions import dev.kord.core.entity.application.GlobalApplicationCommand @@ -22,15 +20,7 @@ import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.entity.channel.thread.ThreadMember import dev.kord.core.exception.EntityNotFoundException import dev.kord.gateway.Gateway -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.* import kotlinx.datetime.Instant import kotlinx.datetime.toInstant @@ -52,10 +42,10 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { * * The Cache this [CacheEntitySupplier] operates on. * - * short-hand for [Kord.cache] + * Shorthand for [kord.cache][Kord.cache]. * */ - private val cache: DataCache = kord.cache + private val cache: DataCache get() = kord.cache /** * Returns a [Flow] of [Channel]s fetched from cache. @@ -157,20 +147,20 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { return Message(data, kord) } - override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } + override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow { + checkLimit(limit) return cache.query { idEq(MessageData::channelId, channelId) MessageData::id gt messageId - }.asFlow().map { Message(it, kord) }.take(limit) + }.asFlow().map { Message(it, kord) }.limit(limit) } - override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } + override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow { + checkLimit(limit) return cache.query { idEq(MessageData::channelId, channelId) - idGt(MessageData::id, messageId) - }.asFlow().map { Message(it, kord) }.take(limit) + MessageData::id lt messageId + }.asFlow().map { Message(it, kord) }.limit(limit) } @@ -178,6 +168,7 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { require(limit in 1..100) { "Expected limit to be in 1..100, but was $limit" } return flow { emitAll(getMessagesBefore(messageId, channelId, limit / 2)) + getMessageOrNull(channelId, messageId)?.let { emit(it) } emitAll(getMessagesAfter(messageId, channelId, limit / 2)) } } @@ -209,13 +200,15 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { idEq(BanData::guildId, guildId) }.asFlow().map { Ban(it, kord) } - override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - return cache.query { idEq(MemberData::guildId, guildId) }.asFlow().mapNotNull { - val userData = - cache.query { idEq(UserData::id, it.userId) }.singleOrNull() ?: return@mapNotNull null - Member(it, userData, kord) - } + override fun getGuildMembers(guildId: Snowflake, limit: Int?): Flow { + checkLimit(limit) + return cache.query { idEq(MemberData::guildId, guildId) } + .asFlow() + .mapNotNull { memberData -> + val userData = cache.query { idEq(UserData::id, memberData.userId) }.singleOrNull() + userData?.let { Member(memberData, userData = it, kord) } + } + .limit(limit) } override fun getGuildVoiceRegions(guildId: Snowflake): Flow = cache.query { @@ -235,11 +228,11 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { idEq(EmojiData::guildId, guildId) }.asFlow().map { GuildEmoji(it, kord) } - override fun getCurrentUserGuilds(limit: Int): Flow { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } + override fun getCurrentUserGuilds(limit: Int?): Flow { + checkLimit(limit) return guilds.filter { members.any { it.id == kord.selfId } - }.take(limit) + }.limit(limit) } override fun getChannelWebhooks(channelId: Snowflake): Flow = cache.query { @@ -310,60 +303,70 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { emitAll(result) } - override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow = - flow { - val result = cache.query { - idEq(ChannelData::parentId, channelId) - }.toCollection() + override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow { + checkLimit(limit) + return flow { + val result = cache.query { idEq(ChannelData::parentId, channelId) } + .toCollection() .sortedByDescending { it.threadMetadata.value?.archiveTimestamp?.toInstant() } .asFlow() .filter { val time = it.threadMetadata.value?.archiveTimestamp?.toInstant() it.threadMetadata.value?.archived == true && time != null - && time < before + && (before == null || time < before) && (it.type == ChannelType.PublicGuildThread || it.type == ChannelType.PublicNewsThread) - }.take(limit).mapNotNull { Channel.from(it, kord) as? ThreadChannel } + } + .limit(limit) + .mapNotNull { Channel.from(it, kord) as? ThreadChannel } emitAll(result) } + } - override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow = - flow { - val result = cache.query { - idEq(ChannelData::parentId, channelId) - }.toCollection() + override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow { + checkLimit(limit) + return flow { + val result = cache.query { idEq(ChannelData::parentId, channelId) } + .toCollection() .sortedByDescending { it.threadMetadata.value?.archiveTimestamp?.toInstant() } .asFlow() .filter { val time = it.threadMetadata.value?.archiveTimestamp?.toInstant() it.threadMetadata.value?.archived == true && time != null - && time < before + && (before == null || time < before) && it.type == ChannelType.PrivateThread - }.take(limit).mapNotNull { Channel.from(it, kord) as? ThreadChannel } + } + .limit(limit) + .mapNotNull { Channel.from(it, kord) as? ThreadChannel } emitAll(result) } + } override fun getJoinedPrivateArchivedThreads( channelId: Snowflake, - before: Snowflake, - limit: Int - ): Flow = flow { - val result = cache.query { - idEq(ChannelData::parentId, channelId) - }.toCollection() - .sortedByDescending { it.id } - .asFlow() - .filter { - it.threadMetadata.value?.archived == true - && it.id < before - && it.type == ChannelType.PrivateThread - && it.member !is Optional.Missing - }.take(limit).mapNotNull { Channel.from(it, kord) as? ThreadChannel } + before: Snowflake?, + limit: Int?, + ): Flow { + checkLimit(limit) + return flow { + val result = cache.query { idEq(ChannelData::parentId, channelId) } + .toCollection() + .sortedByDescending { it.id } + .asFlow() + .filter { + it.threadMetadata.value?.archived == true + && (before == null || it.id < before) + && it.type == ChannelType.PrivateThread + && it.member !is Optional.Missing + } + .limit(limit) + .mapNotNull { Channel.from(it, kord) as? ThreadChannel } - emitAll(result) + emitAll(result) + } } override fun getGuildApplicationCommands( @@ -474,5 +477,11 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { override fun toString(): String { return "CacheEntitySupplier(cache=$cache)" } +} + +private fun checkLimit(limit: Int?) { + require(limit == null || limit > 0) { "At least 1 item should be requested, but got $limit." } } + +private fun Flow.limit(limit: Int?): Flow = if (limit == null) this else take(limit) diff --git a/core/src/main/kotlin/supplier/EntitySupplier.kt b/core/src/main/kotlin/supplier/EntitySupplier.kt index 86d3b752452a..d2c863e47ef6 100644 --- a/core/src/main/kotlin/supplier/EntitySupplier.kt +++ b/core/src/main/kotlin/supplier/EntitySupplier.kt @@ -14,7 +14,6 @@ import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.entity.channel.thread.ThreadMember import dev.kord.core.exception.EntityNotFoundException import kotlinx.coroutines.flow.Flow -import kotlinx.datetime.Clock import kotlinx.datetime.Instant /** @@ -151,28 +150,28 @@ public interface EntitySupplier { * in the [channel][MessageChannel] with the [channelId]. * * The flow may use paginated requests to supply messages, [limit] will limit the maximum number of messages - * supplied and may optimize the batch size accordingly. A value of [Int.MAX_VALUE] means no limit. + * supplied and may optimize the batch size accordingly. `null` means no limit. * * The returned flow is lazily executed, any [RequestException] will be thrown on * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. * * @throws IllegalArgumentException if a [limit] < 1 was supplied. */ - public fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int = Int.MAX_VALUE): Flow + public fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int? = null): Flow /** * Requests a flow of messages created before the [Message] with the [messageId] * in the [channel][MessageChannel] with the [channelId]. * * The flow may use paginated requests to supply messages, [limit] will limit the maximum number of messages - * supplied and may optimize the batch size accordingly. A value of [Int.MAX_VALUE] means no limit. + * supplied and may optimize the batch size accordingly. `null` means no limit. * * The returned flow is lazily executed, any [RequestException] will be thrown on * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. * * @throws IllegalArgumentException if a [limit] < 1 was supplied. */ - public fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int = Int.MAX_VALUE): Flow + public fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int? = null): Flow /** * Requests a flow of messages created around the [Message] with the [messageId] @@ -183,7 +182,10 @@ public interface EntitySupplier { * * Supplied messages will be equally distributed * before and after the [messageId]. The remaining message for an odd [limit] is undefined and may appear on either - * side. + * side or no side at all. + * + * If a [Message] with the given [messageId] exists, the flow might also contain it, so it **could have one more + * element than the given [limit]**. * * The returned flow is lazily executed, any [RequestException] will be thrown on * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. @@ -279,7 +281,7 @@ public interface EntitySupplier { * The returned flow is lazily executed, any [RequestException] will be thrown on * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. */ - public fun getGuildMembers(guildId: Snowflake, limit: Int = Int.MAX_VALUE): Flow + public fun getGuildMembers(guildId: Snowflake, limit: Int? = null): Flow /** * Requests the [regions][Region] of the [Guild] with the given [guildId]. @@ -318,14 +320,14 @@ public interface EntitySupplier { * Requests [guilds][Guild] this bot is known to be part of. * * The flow may use paginated requests to supply guilds, [limit] will limit the maximum number of guilds - * supplied and may optimize the batch size accordingly. A value of [Int.MAX_VALUE] means no limit. + * supplied and may optimize the batch size accordingly. `null` means no limit. * * The returned flow is lazily executed, any [RequestException] will be thrown on * [terminal operators](https://kotlinlang.org/docs/reference/coroutines/flow.html#terminal-flow-operators) instead. * * @throws IllegalArgumentException if a [limit] < 1 was supplied. */ - public fun getCurrentUserGuilds(limit: Int = Int.MAX_VALUE): Flow + public fun getCurrentUserGuilds(limit: Int? = null): Flow /** * Requests the [webhooks][Webhook] of the [MessageChannel] with the given [channelId]. @@ -407,20 +409,20 @@ public interface EntitySupplier { public fun getPublicArchivedThreads( channelId: Snowflake, - before: Instant = Clock.System.now(), - limit: Int = Int.MAX_VALUE, + before: Instant? = null, + limit: Int? = null, ): Flow public fun getPrivateArchivedThreads( channelId: Snowflake, - before: Instant = Clock.System.now(), - limit: Int = Int.MAX_VALUE, + before: Instant? = null, + limit: Int? = null, ): Flow public fun getJoinedPrivateArchivedThreads( channelId: Snowflake, - before: Snowflake = Snowflake.max, - limit: Int = Int.MAX_VALUE, + before: Snowflake? = null, + limit: Int? = null, ): Flow public fun getGuildApplicationCommands(applicationId: Snowflake, guildId: Snowflake): Flow diff --git a/core/src/main/kotlin/supplier/EntitySupplyStrategy.kt b/core/src/main/kotlin/supplier/EntitySupplyStrategy.kt index 7dac9faceb5d..64c0c9e92838 100644 --- a/core/src/main/kotlin/supplier/EntitySupplyStrategy.kt +++ b/core/src/main/kotlin/supplier/EntitySupplyStrategy.kt @@ -41,12 +41,12 @@ public interface EntitySupplyStrategy { /** * A supplier providing a strategy which exclusively uses REST calls to fetch entities. - * fetched entities are stored in [Kord's cache][kord.cache]. + * fetched entities are stored in [Kord's cache][Kord.cache]. * See [StoreEntitySupplier] for more details. */ public val cachingRest: EntitySupplyStrategy = object : EntitySupplyStrategy { override fun supply(kord: Kord): EntitySupplier { - return StoreEntitySupplier(rest.supply(kord), kord.cache, kord) + return StoreEntitySupplier(rest.supply(kord), kord.cache) } override fun toString(): String = "EntitySupplyStrategy.cacheAwareRest" diff --git a/core/src/main/kotlin/supplier/FallbackEntitySupplier.kt b/core/src/main/kotlin/supplier/FallbackEntitySupplier.kt index 08644c54a23f..38d04bdb4a6e 100644 --- a/core/src/main/kotlin/supplier/FallbackEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/FallbackEntitySupplier.kt @@ -50,11 +50,11 @@ private class FallbackEntitySupplier(val first: EntitySupplier, val second: Enti override suspend fun getMember(guildId: Snowflake, userId: Snowflake): Member = getMemberOrNull(guildId, userId)!! - override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = + override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow = first.getMessagesAfter(messageId, channelId, limit) .switchIfEmpty(second.getMessagesAfter(messageId, channelId, limit)) - override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = + override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow = first.getMessagesBefore(messageId, channelId, limit) .switchIfEmpty(second.getMessagesBefore(messageId, channelId, limit)) @@ -81,7 +81,7 @@ private class FallbackEntitySupplier(val first: EntitySupplier, val second: Enti override fun getGuildBans(guildId: Snowflake): Flow = first.getGuildBans(guildId).switchIfEmpty(second.getGuildBans(guildId)) - override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow = + override fun getGuildMembers(guildId: Snowflake, limit: Int?): Flow = first.getGuildMembers(guildId, limit).switchIfEmpty(second.getGuildMembers(guildId, limit)) override fun getGuildVoiceRegions(guildId: Snowflake): Flow = @@ -93,7 +93,7 @@ private class FallbackEntitySupplier(val first: EntitySupplier, val second: Enti override fun getEmojis(guildId: Snowflake): Flow = first.getEmojis(guildId).switchIfEmpty(second.getEmojis(guildId)) - override fun getCurrentUserGuilds(limit: Int): Flow = + override fun getCurrentUserGuilds(limit: Int?): Flow = first.getCurrentUserGuilds(limit).switchIfEmpty(second.getCurrentUserGuilds(limit)) override fun getChannelWebhooks(channelId: Snowflake): Flow = @@ -132,20 +132,20 @@ private class FallbackEntitySupplier(val first: EntitySupplier, val second: Enti return first.getActiveThreads(guildId).switchIfEmpty(second.getActiveThreads(guildId)) } - override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { + override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow { return first.getPublicArchivedThreads(channelId, before, limit) .switchIfEmpty(second.getPublicArchivedThreads(channelId, before, limit)) } - override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { + override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow { return first.getPrivateArchivedThreads(channelId, before, limit) .switchIfEmpty(second.getPrivateArchivedThreads(channelId, before, limit)) } override fun getJoinedPrivateArchivedThreads( channelId: Snowflake, - before: Snowflake, - limit: Int + before: Snowflake?, + limit: Int?, ): Flow { return first.getJoinedPrivateArchivedThreads(channelId, before, limit) .switchIfEmpty(second.getJoinedPrivateArchivedThreads(channelId, before, limit)) @@ -225,4 +225,3 @@ private class FallbackEntitySupplier(val first: EntitySupplier, val second: Enti return "FallbackEntitySupplier(first=$first, second=$second)" } } - diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index 1a27a517a3bf..a30f65b54da0 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -35,7 +35,7 @@ import kotlin.math.min * 404(Not Found) will be caught by the `xOrNull` variant and return null instead. * * This supplier will always be able to resolve entities if they exist according - * to Discord, entities will always be up to date at the moment of the call. + * to Discord, entities will always be up-to-date at the moment of the call. */ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { @@ -119,7 +119,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } // maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages - override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = + override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> paginateForwards(batchSize, start = messageId, idSelector = { it.id }) { after -> channel.getMessages(channelId, position = after, limit = batchSize) @@ -130,7 +130,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } // maxBatchSize: see https://discord.com/developers/docs/resources/channel#get-channel-messages - override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = + override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> paginateBackwards(batchSize, start = messageId, idSelector = { it.id }) { before -> channel.getMessages(channelId, position = before, limit = batchSize) @@ -140,12 +140,14 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { Message(data, kord) } - override fun getMessagesAround(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow = flow { - val responses = channel.getMessages(channelId, Position.Around(messageId), limit) - - for (response in responses) { - val data = MessageData.from(response) - emit(Message(data, kord)) + override fun getMessagesAround(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { + require(limit in 1..100) { "Expected limit to be in 1..100, but was $limit" } + return flow { + val responses = channel.getMessages(channelId, Position.Around(messageId), limit) + for (response in responses) { + val data = MessageData.from(response) + emit(Message(data, kord)) + } } } @@ -179,7 +181,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } // maxBatchSize: see https://discord.com/developers/docs/resources/guild#list-guild-members - override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow = + override fun getGuildMembers(guildId: Snowflake, limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 1000) { batchSize -> paginateForwards(batchSize, idSelector = { it.user.value!!.id }) { after -> guild.getGuildMembers(guildId, after, limit = batchSize) @@ -220,7 +222,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } // maxBatchSize: see https://discord.com/developers/docs/resources/user#get-current-user-guilds - override fun getCurrentUserGuilds(limit: Int): Flow = + override fun getCurrentUserGuilds(limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 200) { batchSize -> paginateForwards(batchSize, idSelector = { it.id }) { after -> user.getCurrentUserGuilds(position = after, limit = batchSize) @@ -342,7 +344,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-public-archived-threads - override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow = + override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> paginateThreads(batchSize, start = before) { beforeTimestamp -> val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) @@ -354,7 +356,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-private-archived-threads - override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow = + override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> paginateThreads(batchSize, start = before) { beforeTimestamp -> val request = ListThreadsByTimestampRequest(before = beforeTimestamp, limit = batchSize) @@ -368,10 +370,10 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads override fun getJoinedPrivateArchivedThreads( channelId: Snowflake, - before: Snowflake, - limit: Int, + before: Snowflake?, + limit: Int?, ): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> - paginateBackwards(batchSize, start = before, idSelector = { it.id }) { beforePosition -> + paginateBackwards(batchSize, start = before ?: Snowflake.max, idSelector = { it.id }) { beforePosition -> val request = ListThreadsBySnowflakeRequest(before = beforePosition.value, limit = batchSize) channel.listJoinedPrivateArchivedThreads(channelId, request).threads }.mapNotNull { @@ -503,15 +505,15 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } -private fun checkLimitAndGetBatchSize(limit: Int, maxBatchSize: Int): Int { - require(limit > 0) { "At least 1 item should be requested, but got $limit." } - return min(limit, maxBatchSize) +private fun checkLimitAndGetBatchSize(limit: Int?, maxBatchSize: Int): Int { + require(limit == null || limit > 0) { "At least 1 item should be requested, but got $limit." } + return if (limit == null) maxBatchSize else min(limit, maxBatchSize) } -private fun Flow.limitPagination(limit: Int): Flow = if (limit == Int.MAX_VALUE) this else take(limit) +private fun Flow.limitPagination(limit: Int?): Flow = if (limit == null) this else take(limit) private inline fun limitedPagination( - limit: Int, + limit: Int?, maxBatchSize: Int, paginationCreator: (batchSize: Int) -> Flow, ): Flow { diff --git a/core/src/main/kotlin/supplier/CacheAwareRestSupplier.kt b/core/src/main/kotlin/supplier/StoreEntitySupplier.kt similarity index 93% rename from core/src/main/kotlin/supplier/CacheAwareRestSupplier.kt rename to core/src/main/kotlin/supplier/StoreEntitySupplier.kt index 93b795448029..e217cbed9873 100644 --- a/core/src/main/kotlin/supplier/CacheAwareRestSupplier.kt +++ b/core/src/main/kotlin/supplier/StoreEntitySupplier.kt @@ -24,9 +24,15 @@ import kotlinx.datetime.Instant public class StoreEntitySupplier( private val supplier: EntitySupplier, private val cache: DataCache, - private val kord: Kord ) : EntitySupplier { + @Deprecated( + "'kord' parameter is unused, use other constructor instead.", + ReplaceWith("StoreEntitySupplier(supplier, cache)"), + DeprecationLevel.ERROR, + ) + @Suppress("UNUSED_PARAMETER") + public constructor(supplier: EntitySupplier, cache: DataCache, kord: Kord) : this(supplier, cache) override val guilds: Flow get() = storeOnEach(supplier.guilds) { it.data } @@ -69,11 +75,11 @@ public class StoreEntitySupplier( return storeAndReturn(supplier.getMessageOrNull(channelId, messageId)) { it.data } } - override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { + override fun getMessagesAfter(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow { return storeOnEach(supplier.getMessagesAfter(messageId, channelId, limit)) { it.data } } - override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int): Flow { + override fun getMessagesBefore(messageId: Snowflake, channelId: Snowflake, limit: Int?): Flow { return storeOnEach(supplier.getMessagesBefore(messageId, channelId, limit)) { it.data } } @@ -105,7 +111,7 @@ public class StoreEntitySupplier( return storeOnEach(supplier.getGuildBans(guildId)) { it.data } } - override fun getGuildMembers(guildId: Snowflake, limit: Int): Flow { + override fun getGuildMembers(guildId: Snowflake, limit: Int?): Flow { return storeOnEach(supplier.getGuildMembers(guildId, limit)) { it.data } } @@ -122,7 +128,7 @@ public class StoreEntitySupplier( } - override fun getCurrentUserGuilds(limit: Int): Flow { + override fun getCurrentUserGuilds(limit: Int?): Flow { return storeOnEach(supplier.getCurrentUserGuilds(limit)) { it.data } } @@ -163,18 +169,18 @@ public class StoreEntitySupplier( return storeOnEach(supplier.getActiveThreads(guildId)) { it.data } } - override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { + override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow { return storeOnEach(supplier.getPublicArchivedThreads(channelId, before, limit)) { it.data } } - override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant, limit: Int): Flow { + override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow { return storeOnEach(supplier.getPrivateArchivedThreads(channelId, before, limit)) { it.data } } override fun getJoinedPrivateArchivedThreads( channelId: Snowflake, - before: Snowflake, - limit: Int + before: Snowflake?, + limit: Int?, ): Flow { return storeOnEach(supplier.getJoinedPrivateArchivedThreads(channelId, before, limit)) { it.data } } diff --git a/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt b/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt index f676def19a98..fcb3bd6dc53c 100644 --- a/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt +++ b/rest/src/main/kotlin/builder/auditlog/AuditLogGetRequestBuilder.kt @@ -4,6 +4,7 @@ import dev.kord.common.entity.AuditLogEvent import dev.kord.common.entity.Snowflake import dev.kord.rest.builder.RequestBuilder import dev.kord.rest.json.request.AuditLogGetRequest +import dev.kord.rest.service.AuditLogService class AuditLogGetRequestBuilder : RequestBuilder { @@ -23,9 +24,13 @@ class AuditLogGetRequestBuilder : RequestBuilder { var before: Snowflake? = null /** - * How many entries are returned (default 50, minimum 1, maximum 100). + * How many entries are returned. + * + * When used in a [direct rest request][AuditLogService.getAuditLogs]: default 50, minimum 1, maximum 100. + * + * When used through pagination in core module: `null` means no limit, must be positive otherwise. */ - var limit: Int = 50 + var limit: Int? = null override fun toRequest(): AuditLogGetRequest = AuditLogGetRequest(userId, action, before, limit) } diff --git a/rest/src/main/kotlin/json/request/AuditLogRequests.kt b/rest/src/main/kotlin/json/request/AuditLogRequests.kt index 7d59e7ed51d0..b707e6540b3e 100644 --- a/rest/src/main/kotlin/json/request/AuditLogRequests.kt +++ b/rest/src/main/kotlin/json/request/AuditLogRequests.kt @@ -7,5 +7,5 @@ data class AuditLogGetRequest( val userId: Snowflake? = null, val action: AuditLogEvent? = null, val before: Snowflake? = null, - val limit: Int = 50, + val limit: Int? = null, ) diff --git a/rest/src/main/kotlin/service/AuditLogService.kt b/rest/src/main/kotlin/service/AuditLogService.kt index b5143eecac40..296652f92725 100644 --- a/rest/src/main/kotlin/service/AuditLogService.kt +++ b/rest/src/main/kotlin/service/AuditLogService.kt @@ -26,6 +26,6 @@ class AuditLogService(requestHandler: RequestHandler) : RestService(requestHandl request.userId?.let { parameter("user_id", it) } request.action?.let { parameter("action_type", "${it.value}") } request.before?.let { parameter("before", it) } - parameter("limit", "${request.limit}") + request.limit?.let { parameter("limit", it) } } } diff --git a/rest/src/main/kotlin/service/ChannelService.kt b/rest/src/main/kotlin/service/ChannelService.kt index cf8f7116b4ff..e4479f0e3809 100644 --- a/rest/src/main/kotlin/service/ChannelService.kt +++ b/rest/src/main/kotlin/service/ChannelService.kt @@ -34,14 +34,11 @@ class ChannelService(requestHandler: RequestHandler) : RestService(requestHandle return createMessage(channelId, multipartRequest) } - suspend fun getMessages(channelId: Snowflake, position: Position? = null, limit: Int = 50) = + suspend fun getMessages(channelId: Snowflake, position: Position? = null, limit: Int? = null) = call(Route.MessagesGet) { keys[Route.ChannelId] = channelId - if (position != null) { - parameter(position.key, position.value) - } - parameter("limit", "$limit") - + position?.let { parameter(it.key, it.value) } + limit?.let { parameter("limit", it) } } suspend fun getMessage(channelId: Snowflake, messageId: Snowflake) = call(Route.MessageGet) { @@ -148,16 +145,14 @@ class ChannelService(requestHandler: RequestHandler) : RestService(requestHandle messageId: Snowflake, emoji: String, after: Position.After? = null, - limit: Int = 25 + limit: Int? = null, ) = call(Route.ReactionsGet) { keys[Route.ChannelId] = channelId keys[Route.MessageId] = messageId keys[Route.Emoji] = emoji - if (after != null) { - parameter(after.key, after.value) - } - parameter("limit", "$limit") + after?.let { parameter(it.key, it.value) } + limit?.let { parameter("limit", it) } } suspend fun triggerTypingIndicator(channelId: Snowflake) = call(Route.TypingIndicatorPost) { diff --git a/rest/src/main/kotlin/service/GuildService.kt b/rest/src/main/kotlin/service/GuildService.kt index 12d0adf23d4b..adf11c4bc2e7 100644 --- a/rest/src/main/kotlin/service/GuildService.kt +++ b/rest/src/main/kotlin/service/GuildService.kt @@ -136,25 +136,23 @@ class GuildService(requestHandler: RequestHandler) : RestService(requestHandler) keys[Route.UserId] = userId } - suspend fun getGuildMembers(guildId: Snowflake, after: Position.After? = null, limit: Int = 1) = + suspend fun getGuildMembers(guildId: Snowflake, after: Position.After? = null, limit: Int? = null) = call(Route.GuildMembersGet) { keys[Route.GuildId] = guildId - if (after != null) { - parameter(after.key, after.value) - } - parameter("limit", "$limit") + after?.let { parameter(it.key, it.value) } + limit?.let { parameter("limit", it) } } /** - * Requests members with a username or nickname matching [query]. + * Requests members with a username or nickname starting with [query]. * * @param limit limits the maximum amount of members returned. Max `1000`, defaults to `1`. */ @KordExperimental - suspend fun getGuildMembers(guildId: Snowflake, query: String, limit: Int = 1) = call(Route.GuildMembersSearchGet) { + suspend fun getGuildMembers(guildId: Snowflake, query: String, limit: Int? = null) = call(Route.GuildMembersSearchGet) { keys[Route.GuildId] = guildId parameter("query", query) - parameter("limit", "$limit") + limit?.let { parameter("limit", it) } } suspend fun addGuildMember( diff --git a/rest/src/main/kotlin/service/UserService.kt b/rest/src/main/kotlin/service/UserService.kt index 03edfba0bf15..409830c7131f 100644 --- a/rest/src/main/kotlin/service/UserService.kt +++ b/rest/src/main/kotlin/service/UserService.kt @@ -22,14 +22,12 @@ class UserService(requestHandler: RequestHandler) : RestService(requestHandler) keys[Route.UserId] = userId } - suspend fun getCurrentUserGuilds(position: Position.BeforeOrAfter? = null, limit: Int = 200) = call(Route.CurrentUsersGuildsGet) { - if (position != null) { - parameter(position.key, position.value) + suspend fun getCurrentUserGuilds(position: Position.BeforeOrAfter? = null, limit: Int? = null) = + call(Route.CurrentUsersGuildsGet) { + position?.let { parameter(it.key, it.value) } + limit?.let { parameter("limit", it) } } - parameter("limit", "$limit") - } - suspend fun leaveGuild(guildId: Snowflake) = call(Route.GuildLeave) { keys[Route.GuildId] = guildId } From 20ebdf6ebda50c82c985a751fa7b22d3d68fa3cf Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sun, 16 Jan 2022 03:47:12 +0100 Subject: [PATCH 19/23] Clearer names in pagination --- core/src/main/kotlin/Util.kt | 54 ++++++++++--------- .../kotlin/supplier/StoreEntitySupplier.kt | 2 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index b9fde57f892c..f4bcd637a1b6 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -120,24 +120,25 @@ internal suspend fun Flow.indexOfFirstOrNull(predicate: suspend (T) -> Bo .singleOrNull()?.first } -internal fun , T, P : Position> paginate( +internal fun , Item, Direction : Position.BeforeOrAfter> paginate( start: Snowflake, batchSize: Int, - idSelector: (T) -> Snowflake, - itemSelector: (C) -> T?, - directionSelector: (Snowflake) -> P, - request: suspend (position: P) -> C, -): Flow = flow { - var position = directionSelector(start) + itemSelector: (Batch) -> Item?, + idSelector: (Item) -> Snowflake, + directionSelector: (Snowflake) -> Direction, + request: suspend (Direction) -> Batch, +): Flow = flow { + + var direction = directionSelector(start) while (true) { - val response = request(position) - for (item in response) emit(item) + val batch = request(direction) + for (item in batch) emit(item) - val id = itemSelector(response)?.let(idSelector) ?: break - position = directionSelector(id) + if (batch.size < batchSize) break - if (response.size < batchSize) break + val item = itemSelector(batch) ?: break + direction = directionSelector(idSelector(item)) } } @@ -186,8 +187,8 @@ internal fun paginateForwards( ): Flow = paginate( start, batchSize, - idSelector, itemSelector = youngestItem(idSelector), + idSelector, directionSelector = Position::After, request, ) @@ -202,8 +203,8 @@ internal fun paginateForwards( ): Flow = paginate( start, batchSize, - idSelector = { it.id }, itemSelector = youngestItem { it.id }, + idSelector = { it.id }, directionSelector = Position::After, request, ) @@ -219,8 +220,8 @@ internal fun paginateBackwards( ): Flow = paginate( start, batchSize, - idSelector, itemSelector = oldestItem(idSelector), + idSelector, directionSelector = Position::Before, request, ) @@ -235,8 +236,8 @@ internal fun paginateBackwards( ): Flow = paginate( start, batchSize, - idSelector = { it.id }, itemSelector = oldestItem { it.id }, + idSelector = { it.id }, directionSelector = Position::Before, request, ) @@ -249,27 +250,28 @@ internal fun paginateBackwards( * * [Collection]'s size fall behind [batchSize]. * * [instantSelector] returns null. */ -internal fun , T> paginateByDate( +internal fun , Item> paginateByDate( batchSize: Int, start: Instant?, - instantSelector: (C) -> Instant?, - request: suspend (Instant) -> C, -): Flow = flow { + instantSelector: (Batch) -> Instant?, + request: suspend (Instant) -> Batch, +): Flow = flow { + + var currentTimestamp = start ?: Clock.System.now() // get default current time as late as possible - var currentTimestamp = start while (true) { - val response = request(currentTimestamp ?: Clock.System.now()) // get default current time as late as possible + val batch = request(currentTimestamp) + for (item in batch) emit(item) - for (item in response) emit(item) + if (batch.size < batchSize) break - currentTimestamp = instantSelector(response) ?: break - if (response.size < batchSize) break + currentTimestamp = instantSelector(batch) ?: break } } /** * A special function to paginate [ThreadChannel] endpoints. - * selects the earliest time reference found in the response of the request on each pagination. + * selects the earliest reference in time found in the response of the request on each pagination. * see [paginateByDate] */ internal fun paginateThreads( diff --git a/core/src/main/kotlin/supplier/StoreEntitySupplier.kt b/core/src/main/kotlin/supplier/StoreEntitySupplier.kt index e217cbed9873..e28fd7b26269 100644 --- a/core/src/main/kotlin/supplier/StoreEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/StoreEntitySupplier.kt @@ -27,7 +27,7 @@ public class StoreEntitySupplier( ) : EntitySupplier { @Deprecated( - "'kord' parameter is unused, use other constructor instead.", + "Parameter 'kord' is unused, use other constructor instead.", ReplaceWith("StoreEntitySupplier(supplier, cache)"), DeprecationLevel.ERROR, ) From 8d91842e7add104bf1f1c08dbfe45bd240181cae Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sun, 16 Jan 2022 05:16:22 +0100 Subject: [PATCH 20/23] Cannot paginate nullable items --- core/src/main/kotlin/Util.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/Util.kt b/core/src/main/kotlin/Util.kt index f4bcd637a1b6..1b03376545d2 100644 --- a/core/src/main/kotlin/Util.kt +++ b/core/src/main/kotlin/Util.kt @@ -120,7 +120,7 @@ internal suspend fun Flow.indexOfFirstOrNull(predicate: suspend (T) -> Bo .singleOrNull()?.first } -internal fun , Item, Direction : Position.BeforeOrAfter> paginate( +internal fun , Item : Any, Direction : Position.BeforeOrAfter> paginate( start: Snowflake, batchSize: Int, itemSelector: (Batch) -> Item?, @@ -179,7 +179,7 @@ internal fun oldestItem(idSelector: (T) -> Snowflake): (Collection) -> T? /** * Selects the [Position.After] the youngest item in the batch. */ -internal fun paginateForwards( +internal fun paginateForwards( batchSize: Int, start: Snowflake = Snowflake.min, idSelector: (T) -> Snowflake, @@ -212,7 +212,7 @@ internal fun paginateForwards( /** * Selects the [Position.Before] the oldest item in the batch. */ -internal fun paginateBackwards( +internal fun paginateBackwards( batchSize: Int, start: Snowflake = Snowflake.max, idSelector: (T) -> Snowflake, @@ -250,7 +250,7 @@ internal fun paginateBackwards( * * [Collection]'s size fall behind [batchSize]. * * [instantSelector] returns null. */ -internal fun , Item> paginateByDate( +internal fun , Item : Any> paginateByDate( batchSize: Int, start: Instant?, instantSelector: (Batch) -> Instant?, From 7b97dcd91077ea786576d9ea91456492482918ae Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sun, 16 Jan 2022 05:23:53 +0100 Subject: [PATCH 21/23] Clarify no maxBatchSize documented --- core/src/main/kotlin/supplier/RestEntitySupplier.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index a30f65b54da0..b0b2e3a5f67b 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -343,7 +343,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } } - // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-public-archived-threads + // no maxBatchSize documented (but api errors say it's 100): see https://discord.com/developers/docs/resources/channel#list-public-archived-threads override fun getPublicArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> paginateThreads(batchSize, start = before) { beforeTimestamp -> @@ -355,7 +355,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } } - // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-private-archived-threads + // no maxBatchSize documented (but api errors say it's 100): see https://discord.com/developers/docs/resources/channel#list-private-archived-threads override fun getPrivateArchivedThreads(channelId: Snowflake, before: Instant?, limit: Int?): Flow = limitedPagination(limit, maxBatchSize = 100) { batchSize -> paginateThreads(batchSize, start = before) { beforeTimestamp -> @@ -367,7 +367,7 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { } } - // no maxBatchSize documented: see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads + // no maxBatchSize documented (but api errors say it's 100): see https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads override fun getJoinedPrivateArchivedThreads( channelId: Snowflake, before: Snowflake?, From 25fa827c1cdbb235a0dfa5a6cd7a69ef8f61f92e Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Sun, 16 Jan 2022 16:29:33 +0100 Subject: [PATCH 22/23] Move map one more level in RestEntitySupplier.getJoinedPrivateArchivedThreads() --- core/src/main/kotlin/supplier/RestEntitySupplier.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/supplier/RestEntitySupplier.kt b/core/src/main/kotlin/supplier/RestEntitySupplier.kt index b0b2e3a5f67b..2f2cb3163fa5 100644 --- a/core/src/main/kotlin/supplier/RestEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/RestEntitySupplier.kt @@ -376,10 +376,10 @@ public class RestEntitySupplier(public val kord: Kord) : EntitySupplier { paginateBackwards(batchSize, start = before ?: Snowflake.max, idSelector = { it.id }) { beforePosition -> val request = ListThreadsBySnowflakeRequest(before = beforePosition.value, limit = batchSize) channel.listJoinedPrivateArchivedThreads(channelId, request).threads - }.mapNotNull { - val data = ChannelData.from(it) - Channel.from(data, kord) as? ThreadChannel } + }.mapNotNull { + val data = ChannelData.from(it) + Channel.from(data, kord) as? ThreadChannel } override fun getGuildApplicationCommands( From f942ca2914825c15ac03c1694c96ca0ca6cc6662 Mon Sep 17 00:00:00 2001 From: Lukellmann Date: Mon, 17 Jan 2022 20:45:51 +0100 Subject: [PATCH 23/23] idLt for consistency --- core/src/main/kotlin/cache/Query.kt | 4 ++++ core/src/main/kotlin/supplier/CacheEntitySupplier.kt | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/cache/Query.kt b/core/src/main/kotlin/cache/Query.kt index 3b1802fd7fa8..43328bc17884 100644 --- a/core/src/main/kotlin/cache/Query.kt +++ b/core/src/main/kotlin/cache/Query.kt @@ -29,6 +29,10 @@ public fun QueryBuilder.idGt(property: KProperty1, va property.gt(value) } +public fun QueryBuilder.idLt(property: KProperty1, value: Snowflake) { + property.lt(value) +} + @JvmName("stringEq") public fun QueryBuilder.idEq(property: KProperty1, value: String?) { property.eq(value) diff --git a/core/src/main/kotlin/supplier/CacheEntitySupplier.kt b/core/src/main/kotlin/supplier/CacheEntitySupplier.kt index 2a685ede6d12..27dd36b020ec 100644 --- a/core/src/main/kotlin/supplier/CacheEntitySupplier.kt +++ b/core/src/main/kotlin/supplier/CacheEntitySupplier.kt @@ -10,6 +10,8 @@ import dev.kord.core.Kord import dev.kord.core.any import dev.kord.core.cache.data.* import dev.kord.core.cache.idEq +import dev.kord.core.cache.idGt +import dev.kord.core.cache.idLt import dev.kord.core.entity.* import dev.kord.core.entity.application.ApplicationCommandPermissions import dev.kord.core.entity.application.GlobalApplicationCommand @@ -151,7 +153,7 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { checkLimit(limit) return cache.query { idEq(MessageData::channelId, channelId) - MessageData::id gt messageId + idGt(MessageData::id, messageId) }.asFlow().map { Message(it, kord) }.limit(limit) } @@ -159,7 +161,7 @@ public class CacheEntitySupplier(private val kord: Kord) : EntitySupplier { checkLimit(limit) return cache.query { idEq(MessageData::channelId, channelId) - MessageData::id lt messageId + idLt(MessageData::id, messageId) }.asFlow().map { Message(it, kord) }.limit(limit) }