From 9ce6b6a04af63a5badd498bc9bae1c1c4243dcc1 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister Date: Sun, 27 Aug 2023 13:47:46 +0200 Subject: [PATCH] Add WebhookClient mode and unify builders - Extract webhook related functions of Kord into WebhookClient - Extract webhook constructor of Unsafe to WebhookUnsafe - Add AbstractKordBuilder - Add stackTraceRecovery to AbstractKordBuilder and therefore restOnly and webhook mode --- core/api/core.api | 103 ++++++++++++------ core/src/commonMain/kotlin/Kord.kt | 48 ++++++-- core/src/commonMain/kotlin/Unsafe.kt | 4 +- core/src/commonMain/kotlin/WebhookClient.kt | 84 ++++++++++++++ .../builder/kord/AbstractKordBuilder.kt | 86 +++++++++++++++ .../kotlin/builder/kord/KordBuilder.kt | 57 +--------- .../kotlin/builder/kord/KordProxyBuilder.kt | 2 +- .../builder/kord/KordRestOnlyBuilder.kt | 11 +- .../kotlin/builder/kord/RestOnlyBuilder.kt | 43 +------- .../builder/kord/WebhookClientBuilder.kt | 50 +++++++++ 10 files changed, 338 insertions(+), 150 deletions(-) create mode 100644 core/src/commonMain/kotlin/WebhookClient.kt create mode 100644 core/src/commonMain/kotlin/builder/kord/AbstractKordBuilder.kt create mode 100644 core/src/commonMain/kotlin/builder/kord/WebhookClientBuilder.kt diff --git a/core/api/core.api b/core/api/core.api index a5cc99f4ea5..75c8381bf52 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -9,7 +9,7 @@ public final class dev/kord/core/ClientResources { public fun toString ()Ljava/lang/String; } -public final class dev/kord/core/Kord : kotlinx/coroutines/CoroutineScope { +public final class dev/kord/core/Kord : dev/kord/core/WebhookClient, kotlinx/coroutines/CoroutineScope { public static final field Companion Ldev/kord/core/Kord$Companion; public fun (Ldev/kord/core/ClientResources;Ldev/kord/cache/api/DataCache;Ldev/kord/core/gateway/MasterGateway;Ldev/kord/rest/service/RestClient;Ldev/kord/common/entity/Snowflake;Lkotlinx/coroutines/flow/MutableSharedFlow;Lkotlinx/coroutines/CoroutineDispatcher;Ldev/kord/core/gateway/handler/GatewayEventInterceptor;)V public final fun createGlobalApplicationCommands (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -66,23 +66,20 @@ public final class dev/kord/core/Kord : kotlinx/coroutines/CoroutineScope { public static synthetic fun getInviteOrNull$default (Ldev/kord/core/Kord;Ljava/lang/String;ZZLdev/kord/common/entity/Snowflake;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun getNitroStickerPacks ()Lkotlinx/coroutines/flow/Flow; public final fun getRegions ()Lkotlinx/coroutines/flow/Flow; - public final fun getResources ()Ldev/kord/core/ClientResources; + public fun getResources ()Ldev/kord/core/ClientResources; public final fun getRest ()Ldev/kord/rest/service/RestClient; public final fun getSelf (Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getSelf$default (Ldev/kord/core/Kord;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun getSelfId ()Ldev/kord/common/entity/Snowflake; public final fun getSticker (Ldev/kord/common/entity/Snowflake;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getUnsafe ()Ldev/kord/core/Unsafe; + public fun getUnsafe ()Ldev/kord/core/Unsafe; + public synthetic fun getUnsafe ()Ldev/kord/core/WebhookUnsafe; public final fun getUser (Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getUser$default (Ldev/kord/core/Kord;Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getWebhook (Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun getWebhook$default (Ldev/kord/core/Kord;Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getWebhookOrNull (Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun getWebhookOrNull$default (Ldev/kord/core/Kord;Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getWebhookWithToken (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun getWebhookWithToken$default (Ldev/kord/core/Kord;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getWebhookWithTokenOrNull (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun getWebhookWithTokenOrNull$default (Ldev/kord/core/Kord;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun getWebhook (Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getWebhookOrNull (Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getWebhookWithToken (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getWebhookWithTokenOrNull (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun hashCode ()I public final fun login (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun login$default (Ldev/kord/core/Kord;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; @@ -98,6 +95,7 @@ public final class dev/kord/core/Kord$Companion { public static synthetic fun proxy$default (Ldev/kord/core/Kord$Companion;Ldev/kord/common/entity/Snowflake;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/kord/core/Kord; public final fun restOnly (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/kord/core/Kord; public static synthetic fun restOnly$default (Ldev/kord/core/Kord$Companion;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/kord/core/Kord; + public final fun webhookClient (Lkotlin/jvm/functions/Function1;)Ldev/kord/core/WebhookClient; } public final class dev/kord/core/KordKt { @@ -110,7 +108,7 @@ public abstract interface class dev/kord/core/KordObject { public abstract fun getKord ()Ldev/kord/core/Kord; } -public final class dev/kord/core/Unsafe { +public final class dev/kord/core/Unsafe : dev/kord/core/WebhookUnsafe { public fun (Ldev/kord/core/Kord;)V public final fun applicationCommandInteraction (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/interaction/ApplicationCommandInteractionBehavior; public final fun autoModerationRule (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/automoderation/AutoModerationRuleBehavior; @@ -149,7 +147,7 @@ public final class dev/kord/core/Unsafe { public final fun topGuildMessageChannel (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/channel/TopGuildMessageChannelBehavior; public final fun user (Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/UserBehavior; public final fun voiceChannel (Ldev/kord/common/entity/Snowflake;Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/channel/VoiceChannelBehavior; - public final fun webhook (Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/WebhookBehavior; + public fun webhook (Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/WebhookBehavior; } public final class dev/kord/core/UtilKt { @@ -158,6 +156,26 @@ public final class dev/kord/core/UtilKt { public static final fun enableEvents (Ldev/kord/gateway/Intents$IntentsBuilder;[Lkotlin/reflect/KClass;)V } +public abstract interface class dev/kord/core/WebhookClient { + public abstract fun getResources ()Ldev/kord/core/ClientResources; + public abstract fun getUnsafe ()Ldev/kord/core/WebhookUnsafe; + public abstract fun getWebhook (Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getWebhookOrNull (Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getWebhookWithToken (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getWebhookWithTokenOrNull (Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/kord/core/WebhookClient$DefaultImpls { + public static synthetic fun getWebhook$default (Ldev/kord/core/WebhookClient;Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun getWebhookOrNull$default (Ldev/kord/core/WebhookClient;Ldev/kord/common/entity/Snowflake;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun getWebhookWithToken$default (Ldev/kord/core/WebhookClient;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun getWebhookWithTokenOrNull$default (Ldev/kord/core/WebhookClient;Ldev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/core/supplier/EntitySupplyStrategy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public abstract interface class dev/kord/core/WebhookUnsafe { + public abstract fun webhook (Ldev/kord/common/entity/Snowflake;)Ldev/kord/core/behavior/WebhookBehavior; +} + public abstract interface class dev/kord/core/behavior/ApplicationCommandBehavior : dev/kord/core/entity/Entity { public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getApplicationId ()Ldev/kord/common/entity/Snowflake; @@ -2073,30 +2091,48 @@ public final class dev/kord/core/builder/components/ButtonBuilderExtensionsKt { public static final fun emoji (Ldev/kord/rest/builder/component/ButtonBuilder;Ldev/kord/core/entity/ReactionEmoji$Unicode;)V } -public abstract class dev/kord/core/builder/kord/BaseKordBuilder { +public abstract class dev/kord/core/builder/kord/AbstractKordBuilder { + protected final fun buildRequestHandler (Ldev/kord/core/ClientResources;)Ldev/kord/rest/request/RequestHandler; + public final fun getDefaultDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; + protected final fun getHandlerBuilder ()Lkotlin/jvm/functions/Function1; + public final fun getHttpClient ()Lio/ktor/client/HttpClient; + public final fun getStackTraceRecovery ()Z + public final fun requestHandler (Lkotlin/jvm/functions/Function1;)V + public final fun setDefaultDispatcher (Lkotlinx/coroutines/CoroutineDispatcher;)V + protected final fun setHandlerBuilder (Lkotlin/jvm/functions/Function1;)V + public final fun setHttpClient (Lio/ktor/client/HttpClient;)V + public final fun setStackTraceRecovery (Z)V +} + +public abstract class dev/kord/core/builder/kord/BaseKordBuilder : dev/kord/core/builder/kord/AbstractKordBuilder, dev/kord/core/builder/kord/HasApplication { public fun build (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected final fun buildBase (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun cache (Lkotlin/jvm/functions/Function2;)V public final fun gateways (Lkotlin/jvm/functions/Function2;)V - public final fun getApplicationId ()Ldev/kord/common/entity/Snowflake; - public final fun getDefaultDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; + public fun getActualApplicationId ()Ldev/kord/common/entity/Snowflake; + public fun getApplicationId ()Ldev/kord/common/entity/Snowflake; public final fun getDefaultStrategy ()Ldev/kord/core/supplier/EntitySupplyStrategy; public final fun getEventFlow ()Lkotlinx/coroutines/flow/MutableSharedFlow; public final fun getGatewayEventInterceptor ()Ldev/kord/core/gateway/handler/GatewayEventInterceptor; - public final fun getHttpClient ()Lio/ktor/client/HttpClient; - public final fun getStackTraceRecovery ()Z - public final fun getToken ()Ljava/lang/String; - public final fun requestHandler (Lkotlin/jvm/functions/Function1;)V - public final fun setApplicationId (Ldev/kord/common/entity/Snowflake;)V - public final fun setDefaultDispatcher (Lkotlinx/coroutines/CoroutineDispatcher;)V + public fun getToken ()Ljava/lang/String; + public fun setApplicationId (Ldev/kord/common/entity/Snowflake;)V public final fun setDefaultStrategy (Ldev/kord/core/supplier/EntitySupplyStrategy;)V public final fun setEventFlow (Lkotlinx/coroutines/flow/MutableSharedFlow;)V public final fun setGatewayEventInterceptor (Ldev/kord/core/gateway/handler/GatewayEventInterceptor;)V - public final fun setHttpClient (Lio/ktor/client/HttpClient;)V - public final fun setStackTraceRecovery (Z)V public final fun sharding (Lkotlin/jvm/functions/Function1;)V } +public abstract interface class dev/kord/core/builder/kord/HasApplication { + public abstract fun getActualApplicationId ()Ldev/kord/common/entity/Snowflake; + public abstract fun getApplicationId ()Ldev/kord/common/entity/Snowflake; + public abstract fun getToken ()Ljava/lang/String; + public abstract fun setApplicationId (Ldev/kord/common/entity/Snowflake;)V +} + +public final class dev/kord/core/builder/kord/HasApplication$DefaultImpls { + public static fun getActualApplicationId (Ldev/kord/core/builder/kord/HasApplication;)Ldev/kord/common/entity/Snowflake; +} + public final class dev/kord/core/builder/kord/KordBuilder : dev/kord/core/builder/kord/BaseKordBuilder { public fun (Ljava/lang/String;)V public fun build (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -2110,6 +2146,7 @@ public final class dev/kord/core/builder/kord/KordBuilderUtilKt { public final class dev/kord/core/builder/kord/KordProxyBuilder : dev/kord/core/builder/kord/RestOnlyBuilder { public fun (Ldev/kord/common/entity/Snowflake;)V public fun getApplicationId ()Ldev/kord/common/entity/Snowflake; + public fun getToken ()Ljava/lang/String; public fun setApplicationId (Ldev/kord/common/entity/Snowflake;)V } @@ -2121,19 +2158,15 @@ public final class dev/kord/core/builder/kord/KordRestOnlyBuilder : dev/kord/cor public fun setToken (Ljava/lang/String;)V } -public abstract class dev/kord/core/builder/kord/RestOnlyBuilder { +public abstract class dev/kord/core/builder/kord/RestOnlyBuilder : dev/kord/core/builder/kord/AbstractKordBuilder, dev/kord/core/builder/kord/HasApplication { public fun ()V public final fun build ()Ldev/kord/core/Kord; - public abstract fun getApplicationId ()Ldev/kord/common/entity/Snowflake; - public final fun getDefaultDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; - protected final fun getHandlerBuilder ()Lkotlin/jvm/functions/Function1; - public final fun getHttpClient ()Lio/ktor/client/HttpClient; - protected abstract fun getToken ()Ljava/lang/String; - public final fun requestHandler (Lkotlin/jvm/functions/Function1;)V - public abstract fun setApplicationId (Ldev/kord/common/entity/Snowflake;)V - public final fun setDefaultDispatcher (Lkotlinx/coroutines/CoroutineDispatcher;)V - protected final fun setHandlerBuilder (Lkotlin/jvm/functions/Function1;)V - public final fun setHttpClient (Lio/ktor/client/HttpClient;)V + public fun getActualApplicationId ()Ldev/kord/common/entity/Snowflake; +} + +public final class dev/kord/core/builder/kord/WebhookClientBuilder : dev/kord/core/builder/kord/AbstractKordBuilder { + public fun ()V + public final fun build ()Ldev/kord/core/WebhookClient; } public final class dev/kord/core/cache/CachingGateway : dev/kord/cache/api/DataCache, dev/kord/gateway/Gateway, kotlinx/coroutines/CoroutineScope { diff --git a/core/src/commonMain/kotlin/Kord.kt b/core/src/commonMain/kotlin/Kord.kt index 60c217cd447..ca5f10657b5 100644 --- a/core/src/commonMain/kotlin/Kord.kt +++ b/core/src/commonMain/kotlin/Kord.kt @@ -9,6 +9,7 @@ import dev.kord.common.exception.RequestException import dev.kord.core.builder.kord.KordBuilder import dev.kord.core.builder.kord.KordProxyBuilder import dev.kord.core.builder.kord.KordRestOnlyBuilder +import dev.kord.core.builder.kord.WebhookClientBuilder import dev.kord.core.cache.data.ApplicationCommandData import dev.kord.core.cache.data.GuildData import dev.kord.core.cache.data.UserData @@ -48,7 +49,7 @@ public val kordLogger: KLogger = KotlinLogging.logger { } * The central adapter between other Kord modules and source of core [events]. */ public class Kord( - public val resources: ClientResources, + public override val resources: ClientResources, public val cache: DataCache, public val gateway: MasterGateway, public val rest: RestClient, @@ -56,7 +57,7 @@ public class Kord( private val eventFlow: MutableSharedFlow, dispatcher: CoroutineDispatcher, private val interceptor: GatewayEventInterceptor, -) : CoroutineScope { +) : CoroutineScope, WebhookClient { public val nitroStickerPacks: Flow get() = defaultSupplier.getNitroStickerPacks() @@ -74,7 +75,7 @@ public class Kord( * A reference to all exposed [unsafe][KordUnsafe] entity constructors for this instance. */ @OptIn(KordUnsafe::class) - public val unsafe: Unsafe = Unsafe(this) + public override val unsafe: Unsafe = Unsafe(this) /** * The events emitted from the [gateway]. Call [Kord.login] to start receiving events. @@ -272,9 +273,9 @@ public class Kord( * @throws [RestRequestException] if something went wrong during the request. * @throws [EntityNotFoundException] if the webhook was not present. */ - public suspend fun getWebhook( + override suspend fun getWebhook( id: Snowflake, - strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + strategy: EntitySupplyStrategy<*> ): Webhook = strategy.supply(this).getWebhook(id) /** @@ -284,9 +285,9 @@ public class Kord( * @throws [RestRequestException] if something went wrong during the request. */ - public suspend fun getWebhookOrNull( + override suspend fun getWebhookOrNull( id: Snowflake, - strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + strategy: EntitySupplyStrategy<*> ): Webhook? = strategy.supply(this).getWebhookOrNull(id) /** @@ -295,10 +296,10 @@ public class Kord( * @throws [RestRequestException] if something went wrong during the request. * @throws [EntityNotFoundException] if the webhook was not present. */ - public suspend fun getWebhookWithToken( + override suspend fun getWebhookWithToken( id: Snowflake, token: String, - strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + strategy: EntitySupplyStrategy<*> ): Webhook = strategy.supply(this).getWebhookWithToken(id, token) /** @@ -308,10 +309,10 @@ public class Kord( * @throws [RestRequestException] if something went wrong during the request. */ - public suspend fun getWebhookWithTokenOrNull( + override suspend fun getWebhookWithTokenOrNull( id: Snowflake, token: String, - strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + strategy: EntitySupplyStrategy<*> ): Webhook? = strategy.supply(this).getWebhookWithTokenOrNull(id, token) @@ -416,6 +417,31 @@ public class Kord( return KordRestOnlyBuilder(token).apply(builder).build() } + /** + * Builds a [WebhookClient] instance configured by the [builder]. + * + * This returns a [WebhookClient] instead of a normal [Kord] instance, the + * underlying [Kord] instance returned by [Webhook] objects will only work for + * webhook requests + * + * ```kotlin + * val client = Kord.webhookClient() + * val webhook = client.unsafe.webhook(Snowflake(1234)) + * + * webhook.execute(webhookToken) { + * content = "Cool message" + * } + * ``` + */ + @KordExperimental + public inline fun webhookClient(builder: WebhookClientBuilder.() -> Unit): WebhookClient { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } + + return WebhookClientBuilder().apply(builder).build() + } + /** * Builds a [Kord] instance configured by the [builder]. * diff --git a/core/src/commonMain/kotlin/Unsafe.kt b/core/src/commonMain/kotlin/Unsafe.kt index 269d0676fb5..93291feed5b 100644 --- a/core/src/commonMain/kotlin/Unsafe.kt +++ b/core/src/commonMain/kotlin/Unsafe.kt @@ -25,7 +25,7 @@ import dev.kord.rest.service.InteractionService */ @KordUnsafe @KordExperimental -public class Unsafe(private val kord: Kord) { +public class Unsafe(private val kord: Kord): WebhookUnsafe { public fun autoModerationRule(guildId: Snowflake, ruleId: Snowflake): AutoModerationRuleBehavior = AutoModerationRuleBehaviorImpl(guildId, ruleId, kord) @@ -107,7 +107,7 @@ public class Unsafe(private val kord: Kord) { public fun member(guildId: Snowflake, id: Snowflake): MemberBehavior = MemberBehavior(guildId = guildId, id = id, kord = kord) - public fun webhook(id: Snowflake): WebhookBehavior = + public override fun webhook(id: Snowflake): WebhookBehavior = WebhookBehavior(id, kord) public fun stageInstance(id: Snowflake, channelId: Snowflake): StageInstanceBehavior = StageInstanceBehavior( diff --git a/core/src/commonMain/kotlin/WebhookClient.kt b/core/src/commonMain/kotlin/WebhookClient.kt new file mode 100644 index 00000000000..53cca443d01 --- /dev/null +++ b/core/src/commonMain/kotlin/WebhookClient.kt @@ -0,0 +1,84 @@ +package dev.kord.core + +import dev.kord.common.annotation.KordUnsafe +import dev.kord.common.entity.Snowflake +import dev.kord.core.behavior.WebhookBehavior +import dev.kord.core.entity.Webhook +import dev.kord.core.exception.EntityNotFoundException +import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kord.rest.request.RestRequestException + +/** + * Client for interacting with webhooks. + */ +public interface WebhookClient { + public val resources: ClientResources + + /** + * A reference to [unsafe][WebhookUnsafe] [Webhook] constructors. + */ + @KordUnsafe + public val unsafe: WebhookUnsafe + + /** + * Requests to get the [Webhook] in this guild. + * + * @throws [RestRequestException] if something went wrong during the request. + * @throws [EntityNotFoundException] if the webhook was not present. + */ + public suspend fun getWebhook( + id: Snowflake, + strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + ): Webhook + + /** + * Requests to get the [Webhook] in this guild with an authentication token, + * returns null if the webhook was not present. + * + * @throws [RestRequestException] if something went wrong during the request. + */ + + public suspend fun getWebhookOrNull( + id: Snowflake, + strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + ): Webhook? + + /** + * Requests to get the [Webhook] in this guild with an authentication token. + * + * @throws [RestRequestException] if something went wrong during the request. + * @throws [EntityNotFoundException] if the webhook was not present. + */ + public suspend fun getWebhookWithToken( + id: Snowflake, + token: String, + strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + ): Webhook + + /** + * Requests to get the [Webhook] in this guild with an authentication token, + * returns null if the webhook was not present. + * + * @throws [RestRequestException] if something went wrong during the request. + */ + public suspend fun getWebhookWithTokenOrNull( + id: Snowflake, + token: String, + strategy: EntitySupplyStrategy<*> = resources.defaultStrategy + ): Webhook? +} + +/** + * Unsafe constructor for [WebhookBehaviors][WebhookBehavior]. + * + * Using these won't check whether the underlying entity actually exists and might + * throw an [RestRequestException] if the id is invalid. + */ +@KordUnsafe +public interface WebhookUnsafe { + /** + * Constructs a new [WebhookBehavior] without checking whether the underlying + * Webhook exists. + */ + public fun webhook(id: Snowflake): WebhookBehavior +} diff --git a/core/src/commonMain/kotlin/builder/kord/AbstractKordBuilder.kt b/core/src/commonMain/kotlin/builder/kord/AbstractKordBuilder.kt new file mode 100644 index 00000000000..c149bc9fb0b --- /dev/null +++ b/core/src/commonMain/kotlin/builder/kord/AbstractKordBuilder.kt @@ -0,0 +1,86 @@ +package dev.kord.core.builder.kord + +import dev.kord.common.annotation.KordInternal +import dev.kord.common.entity.Snowflake +import dev.kord.core.ClientResources +import dev.kord.gateway.Gateway +import dev.kord.rest.ratelimit.ExclusionRequestRateLimiter +import dev.kord.rest.request.KtorRequestHandler +import dev.kord.rest.request.RequestHandler +import dev.kord.rest.request.StackTraceRecoveringKtorRequestHandler +import dev.kord.rest.request.withStackTraceRecovery +import dev.kord.rest.service.RestClient +import io.ktor.client.* +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +/** + * Abstract base for all Kord builders. + */ +public sealed class AbstractKordBuilder { + protected var handlerBuilder: (resources: ClientResources) -> RequestHandler = + { KtorRequestHandler(it.httpClient, ExclusionRequestRateLimiter(), token = it.token) } + + /** + * The [CoroutineDispatcher] kord uses to launch suspending tasks. [Dispatchers.Default] by default. + */ + public var defaultDispatcher: CoroutineDispatcher = Dispatchers.Default + + /** + * The client used for building [Gateways][Gateway] and [RequestHandlers][RequestHandler]. A default implementation + * will be used when not set. + */ + public var httpClient: HttpClient? = null + + + /** + * Enables stack trace recovery on the currently defined [RequestHandler]. + * + * @throws IllegalStateException if the [RequestHandler] is not a [KtorRequestHandler] + * + * @see StackTraceRecoveringKtorRequestHandler + * @see withStackTraceRecovery + */ + public var stackTraceRecovery: Boolean = false + + /** + * Configures the [RequestHandler] for the [RestClient]. + * + * ``` + * Kord(token) { + * requestHandler { resources -> KtorRequestHandler(resources.httpClient, ExclusionRequestRateLimiter()) } + * } + * ``` + */ + public fun requestHandler(handlerBuilder: (resources: ClientResources) -> RequestHandler) { + this.handlerBuilder = handlerBuilder + } + + protected fun buildRequestHandler(resources: ClientResources): RequestHandler { + val rawRequestHandler = handlerBuilder(resources) + return if (stackTraceRecovery) { + if (rawRequestHandler is KtorRequestHandler) { + rawRequestHandler.withStackTraceRecovery() + } else { + error("stackTraceRecovery only works with KtorRequestHandlers, please set stackTraceRecovery = false or use a different RequestHandler") + } + } else { + rawRequestHandler + } + } +} + +/** + * Interface supposed to be used together with [AbstractKordBuilder] to add a application related information. + * + * @property token the token used for authentication + * @property applicationId the id of the application + */ +public interface HasApplication { + public val token: String + public var applicationId: Snowflake? + + @KordInternal + public val actualApplicationId: Snowflake + get() = applicationId ?: getBotIdFromToken(token) +} diff --git a/core/src/commonMain/kotlin/builder/kord/KordBuilder.kt b/core/src/commonMain/kotlin/builder/kord/KordBuilder.kt index 287de6521dc..e83b8d0b684 100644 --- a/core/src/commonMain/kotlin/builder/kord/KordBuilder.kt +++ b/core/src/commonMain/kotlin/builder/kord/KordBuilder.kt @@ -20,7 +20,6 @@ import dev.kord.gateway.Gateway import dev.kord.gateway.builder.Shards import dev.kord.gateway.ratelimit.IdentifyRateLimiter import dev.kord.rest.json.response.BotGatewayResponse -import dev.kord.rest.ratelimit.ExclusionRequestRateLimiter import dev.kord.rest.request.* import dev.kord.rest.route.Route import dev.kord.rest.service.RestClient @@ -30,8 +29,6 @@ import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.http.HttpHeaders.Authorization import io.ktor.http.HttpHeaders.UserAgent -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.serialization.json.Json import mu.KotlinLogging @@ -43,7 +40,7 @@ private val gatewayInfoJson = Json { ignoreUnknownKeys = true } public expect class KordBuilder(token: String) : BaseKordBuilder -public abstract class BaseKordBuilder internal constructor(public val token: String) { +public abstract class BaseKordBuilder internal constructor(override val token: String) : AbstractKordBuilder(), HasApplication { private var shardsBuilder: (recommended: Int) -> Shards = { Shards(it) } private var gatewayBuilder: (resources: ClientResources, shards: List) -> List = { resources, shards -> @@ -57,20 +54,9 @@ public abstract class BaseKordBuilder internal constructor(public val token: Str } } - private var handlerBuilder: (resources: ClientResources) -> RequestHandler = - { KtorRequestHandler(it.httpClient, ExclusionRequestRateLimiter(), token = token) } + override var applicationId: Snowflake? = null private var cacheBuilder: KordCacheBuilder.(resources: ClientResources) -> Unit = {} - /** - * Enables stack trace recovery on the currently defined [RequestHandler]. - * - * @throws IllegalStateException if the [RequestHandler] is not a [KtorRequestHandler] - * - * @see StackTraceRecoveringKtorRequestHandler - * @see withStackTraceRecovery - */ - public var stackTraceRecovery: Boolean = false - /** * The event flow used by [Kord.eventFlow] to publish [events][Kord.events]. * @@ -81,23 +67,11 @@ public abstract class BaseKordBuilder internal constructor(public val token: Str extraBufferCapacity = Int.MAX_VALUE ) - /** - * The [CoroutineDispatcher] kord uses to launch suspending tasks. [Dispatchers.Default] by default. - */ - public var defaultDispatcher: CoroutineDispatcher = Dispatchers.Default - /** * The default strategy used by entities to retrieve entities. [EntitySupplyStrategy.cacheWithRestFallback] by default. */ public var defaultStrategy: EntitySupplyStrategy<*> = EntitySupplyStrategy.cacheWithRestFallback - /** - * The client used for building [Gateways][Gateway] and [RequestHandlers][RequestHandler]. A default implementation - * will be used when not set. - */ - public var httpClient: HttpClient? = null - - public var applicationId: Snowflake? = null /** * The [GatewayEventInterceptor] used for converting [gateway events][dev.kord.gateway.Event] to @@ -144,19 +118,6 @@ public abstract class BaseKordBuilder internal constructor(public val token: Str } - /** - * Configures the [RequestHandler] for the [RestClient]. - * - * ``` - * Kord(token) { - * { resources -> KtorRequestHandler(resources.httpClient, ExclusionRequestRateLimiter()) } - * } - * ``` - */ - public fun requestHandler(handlerBuilder: (resources: ClientResources) -> RequestHandler) { - this.handlerBuilder = handlerBuilder - } - /** * Configures the [DataCache] for caching. * @@ -231,22 +192,14 @@ public abstract class BaseKordBuilder internal constructor(public val token: Str val resources = ClientResources( token = token, - applicationId = applicationId ?: getBotIdFromToken(token), + applicationId = actualApplicationId, shards = shardsInfo, maxConcurrency = gatewayInfo.sessionStartLimit.maxConcurrency, httpClient = client, defaultStrategy = defaultStrategy, ) - val rawRequestHandler = handlerBuilder(resources) - val requestHandler = if (stackTraceRecovery) { - if (rawRequestHandler is KtorRequestHandler) { - rawRequestHandler.withStackTraceRecovery() - } else { - error("stackTraceRecovery only works with KtorRequestHandlers, please set stackTraceRecovery = false or use a different RequestHandler") - } - } else { - rawRequestHandler - } + val requestHandler = buildRequestHandler(resources) + val rest = RestClient(requestHandler) val cache = KordCacheBuilder().apply { cacheBuilder(resources) }.build() cache.registerKordData() diff --git a/core/src/commonMain/kotlin/builder/kord/KordProxyBuilder.kt b/core/src/commonMain/kotlin/builder/kord/KordProxyBuilder.kt index 6550afca39a..750f186237a 100644 --- a/core/src/commonMain/kotlin/builder/kord/KordProxyBuilder.kt +++ b/core/src/commonMain/kotlin/builder/kord/KordProxyBuilder.kt @@ -8,7 +8,7 @@ import dev.kord.core.Kord * The proxy Kord builder. You probably want to invoke the [DSL builder][Kord.proxy] instead. */ @KordExperimental -public class KordProxyBuilder(override var applicationId: Snowflake) : RestOnlyBuilder() { +public class KordProxyBuilder(override var applicationId: Snowflake?) : RestOnlyBuilder() { override val token: String get() = "" } diff --git a/core/src/commonMain/kotlin/builder/kord/KordRestOnlyBuilder.kt b/core/src/commonMain/kotlin/builder/kord/KordRestOnlyBuilder.kt index 2a03847e162..f8ab21841bd 100644 --- a/core/src/commonMain/kotlin/builder/kord/KordRestOnlyBuilder.kt +++ b/core/src/commonMain/kotlin/builder/kord/KordRestOnlyBuilder.kt @@ -8,13 +8,6 @@ import dev.kord.core.Kord * The rest only Kord builder. You probably want to invoke the [DSL builder][Kord.restOnly] instead. */ @KordExperimental -public class KordRestOnlyBuilder(public override var token: String) : RestOnlyBuilder() { - - private var id: Snowflake? = null - - override var applicationId: Snowflake - get() = id ?: getBotIdFromToken(token) - set(value) { - id = value - } +public class KordRestOnlyBuilder(override var token: String) : RestOnlyBuilder() { + override var applicationId: Snowflake? = null } diff --git a/core/src/commonMain/kotlin/builder/kord/RestOnlyBuilder.kt b/core/src/commonMain/kotlin/builder/kord/RestOnlyBuilder.kt index 9d240dc75cf..e75eb977559 100644 --- a/core/src/commonMain/kotlin/builder/kord/RestOnlyBuilder.kt +++ b/core/src/commonMain/kotlin/builder/kord/RestOnlyBuilder.kt @@ -1,7 +1,6 @@ package dev.kord.core.builder.kord import dev.kord.cache.api.DataCache -import dev.kord.common.entity.Snowflake import dev.kord.core.ClientResources import dev.kord.core.Kord import dev.kord.core.gateway.DefaultMasterGateway @@ -9,51 +8,15 @@ import dev.kord.core.gateway.handler.GatewayEventInterceptor import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.Gateway import dev.kord.gateway.builder.Shards -import dev.kord.rest.ratelimit.ExclusionRequestRateLimiter -import dev.kord.rest.request.KtorRequestHandler -import dev.kord.rest.request.RequestHandler import dev.kord.rest.service.RestClient -import io.ktor.client.* -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow -public abstract class RestOnlyBuilder { - protected var handlerBuilder: (resources: ClientResources) -> RequestHandler = - { KtorRequestHandler(it.httpClient, ExclusionRequestRateLimiter(), token = it.token) } - - protected abstract val token: String - - /** - * The [CoroutineDispatcher] kord uses to launch suspending tasks. [Dispatchers.Default] by default. - */ - public var defaultDispatcher: CoroutineDispatcher = Dispatchers.Default - - /** - * The client used for building [Gateways][Gateway] and [RequestHandlers][RequestHandler]. A default implementation - * will be used when not set. - */ - public var httpClient: HttpClient? = null - - public abstract var applicationId: Snowflake - - /** - * Configures the [RequestHandler] for the [RestClient]. - * - * ``` - * Kord(token) { - * requestHandler { resources -> KtorRequestHandler(resources.httpClient, ExclusionRequestRateLimiter()) } - * } - * ``` - */ - public fun requestHandler(handlerBuilder: (resources: ClientResources) -> RequestHandler) { - this.handlerBuilder = handlerBuilder - } +public abstract class RestOnlyBuilder : AbstractKordBuilder(), HasApplication { public fun build(): Kord { val client = httpClient.configure() - val selfId = applicationId + val selfId = actualApplicationId val resources = ClientResources( token, @@ -64,7 +27,7 @@ public abstract class RestOnlyBuilder { EntitySupplyStrategy.rest, ) - val rest = RestClient(handlerBuilder(resources)) + val rest = RestClient(buildRequestHandler(resources)) return Kord( resources = resources, diff --git a/core/src/commonMain/kotlin/builder/kord/WebhookClientBuilder.kt b/core/src/commonMain/kotlin/builder/kord/WebhookClientBuilder.kt new file mode 100644 index 00000000000..9123102b1eb --- /dev/null +++ b/core/src/commonMain/kotlin/builder/kord/WebhookClientBuilder.kt @@ -0,0 +1,50 @@ +package dev.kord.core.builder.kord + +import dev.kord.cache.api.DataCache +import dev.kord.common.entity.Snowflake +import dev.kord.core.ClientResources +import dev.kord.core.Kord +import dev.kord.core.WebhookClient +import dev.kord.core.gateway.DefaultMasterGateway +import dev.kord.core.gateway.handler.GatewayEventInterceptor +import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kord.gateway.Gateway +import dev.kord.gateway.builder.Shards +import dev.kord.rest.service.RestClient +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow + +/** + * Builder for [WebhookClient] + * + * @see Kord.webhookClient + */ +public class WebhookClientBuilder : AbstractKordBuilder() { + public fun build(): WebhookClient { + val client = httpClient.configure() + + // Neither token nor applicationId is used in WebhookClient, so we can assume + // that this won't cause problems + val resources = ClientResources( + "", + Snowflake(-1), + Shards(0), + maxConcurrency = 1, + client, + EntitySupplyStrategy.rest, + ) + + val rest = RestClient(buildRequestHandler(resources)) + + return Kord( + resources = resources, + cache = @OptIn(ExperimentalCoroutinesApi::class) DataCache.none(), + gateway = DefaultMasterGateway(mapOf(0 to Gateway.none())), + rest = rest, + selfId = resources.applicationId, + eventFlow = MutableSharedFlow(), + dispatcher = defaultDispatcher, + interceptor = GatewayEventInterceptor.none(), + ) + } +}