diff --git a/buildSrc/src/main/kotlin/Projects.kt b/buildSrc/src/main/kotlin/Projects.kt index 4516899e4e27..24c76d65d876 100644 --- a/buildSrc/src/main/kotlin/Projects.kt +++ b/buildSrc/src/main/kotlin/Projects.kt @@ -15,19 +15,7 @@ val isJitPack get() = "true" == System.getenv("JITPACK") object Library { const val name = "kord" const val group = "dev.kord" - val version: String - get() = if (isJitPack) System.getenv("RELEASE_TAG") - else { - val tag = System.getenv("GITHUB_TAG_NAME") - val branch = System.getenv("GITHUB_BRANCH_NAME") - when { - !tag.isNullOrBlank() -> tag - !branch.isNullOrBlank() && branch.startsWith("refs/heads/") -> - branch.substringAfter("refs/heads/").replace("/", "-") + "-SNAPSHOT" - else -> "undefined" - } - - } + val version: String = "zerotwo-SNAPSHOT" const val description = "Idiomatic Kotlin Wrapper for The Discord API" const val projectUrl = "https://github.com/kordlib/kord" diff --git a/buildSrc/src/main/kotlin/kord-module.gradle.kts b/buildSrc/src/main/kotlin/kord-module.gradle.kts index fa6fbb0bf74d..ca4b3d30e883 100644 --- a/buildSrc/src/main/kotlin/kord-module.gradle.kts +++ b/buildSrc/src/main/kotlin/kord-module.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.net.URI import java.util.Base64 plugins { @@ -37,58 +38,20 @@ tasks { } } - withType { - useJUnitPlatform() - } - - dokkaHtml.configure { - this.outputDirectory.set(project.projectDir.resolve("dokka").resolve("kord")) - - dokkaSourceSets { - configureEach { - platform.set(org.jetbrains.dokka.Platform.jvm) - - sourceLink { - localDirectory.set(file("src/main/kotlin")) - remoteUrl.set(uri("https://github.com/kordlib/kord/tree/master/${project.name}/src/main/kotlin/").toURL()) - - remoteLineSuffix.set("#L") - } - - jdkVersion.set(8) - } - } - } - val sourcesJar by registering(Jar::class) { archiveClassifier.set("sources") from(sourceSets.main.get().allSource) } - val dokkaHtml by getting - - val dokkaJar by registering(Jar::class) { - group = JavaBasePlugin.DOCUMENTATION_GROUP - description = "Assembles Kotlin docs with Dokka" - archiveClassifier.set("javadoc") - from(dokkaHtml) - dependsOn(dokkaHtml) - } - - withType().configureEach { - doFirst { require(!Library.isUndefined) { "No release/snapshot version found." } } - } - publishing { publications { create(Library.name) { from(components["kotlin"]) - groupId = Library.group + groupId = "dev.kord" artifactId = "kord-${project.name}" - version = Library.version + version = "zerotwo-SNAPSHOT" artifact(sourcesJar.get()) - artifact(dokkaJar.get()) pom { name.set(Library.name) @@ -124,11 +87,9 @@ tasks { } } - if (!isJitPack) { repositories { maven { - url = if (Library.isSnapshot) uri(Repo.snapshotsUrl) - else uri(Repo.releasesUrl) + url = URI("https://nexus.zerotwo.bot/repository/m2-snapshots-public/") credentials { username = System.getenv("NEXUS_USER") @@ -136,19 +97,7 @@ tasks { } } } - } - } - } - } - - if (!isJitPack && Library.isRelease) { - signing { - val signingKey = findProperty("signingKey")?.toString() - val signingPassword = findProperty("signingPassword")?.toString() - if (signingKey != null && signingPassword != null) { - useInMemoryPgpKeys(Base64.getDecoder().decode(signingKey).toString(), signingPassword) } - sign(publishing.publications[Library.name]) } } } diff --git a/common/src/main/kotlin/entity/DiscordChannel.kt b/common/src/main/kotlin/entity/DiscordChannel.kt index 1290aea11cf5..2a9b39bb33c4 100644 --- a/common/src/main/kotlin/entity/DiscordChannel.kt +++ b/common/src/main/kotlin/entity/DiscordChannel.kt @@ -65,6 +65,8 @@ data class DiscordChannel( val parentId: OptionalSnowflake? = OptionalSnowflake.Missing, @SerialName("last_pin_timestamp") val lastPinTimestamp: Optional = Optional.Missing(), + @SerialName("rtc_region") + val rtcRegion: Optional = Optional.Missing(), val permissions: Optional = Optional.Missing(), @SerialName("message_count") val messageCount: OptionalInt = OptionalInt.Missing, diff --git a/common/src/main/kotlin/entity/DiscordGuild.kt b/common/src/main/kotlin/entity/DiscordGuild.kt index c125b69884e7..2bd54135d1d0 100644 --- a/common/src/main/kotlin/entity/DiscordGuild.kt +++ b/common/src/main/kotlin/entity/DiscordGuild.kt @@ -90,7 +90,6 @@ data class DiscordGuild( @SerialName("owner_id") val ownerId: Snowflake, val permissions: Optional = Optional.Missing(), - val region: String, @SerialName("afk_channel_id") val afkChannelId: Snowflake?, @SerialName("afk_timeout") diff --git a/core/src/main/kotlin/Kord.kt b/core/src/main/kotlin/Kord.kt index cbec8526cdf7..5d7ae7263675 100644 --- a/core/src/main/kotlin/Kord.kt +++ b/core/src/main/kotlin/Kord.kt @@ -3,7 +3,6 @@ package dev.kord.core import dev.kord.cache.api.DataCache import dev.kord.common.annotation.DeprecatedSinceKord import dev.kord.common.annotation.KordExperimental -import dev.kord.common.annotation.KordPreview import dev.kord.common.annotation.KordUnsafe import dev.kord.common.entity.DiscordShard import dev.kord.common.entity.PresenceStatus @@ -22,6 +21,7 @@ import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.exception.KordInitializationException import dev.kord.core.gateway.MasterGateway import dev.kord.core.gateway.handler.GatewayEventInterceptor +import dev.kord.core.gateway.start import dev.kord.core.supplier.* import dev.kord.gateway.Gateway import dev.kord.gateway.builder.PresenceBuilder @@ -53,6 +53,7 @@ class Kord( val selfId: Snowflake, private val eventFlow: MutableSharedFlow, dispatcher: CoroutineDispatcher, + val extraContext: (Event.() -> CoroutineContext)? = null ) : CoroutineScope { private val interceptor = GatewayEventInterceptor(this, gateway, cache, eventFlow) @@ -596,6 +597,9 @@ suspend inline fun Kord(token: String, builder: KordBuilder.() -> Unit = {}): Ko inline fun Kord.on(scope: CoroutineScope = this, noinline consumer: suspend T.() -> Unit): Job = events.buffer(CoroutineChannel.UNLIMITED).filterIsInstance() .onEach { - scope.launch { runCatching { consumer(it) }.onFailure { kordLogger.catching(it) } } + val context = extraContext + ?.let { builder -> scope.coroutineContext + builder.invoke(it) } + ?: scope.coroutineContext + scope.launch(context) { runCatching { consumer(it) }.onFailure { kordLogger.catching(it) } } } .launchIn(scope) diff --git a/core/src/main/kotlin/behavior/RoleBehavior.kt b/core/src/main/kotlin/behavior/RoleBehavior.kt index 40c19460e4f7..a492082bbe59 100644 --- a/core/src/main/kotlin/behavior/RoleBehavior.kt +++ b/core/src/main/kotlin/behavior/RoleBehavior.kt @@ -1,11 +1,13 @@ package dev.kord.core.behavior import dev.kord.common.entity.Snowflake +import dev.kord.common.exception.RequestException import dev.kord.core.Kord import dev.kord.core.cache.data.RoleData import dev.kord.core.entity.KordEntity import dev.kord.core.entity.Role import dev.kord.core.entity.Strategizable +import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.indexOfFirstOrNull import dev.kord.core.sorted import dev.kord.core.supplier.EntitySupplier @@ -43,6 +45,22 @@ interface RoleBehavior : KordEntity, Strategizable { else "<@&${id.asString}>" } + /** + * Requests to get the this behavior as a [Role] if it's not an instance of a [Role]. + * + * @throws [RequestException] if anything went wrong during the request. + * @throws [EntityNotFoundException] if the user wasn't present. + */ + suspend fun asRole(): Role = supplier.getRole(guildId, id) + + /** + * Requests to get this behavior as a [Role] if its not an instance of a [Role], + * returns null if the user isn't present. + * + * @throws [RequestException] if anything went wrong during the request. + */ + suspend fun asRoleOrNull(): Role? = supplier.getRoleOrNull(guildId, id) + /** * Requests to change the [position] of this role. * diff --git a/core/src/main/kotlin/behavior/StageInstanceBehavior.kt b/core/src/main/kotlin/behavior/StageInstanceBehavior.kt index f043cfc6b9c7..b306d82d0d1e 100644 --- a/core/src/main/kotlin/behavior/StageInstanceBehavior.kt +++ b/core/src/main/kotlin/behavior/StageInstanceBehavior.kt @@ -1,11 +1,13 @@ package dev.kord.core.behavior import dev.kord.common.entity.Snowflake +import dev.kord.common.exception.RequestException import dev.kord.core.Kord import dev.kord.core.cache.data.StageInstanceData import dev.kord.core.entity.KordEntity import dev.kord.core.entity.StageInstance import dev.kord.core.entity.Strategizable +import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.rest.json.request.StageInstanceUpdateRequest @@ -22,8 +24,22 @@ interface StageInstanceBehavior : KordEntity, Strategizable { return StageInstance(data, kord, supplier) } + /** + * Requests to get the this behavior as a [StageInstance] if it's not an instance of a [StageInstance]. + * + * @throws [RequestException] if anything went wrong during the request. + * @throws [EntityNotFoundException] if the user wasn't present. + */ suspend fun asStageInstance(): StageInstance = supplier.getStageInstance(channelId) + /** + * Requests to get this behavior as a [StageInstance] if its not an instance of a [StageInstance], + * returns null if the user isn't present. + * + * @throws [RequestException] if anything went wrong during the request. + */ + suspend fun asStageInstanceOrNull(): StageInstance? = supplier.getStageInstanceOrNull(channelId) + override fun withStrategy(strategy: EntitySupplyStrategy<*>): StageInstanceBehavior = StageInstanceBehavior(id, channelId, kord, strategy.supply(kord)) } diff --git a/core/src/main/kotlin/builder/kord/KordBuilder.kt b/core/src/main/kotlin/builder/kord/KordBuilder.kt index 910ef1d71c35..9b3e3e4f848a 100644 --- a/core/src/main/kotlin/builder/kord/KordBuilder.kt +++ b/core/src/main/kotlin/builder/kord/KordBuilder.kt @@ -13,7 +13,7 @@ import dev.kord.core.cache.createView import dev.kord.core.cache.registerKordData import dev.kord.core.event.Event import dev.kord.core.exception.KordInitializationException -import dev.kord.core.gateway.MasterGateway +import dev.kord.core.gateway.DefaultMasterGateway import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.DefaultGateway import dev.kord.gateway.Gateway @@ -245,7 +245,7 @@ class KordBuilder(val token: String) { put(shard, gateways[index]) } } - MasterGateway(gateways) + DefaultMasterGateway(gateways) } val self = getBotIdFromToken(token) @@ -265,7 +265,8 @@ class KordBuilder(val token: String) { rest, self, eventFlow, - defaultDispatcher + defaultDispatcher, + null ) } diff --git a/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt b/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt index c6b267ea1dfb..208cdbaa1350 100644 --- a/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt +++ b/core/src/main/kotlin/builder/kord/KordRestOnlyBuilder.kt @@ -7,7 +7,7 @@ import dev.kord.core.ClientResources import dev.kord.core.Kord import dev.kord.core.event.Event import dev.kord.core.exception.KordInitializationException -import dev.kord.core.gateway.MasterGateway +import dev.kord.core.gateway.DefaultMasterGateway import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.Gateway import dev.kord.gateway.Intents @@ -75,11 +75,12 @@ class KordRestOnlyBuilder(val token: String) { return Kord( resources, DataCache.none(), - MasterGateway(mapOf(0 to Gateway.none())), + DefaultMasterGateway(mapOf(0 to Gateway.none())), rest, selfId, MutableSharedFlow(), - defaultDispatcher + defaultDispatcher, + null ) } } \ No newline at end of file diff --git a/core/src/main/kotlin/cache/data/ChannelData.kt b/core/src/main/kotlin/cache/data/ChannelData.kt index 5c530247d014..2d12ce571d20 100644 --- a/core/src/main/kotlin/cache/data/ChannelData.kt +++ b/core/src/main/kotlin/cache/data/ChannelData.kt @@ -25,6 +25,7 @@ data class ChannelData( val applicationId: OptionalSnowflake = OptionalSnowflake.Missing, val parentId: OptionalSnowflake? = OptionalSnowflake.Missing, val lastPinTimestamp: Optional = Optional.Missing(), + val rtcRegion: Optional = Optional.Missing(), val permissions: Optional = Optional.Missing(), val threadMetadata: Optional = Optional.Missing(), val messageCount: OptionalInt = OptionalInt.Missing, @@ -57,6 +58,7 @@ data class ChannelData( applicationId, parentId, lastPinTimestamp, + rtcRegion, permissions, threadMetadata.map { ThreadMetadataData.from(it) }, messageCount, diff --git a/core/src/main/kotlin/cache/data/EmojiData.kt b/core/src/main/kotlin/cache/data/EmojiData.kt index 1e3d64629fd5..bbccecc62e8c 100644 --- a/core/src/main/kotlin/cache/data/EmojiData.kt +++ b/core/src/main/kotlin/cache/data/EmojiData.kt @@ -37,3 +37,7 @@ data class EmojiData( } } } + +fun DiscordEmoji.toData(guildId: Snowflake, id: Snowflake): EmojiData { + return EmojiData.from(guildId, id, this) +} \ No newline at end of file diff --git a/core/src/main/kotlin/cache/data/GuildData.kt b/core/src/main/kotlin/cache/data/GuildData.kt index a321c657cf26..2e87bbbbc92f 100644 --- a/core/src/main/kotlin/cache/data/GuildData.kt +++ b/core/src/main/kotlin/cache/data/GuildData.kt @@ -20,7 +20,6 @@ data class GuildData( //val owner: OptionalBoolean = OptionalBoolean.Missing, useless? val ownerId: Snowflake, val permissions: Optional = Optional.Missing(), - val region: String, val afkChannelId: Snowflake? = null, val afkTimeout: Int, val widgetEnabled: OptionalBoolean = OptionalBoolean.Missing, @@ -82,7 +81,6 @@ data class GuildData( //owner = owner, ownerId = ownerId, permissions = permissions, - region = region, afkChannelId = afkChannelId, afkTimeout = afkTimeout, widgetEnabled = widgetEnabled, diff --git a/core/src/main/kotlin/cache/data/RoleData.kt b/core/src/main/kotlin/cache/data/RoleData.kt index 8ddf2bcd64f4..2d04e85e9f71 100644 --- a/core/src/main/kotlin/cache/data/RoleData.kt +++ b/core/src/main/kotlin/cache/data/RoleData.kt @@ -43,3 +43,7 @@ data class RoleData( } } + +fun DiscordRole.toData(guildId: Snowflake): RoleData { + return RoleData.from(DiscordGuildRole(guildId, this)) +} diff --git a/core/src/main/kotlin/cache/data/StageInstanceData.kt b/core/src/main/kotlin/cache/data/StageInstanceData.kt index 43e6af3ba954..743ab4a3d0b1 100644 --- a/core/src/main/kotlin/cache/data/StageInstanceData.kt +++ b/core/src/main/kotlin/cache/data/StageInstanceData.kt @@ -17,3 +17,7 @@ data class StageInstanceData( } } } + +fun DiscordStageInstance.toData(): StageInstanceData { + return StageInstanceData.from(this) +} \ No newline at end of file diff --git a/core/src/main/kotlin/cache/data/ThreadMemberData.kt b/core/src/main/kotlin/cache/data/ThreadMemberData.kt index c4a618930c1e..1e049b8b93e0 100644 --- a/core/src/main/kotlin/cache/data/ThreadMemberData.kt +++ b/core/src/main/kotlin/cache/data/ThreadMemberData.kt @@ -27,3 +27,7 @@ data class ThreadMemberData( } } } + +fun DiscordThreadMember.toData(threadId: Snowflake?): ThreadMemberData { + return ThreadMemberData.from(this, threadId) +} \ No newline at end of file diff --git a/core/src/main/kotlin/entity/Guild.kt b/core/src/main/kotlin/entity/Guild.kt index c990aa31b218..5ea5ecc1e40c 100644 --- a/core/src/main/kotlin/entity/Guild.kt +++ b/core/src/main/kotlin/entity/Guild.kt @@ -46,6 +46,10 @@ class Guild( */ val afkChannelId: Snowflake? get() = data.afkChannelId + override suspend fun asGuild(): Guild = this + + override suspend fun asGuildOrNull(): Guild = this + val afkChannel: VoiceChannelBehavior? get() = afkChannelId?.let { VoiceChannelBehavior(guildId = id, id = it, kord = kord) } @@ -259,11 +263,6 @@ class Guild( */ val defaultMessageNotificationLevel: DefaultMessageNotificationLevel get() = data.defaultMessageNotifications - /** - * The voice region id for the guild. - */ - val regionId: String get() = data.region - /** * The id of the channel in which a discoverable server's rules should be found **/ @@ -464,15 +463,6 @@ class Guild( */ suspend fun getPublicUpdatesChannel(): TopGuildMessageChannel? = publicUpdatesChannel?.asChannel() - /** - * Requests to get the [voice region][Region] of this guild. - * - * @throws [RequestException] if anything went wrong during the request. - * @throws [EntityNotFoundException] if the [Region] wasn't present. - * @throws [NoSuchElementException] if the [regionId] is not in the available [regions]. - */ - suspend fun getRegion(): Region = regions.first { it.id == regionId } - /** * Requests to get the the channel in which a discoverable server's rules should be found represented *, returns null if the [TopGuildMessageChannel] isn't present, or [rulesChannelId] is null. diff --git a/core/src/main/kotlin/entity/Member.kt b/core/src/main/kotlin/entity/Member.kt index 36a9744e47d8..58272e7e3ea9 100644 --- a/core/src/main/kotlin/entity/Member.kt +++ b/core/src/main/kotlin/entity/Member.kt @@ -13,8 +13,8 @@ import dev.kord.core.cache.data.UserData import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import kotlinx.coroutines.flow.* -import kotlinx.datetime.toInstant import kotlinx.datetime.Instant +import kotlinx.datetime.toInstant import java.util.* /** @@ -111,6 +111,18 @@ class Member( override fun withStrategy(strategy: EntitySupplyStrategy<*>): Member = Member(memberData, data, kord, strategy.supply(kord)) + override suspend fun asUser(): User = this + + override suspend fun asUserOrNull(): User = this + + override suspend fun asMember(guildId: Snowflake): Member = this + + override suspend fun asMember(): Member = this + + override suspend fun asMemberOrNull(guildId: Snowflake): Member = this + + override suspend fun asMemberOrNull(): Member = this + override fun hashCode(): Int = Objects.hash(id, guildId) diff --git a/core/src/main/kotlin/entity/Message.kt b/core/src/main/kotlin/entity/Message.kt index c7c9d0fd0a1e..5a49a396ac0a 100644 --- a/core/src/main/kotlin/entity/Message.kt +++ b/core/src/main/kotlin/entity/Message.kt @@ -47,6 +47,8 @@ class Message( override val channelId: Snowflake get() = data.channelId + override suspend fun asMessageOrNull(): Message = this + /** * The files attached to this message. */ diff --git a/core/src/main/kotlin/entity/Role.kt b/core/src/main/kotlin/entity/Role.kt index 5f8a88bc79de..23c7a90c2780 100644 --- a/core/src/main/kotlin/entity/Role.kt +++ b/core/src/main/kotlin/entity/Role.kt @@ -37,6 +37,10 @@ data class Role( val rawPosition: Int get() = data.position + override suspend fun asRole(): Role = this + + override suspend fun asRoleOrNull(): Role = this + /** * The tags of this role, if present. */ diff --git a/core/src/main/kotlin/entity/StageInstance.kt b/core/src/main/kotlin/entity/StageInstance.kt index 5987e4d95e09..579bcd5f6814 100644 --- a/core/src/main/kotlin/entity/StageInstance.kt +++ b/core/src/main/kotlin/entity/StageInstance.kt @@ -7,7 +7,7 @@ import dev.kord.core.cache.data.StageInstanceData import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy -class StageInstance(val data: StageInstanceData, override val kord: Kord, override val supplier: EntitySupplier) : StageInstanceBehavior { +class StageInstance(val data: StageInstanceData, override val kord: Kord, override val supplier: EntitySupplier = kord.defaultSupplier) : StageInstanceBehavior { override val id: Snowflake get() = data.id val guildId: Snowflake get() = data.guildId override val channelId: Snowflake get() = data.channelId @@ -16,4 +16,7 @@ class StageInstance(val data: StageInstanceData, override val kord: Kord, overri override fun withStrategy(strategy: EntitySupplyStrategy<*>): StageInstanceBehavior = StageInstance(data, kord, strategy.supply(kord)) + override suspend fun asStageInstance(): StageInstance = this + + override suspend fun asStageInstanceOrNull(): StageInstance = this } diff --git a/core/src/main/kotlin/entity/User.kt b/core/src/main/kotlin/entity/User.kt index e9827a7c211f..0fb5da76449d 100644 --- a/core/src/main/kotlin/entity/User.kt +++ b/core/src/main/kotlin/entity/User.kt @@ -43,6 +43,14 @@ open class User( @DeprecatedSinceKord("0.7.0") val flags: UserFlags? by ::publicFlags + override suspend fun asUser(): User { + return this + } + + override suspend fun asUserOrNull(): User { + return this + } + /** * The flags on a user's account, if present. */ diff --git a/core/src/main/kotlin/entity/channel/Category.kt b/core/src/main/kotlin/entity/channel/Category.kt index 0e06a9b17e29..4ea211556d12 100644 --- a/core/src/main/kotlin/entity/channel/Category.kt +++ b/core/src/main/kotlin/entity/channel/Category.kt @@ -28,6 +28,8 @@ class Category( override suspend fun asChannel(): Category = this + override suspend fun asChannelOrNull(): Category = this + override fun compareTo(other: Entity): Int { return super.compareTo(other) } diff --git a/core/src/main/kotlin/entity/channel/DmChannel.kt b/core/src/main/kotlin/entity/channel/DmChannel.kt index 54059b09ce49..eed27b73d57d 100644 --- a/core/src/main/kotlin/entity/channel/DmChannel.kt +++ b/core/src/main/kotlin/entity/channel/DmChannel.kt @@ -49,6 +49,10 @@ data class DmChannel( .map { supplier.getUserOrNull(it) } .filterNotNull() + override suspend fun asChannel(): MessageChannel = this + + override suspend fun asChannelOrNull(): MessageChannel = this + /** * returns a new [DmChannel] with the given [strategy]. */ diff --git a/core/src/main/kotlin/entity/channel/NewsChannel.kt b/core/src/main/kotlin/entity/channel/NewsChannel.kt index bb7a0809ae0a..5ce65e2300a6 100644 --- a/core/src/main/kotlin/entity/channel/NewsChannel.kt +++ b/core/src/main/kotlin/entity/channel/NewsChannel.kt @@ -18,8 +18,6 @@ class NewsChannel( override val supplier: EntitySupplier = kord.defaultSupplier ) : CategorizableChannel, TopGuildMessageChannel, ThreadParentChannel, NewsChannelBehavior { - override suspend fun asChannel(): NewsChannel = this - override fun hashCode(): Int = Objects.hash(id, guildId) override fun equals(other: Any?): Boolean = when (other) { @@ -28,6 +26,10 @@ class NewsChannel( else -> false } + override suspend fun asChannel(): NewsChannel = this + + override suspend fun asChannelOrNull(): NewsChannel = this + /** * Returns a new [NewsChannel] with the given [strategy]. */ diff --git a/core/src/main/kotlin/entity/channel/ResolvedChannel.kt b/core/src/main/kotlin/entity/channel/ResolvedChannel.kt index 7694ac58012f..c21695d32bb2 100644 --- a/core/src/main/kotlin/entity/channel/ResolvedChannel.kt +++ b/core/src/main/kotlin/entity/channel/ResolvedChannel.kt @@ -1,6 +1,5 @@ package dev.kord.core.entity.channel -import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.Permissions import dev.kord.core.Kord import dev.kord.core.cache.data.ChannelData @@ -19,6 +18,10 @@ class ResolvedChannel( val permissions: Permissions get() = data.permissions.value!! + override suspend fun asChannel(): Channel = this + + override suspend fun asChannelOrNull(): Channel = this + override val supplier: EntitySupplier get() = strategy.supply(kord) diff --git a/core/src/main/kotlin/entity/channel/StageVoiceChannel.kt b/core/src/main/kotlin/entity/channel/StageVoiceChannel.kt index 43344d33fbc9..e4dcedb3a857 100644 --- a/core/src/main/kotlin/entity/channel/StageVoiceChannel.kt +++ b/core/src/main/kotlin/entity/channel/StageVoiceChannel.kt @@ -5,7 +5,6 @@ import dev.kord.common.entity.optional.getOrThrow import dev.kord.core.Kord import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.behavior.channel.GuildChannelBehavior -import dev.kord.core.behavior.channel.TopGuildChannelBehavior import dev.kord.core.behavior.channel.StageChannelBehavior import dev.kord.core.cache.data.ChannelData import dev.kord.core.exception.GatewayNotFoundException @@ -45,6 +44,8 @@ class StageChannel( override suspend fun asChannel(): StageChannel = this + override suspend fun asChannelOrNull(): StageChannel = this + override fun hashCode(): Int = Objects.hash(id, guildId) override fun equals(other: Any?): Boolean = when (other) { diff --git a/core/src/main/kotlin/entity/channel/StoreChannel.kt b/core/src/main/kotlin/entity/channel/StoreChannel.kt index d26aca55ea30..b4f2453228b7 100644 --- a/core/src/main/kotlin/entity/channel/StoreChannel.kt +++ b/core/src/main/kotlin/entity/channel/StoreChannel.kt @@ -22,6 +22,8 @@ data class StoreChannel( override suspend fun asChannel(): StoreChannel = this + override suspend fun asChannelOrNull(): StoreChannel = this + /** * Returns a new [StoreChannel] with the given [strategy]. */ diff --git a/core/src/main/kotlin/entity/channel/TextChannel.kt b/core/src/main/kotlin/entity/channel/TextChannel.kt index cf6c91c8a1a8..f813f33f2c6e 100644 --- a/core/src/main/kotlin/entity/channel/TextChannel.kt +++ b/core/src/main/kotlin/entity/channel/TextChannel.kt @@ -38,6 +38,10 @@ class TextChannel( override fun withStrategy(strategy: EntitySupplyStrategy<*>): TextChannel = TextChannel(data, kord, strategy.supply(kord)) + override suspend fun asChannel(): TextChannel = this + + override suspend fun asChannelOrNull(): TextChannel = this + override fun hashCode(): Int = Objects.hash(id, guildId) override fun equals(other: Any?): Boolean = when (other) { diff --git a/core/src/main/kotlin/entity/channel/VoiceChannel.kt b/core/src/main/kotlin/entity/channel/VoiceChannel.kt index 731ef5377242..10203e9ade12 100644 --- a/core/src/main/kotlin/entity/channel/VoiceChannel.kt +++ b/core/src/main/kotlin/entity/channel/VoiceChannel.kt @@ -2,17 +2,21 @@ package dev.kord.core.entity.channel import dev.kord.common.annotation.KordVoice import dev.kord.common.entity.optional.getOrThrow +import dev.kord.common.exception.RequestException import dev.kord.core.Kord import dev.kord.core.behavior.channel.ChannelBehavior import dev.kord.core.behavior.channel.GuildChannelBehavior import dev.kord.core.behavior.channel.TopGuildChannelBehavior import dev.kord.core.behavior.channel.VoiceChannelBehavior import dev.kord.core.cache.data.ChannelData +import dev.kord.core.entity.Region +import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.exception.GatewayNotFoundException import dev.kord.core.supplier.EntitySupplier import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.voice.VoiceConnection import dev.kord.voice.VoiceConnectionBuilder +import kotlinx.coroutines.flow.first import java.util.* /** @@ -35,6 +39,20 @@ class VoiceChannel( */ val userLimit: Int get() = data.userLimit.getOrThrow() + /** + * The region name of the voice channel + */ + val rtcRegion: String? get() = data.rtcRegion.value + + /** + * Requests to get the [voice region][Region] of this guild. + * + * @throws [RequestException] if anything went wrong during the request. + * @throws [EntityNotFoundException] if the [Region] wasn't present. + * @throws [NoSuchElementException] if the [rtcRegion] is not in the available. + */ + suspend fun getRegion(): Region = guild.regions.first { it.id == rtcRegion } + /** * returns a new [VoiceChannel] with the given [strategy]. * @@ -44,6 +62,8 @@ class VoiceChannel( VoiceChannel(data, kord, strategy.supply(kord)) override suspend fun asChannel(): VoiceChannel = this + + override suspend fun asChannelOrNull(): VoiceChannel = this override fun hashCode(): Int = Objects.hash(id, guildId) diff --git a/core/src/main/kotlin/entity/channel/thread/NewsChannelThread.kt b/core/src/main/kotlin/entity/channel/thread/NewsChannelThread.kt index 30ef737dbe93..38f8ceb04036 100644 --- a/core/src/main/kotlin/entity/channel/thread/NewsChannelThread.kt +++ b/core/src/main/kotlin/entity/channel/thread/NewsChannelThread.kt @@ -19,9 +19,9 @@ class NewsChannelThread( ) : ThreadChannel { - override suspend fun asChannel(): NewsChannelThread = super.asChannel() as NewsChannelThread + override suspend fun asChannel(): NewsChannelThread = this - override suspend fun asChannelOrNull(): NewsChannelThread? = super.asChannelOrNull() as? NewsChannelThread + override suspend fun asChannelOrNull(): NewsChannelThread? = this override suspend fun getParent(): NewsChannel { diff --git a/core/src/main/kotlin/entity/channel/thread/TextChannelThread.kt b/core/src/main/kotlin/entity/channel/thread/TextChannelThread.kt index 69c740acd516..345a16209fb1 100644 --- a/core/src/main/kotlin/entity/channel/thread/TextChannelThread.kt +++ b/core/src/main/kotlin/entity/channel/thread/TextChannelThread.kt @@ -34,9 +34,9 @@ class TextChannelThread( override val guildId: Snowflake get() = data.guildId.value!! - override suspend fun asChannel(): TextChannelThread = super.asChannel() as TextChannelThread + override suspend fun asChannel(): TextChannelThread = this - override suspend fun asChannelOrNull(): TextChannelThread? = super.asChannelOrNull() as? TextChannelThread + override suspend fun asChannelOrNull(): TextChannelThread = this override fun withStrategy(strategy: EntitySupplyStrategy<*>): TextChannelThread { return TextChannelThread(data, kord, strategy.supply(kord)) diff --git a/core/src/main/kotlin/event/Event.kt b/core/src/main/kotlin/event/Event.kt index e9c5b6d69bfb..3618e140f935 100644 --- a/core/src/main/kotlin/event/Event.kt +++ b/core/src/main/kotlin/event/Event.kt @@ -7,7 +7,7 @@ import kotlin.coroutines.CoroutineContext interface Event : CoroutineScope { override val coroutineContext: CoroutineContext - get() = kord.coroutineContext + get() = kord.extraContext?.let { kord.coroutineContext + it.invoke(this) } ?: kord.coroutineContext /** * The Gateway that spawned this event. diff --git a/core/src/main/kotlin/gateway/DefaultMasterGateway.kt b/core/src/main/kotlin/gateway/DefaultMasterGateway.kt new file mode 100644 index 000000000000..f520722d4967 --- /dev/null +++ b/core/src/main/kotlin/gateway/DefaultMasterGateway.kt @@ -0,0 +1,40 @@ +package dev.kord.core.gateway + +import dev.kord.gateway.* +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.flattenMerge +import kotlinx.coroutines.flow.map +import kotlin.time.Duration + +class DefaultMasterGateway( + override val gateways: Map, +): MasterGateway { + + /** + * Calculates the average [Gateway.ping] of all running [gateways]. + * + * Gateways that return `null` are not counted into the average, if all [gateways] + * return `null` then this property will return `null` as well. + */ + override val averagePing + get(): Duration? { + val pings = gateways.values.mapNotNull { it.ping.value?.inWholeMicroseconds } + if (pings.isEmpty()) return null + + return Duration.microseconds(pings.average()) + } + + + @OptIn(FlowPreview::class) + override val events: Flow = gateways.entries.asFlow() + .map { (shard, gateway) -> gateway.events.map { ShardEvent(it, gateway, shard) } } + .flattenMerge(gateways.size.coerceAtLeast(1)) + + + override fun toString(): String { + return "MasterGateway(gateways=$gateways)" + } + +} diff --git a/core/src/main/kotlin/gateway/MasterGateway.kt b/core/src/main/kotlin/gateway/MasterGateway.kt index fc1d68a879f1..a20ca4e636de 100644 --- a/core/src/main/kotlin/gateway/MasterGateway.kt +++ b/core/src/main/kotlin/gateway/MasterGateway.kt @@ -4,18 +4,13 @@ import dev.kord.gateway.* import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.flattenMerge -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlin.time.Duration -import kotlin.time.microseconds data class ShardEvent(val event: Event, val gateway: Gateway, val shard: Int) -class MasterGateway( - val gateways: Map, -) { +interface MasterGateway { + val gateways: Map /** * Calculates the average [Gateway.ping] of all running [gateways]. @@ -23,24 +18,13 @@ class MasterGateway( * Gateways that return `null` are not counted into the average, if all [gateways] * return `null` then this property will return `null` as well. */ - val averagePing - get(): Duration? { - val pings = gateways.values.mapNotNull { it.ping.value?.inWholeMicroseconds } - if (pings.isEmpty()) return null - - return Duration.microseconds(pings.average()) - } + val averagePing: Duration? @OptIn(FlowPreview::class) - val events: Flow = gateways.entries.asFlow() - .map { (shard, gateway) -> gateway.events.map { ShardEvent(it, gateway, shard) } } - .flattenMerge(gateways.size.coerceAtLeast(1)) + val events: Flow - /** - * Calls [Gateway.start] on each Gateway in [gateways], changing the [GatewayConfiguration.shard] for each Gateway. - */ - suspend fun start(configuration: GatewayConfiguration): Unit = coroutineScope { + suspend fun startWithConfig(configuration: GatewayConfiguration): Unit = coroutineScope { gateways.entries.forEach { (shard, gateway) -> val config = configuration.copy(shard = configuration.shard.copy(index = shard)) launch { @@ -49,20 +33,15 @@ class MasterGateway( } } - suspend inline fun start(token: String, config: GatewayConfigurationBuilder.() -> Unit = {}) { - val builder = GatewayConfigurationBuilder(token) - builder.apply(config) - start(builder.build()) - } - suspend fun sendAll(command: Command) = gateways.values.forEach { it.send(command) } suspend fun detachAll() = gateways.values.forEach { it.detach() } suspend fun stopAll() = gateways.values.forEach { it.stop() } - - override fun toString(): String { - return "MasterGateway(gateways=$gateways)" - } - } + +suspend inline fun MasterGateway.start(token: String, config: GatewayConfigurationBuilder.() -> Unit = {}) { + val builder = GatewayConfigurationBuilder(token) + builder.apply(config) + startWithConfig(builder.build()) +} \ No newline at end of file diff --git a/core/src/test/kotlin/gateway/MasterGatewayTest.kt b/core/src/test/kotlin/gateway/MasterGatewayTest.kt index b39c4c0cae79..14a1dc009930 100644 --- a/core/src/test/kotlin/gateway/MasterGatewayTest.kt +++ b/core/src/test/kotlin/gateway/MasterGatewayTest.kt @@ -1,6 +1,6 @@ package gateway -import dev.kord.core.gateway.MasterGateway +import dev.kord.core.gateway.DefaultMasterGateway import dev.kord.gateway.Command import dev.kord.gateway.Event import dev.kord.gateway.Gateway @@ -14,7 +14,7 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.time.Duration -internal class MasterGatewayTest { +internal class DefaultMasterGatewayTest { @Test fun `Gateway takes ping of single child`(){ @@ -23,7 +23,7 @@ internal class MasterGatewayTest { dummy.ping.value = ping - val gateway = MasterGateway( + val gateway = DefaultMasterGateway( mapOf(0 to dummy) ) @@ -40,7 +40,7 @@ internal class MasterGatewayTest { dummy1.ping.value = ping1 dummy2.ping.value = ping2 - val gateway = MasterGateway( + val gateway = DefaultMasterGateway( mapOf(0 to dummy1, 1 to dummy2) ) @@ -51,7 +51,7 @@ internal class MasterGatewayTest { fun `Gateway returns null ping when no gateway pings`(){ val dummy = DummyGateway() - val gateway = MasterGateway( + val gateway = DefaultMasterGateway( mapOf(0 to dummy) ) diff --git a/core/src/test/kotlin/live/AbstractLiveEntityTest.kt b/core/src/test/kotlin/live/AbstractLiveEntityTest.kt index a4327a556c85..bc625d02dfdb 100644 --- a/core/src/test/kotlin/live/AbstractLiveEntityTest.kt +++ b/core/src/test/kotlin/live/AbstractLiveEntityTest.kt @@ -6,7 +6,7 @@ import dev.kord.common.entity.Snowflake import dev.kord.core.ClientResources import dev.kord.core.Kord import dev.kord.core.builder.kord.Shards -import dev.kord.core.gateway.MasterGateway +import dev.kord.core.gateway.DefaultMasterGateway import dev.kord.core.live.AbstractLiveKordEntity import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.* @@ -105,11 +105,12 @@ abstract class AbstractLiveEntityTest { return Kord( resources = ClientResources("token", Snowflake(0u), Shards(1), HttpClient(), EntitySupplyStrategy.cache, Intents.none), cache = DataCache.none(), - MasterGateway(mapOf(0 to gateway)), + DefaultMasterGateway(mapOf(0 to gateway)), RestClient(KtorRequestHandler(token = "token")), randomId(), MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE), - Dispatchers.Default + Dispatchers.Default, + null ) } diff --git a/core/src/test/kotlin/performance/KordEventDropTest.kt b/core/src/test/kotlin/performance/KordEventDropTest.kt index a91d9a3f0794..c8e38a4d2724 100644 --- a/core/src/test/kotlin/performance/KordEventDropTest.kt +++ b/core/src/test/kotlin/performance/KordEventDropTest.kt @@ -6,7 +6,7 @@ import dev.kord.core.ClientResources import dev.kord.core.Kord import dev.kord.core.builder.kord.Shards import dev.kord.core.event.guild.GuildCreateEvent -import dev.kord.core.gateway.MasterGateway +import dev.kord.core.gateway.DefaultMasterGateway import dev.kord.core.on import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.* @@ -49,11 +49,12 @@ class KordEventDropTest { val kord = Kord( resources = ClientResources("token", Snowflake(0u), Shards(1), HttpClient(), EntitySupplyStrategy.cache, Intents.none), cache = DataCache.none(), - MasterGateway(mapOf(0 to SpammyGateway)), + DefaultMasterGateway(mapOf(0 to SpammyGateway)), RestClient(KtorRequestHandler("token", clock = Clock.System)), Snowflake("420"), MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE), - Dispatchers.Default + Dispatchers.Default, + null ) @Test diff --git a/core/src/test/kotlin/regression/CacheMissRegression.kt b/core/src/test/kotlin/regression/CacheMissRegression.kt index 34aaa8e4fabe..fb945d3ba4a8 100644 --- a/core/src/test/kotlin/regression/CacheMissRegression.kt +++ b/core/src/test/kotlin/regression/CacheMissRegression.kt @@ -11,7 +11,7 @@ import dev.kord.core.builder.kord.configure import dev.kord.core.builder.kord.getBotIdFromToken import dev.kord.core.cache.data.ChannelData import dev.kord.core.cache.registerKordData -import dev.kord.core.gateway.MasterGateway +import dev.kord.core.gateway.DefaultMasterGateway import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.gateway.* import dev.kord.rest.request.JsonRequest @@ -131,11 +131,12 @@ class CacheMissingRegressions { kord = Kord( resources, MapDataCache().also { it.registerKordData() }, - MasterGateway(mapOf(0 to FakeGateway)), + DefaultMasterGateway(mapOf(0 to FakeGateway)), RestClient(CrashingHandler(resources.httpClient)), getBotIdFromToken(token), MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE), - Dispatchers.Default + Dispatchers.Default, + null ) } diff --git a/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt b/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt index 45476939e875..302529faac22 100644 --- a/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt +++ b/core/src/test/kotlin/supplier/CacheEntitySupplierTest.kt @@ -7,7 +7,7 @@ import dev.kord.core.ClientResources import dev.kord.core.Kord import dev.kord.core.builder.kord.Shards import dev.kord.core.cache.KordCacheBuilder -import dev.kord.core.gateway.MasterGateway +import dev.kord.core.gateway.DefaultMasterGateway import dev.kord.gateway.Gateway import dev.kord.gateway.Intents import dev.kord.gateway.PrivilegedIntent @@ -26,13 +26,14 @@ internal class CacheEntitySupplierTest { @OptIn(PrivilegedIntent::class, KordUnsafe::class, KordExperimental::class) fun `cache does not throw when accessing unregistered entities`(): Unit = runBlocking { val kord = Kord( - ClientResources("", Snowflake(0u), Shards(0), HttpClient(), EntitySupplyStrategy.cache, Intents.all), - KordCacheBuilder().build(), - MasterGateway(mapOf(0 to Gateway.none())), - RestClient(KtorRequestHandler("")), - Snowflake(0u), - MutableSharedFlow(), - Dispatchers.Default + ClientResources("", Snowflake(0u), Shards(0), HttpClient(), EntitySupplyStrategy.cache, Intents.all), + KordCacheBuilder().build(), + DefaultMasterGateway(mapOf(0 to Gateway.none())), + RestClient(KtorRequestHandler("")), + Snowflake(0u), + MutableSharedFlow(), + Dispatchers.Default, + null ) kord.unsafe.guild(Snowflake(0u)).regions.toList()