diff --git a/.github/workflows/deployment-ci.yml b/.github/workflows/deployment-ci.yml index 20f02ea035d..76726467f9a 100644 --- a/.github/workflows/deployment-ci.yml +++ b/.github/workflows/deployment-ci.yml @@ -8,6 +8,9 @@ on: - '**' # We want to run this on all branch pushes tags-ignore: - '**' # We don't want this to run on tags pushes + branches-ignore: + # CI is not setup for Native + - 'feature/native' pull_request: release: types: [ published ] diff --git a/build.gradle.kts b/build.gradle.kts index 0ad64bff26f..be333e43178 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,8 +2,11 @@ plugins { org.jetbrains.dokka // for dokkaHtmlMultiModule task } -repositories { - mavenCentral() +allprojects { + repositories { + mavenCentral() + maven("https://oss.sonatype.org/content/repositories/snapshots/") + } } group = Library.group diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 71fc53572c3..686332581da 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,6 +3,7 @@ plugins { } repositories { + gradlePluginPortal() mavenCentral() } diff --git a/buildSrc/src/main/kotlin/kord-internal-multiplatform-module.gradle.kts b/buildSrc/src/main/kotlin/kord-internal-multiplatform-module.gradle.kts index 53be1985486..367146b1074 100644 --- a/buildSrc/src/main/kotlin/kord-internal-multiplatform-module.gradle.kts +++ b/buildSrc/src/main/kotlin/kord-internal-multiplatform-module.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + plugins { org.jetbrains.kotlin.multiplatform } @@ -6,7 +8,9 @@ repositories { mavenCentral() } +@OptIn(ExperimentalKotlinGradlePluginApi::class) kotlin { + targetHierarchy.default() jvm() js(IR) { nodejs() diff --git a/buildSrc/src/main/kotlin/kord-multiplatform-module.gradle.kts b/buildSrc/src/main/kotlin/kord-multiplatform-module.gradle.kts index ea4cbbf586f..e292be715a9 100644 --- a/buildSrc/src/main/kotlin/kord-multiplatform-module.gradle.kts +++ b/buildSrc/src/main/kotlin/kord-multiplatform-module.gradle.kts @@ -1,4 +1,6 @@ +import org.gradle.configurationcache.extensions.capitalized import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest plugins { @@ -22,7 +24,22 @@ apiValidation { applyKordBCVOptions() } +@OptIn(ExperimentalKotlinGradlePluginApi::class) kotlin { + targetHierarchy.default { + common { + group("nonJvm") { + withNative() + withJs() + } + + group("nonNative") { + withJs() + withJvm() + } + } + } + explicitApi() jvm() @@ -50,17 +67,6 @@ kotlin { implementation(project(":test-kit")) } } - val nonJvmMain by creating { - dependsOn(commonMain.get()) - } - targets - .map { it.name } - .filter { it != "jvm" && it != "metadata" } - .forEach { target -> - sourceSets.getByName("${target}Main") { - dependsOn(nonJvmMain) - } - } } } @@ -75,10 +81,49 @@ tasks { environment("PROJECT_ROOT", rootProject.projectDir.absolutePath) } - for (task in listOf("compileKotlinJvm", "compileKotlinJs", "jvmSourcesJar", "jsSourcesJar")) { - named(task) { - dependsOn("kspCommonMainKotlinMetadata") + afterEvaluate { + val compilationTasks = kotlin.targets.flatMap { + buildList { + add("compileKotlin${it.name.capitalized()}") + val sourcesJarName = "${it.name}SourcesJar" + add(sourcesJarName) + } } + for (task in compilationTasks) { + named(task) { + dependsOn("kspCommonMainKotlinMetadata") + } + } + } + + + register("publishNonNative") { + dependsOn( + "publishKotlinMultiplatformPublicationToMavenRepository", + "publishJsPublicationToMavenRepository", + "publishJvmPublicationToMavenRepository" + ) + } + + register("publishLinux") { + dependsOn("publishLinuxX64PublicationToMavenRepository") + } + + register("publishMingw") { + dependsOn("publishMingwX64PublicationToMavenRepository") + } + + register("publishDarwin") { + dependsOn( + "publishMacosArm64PublicationToMavenRepository", + "publishMacosX64PublicationToMavenRepository", + "publishIosArm64PublicationToMavenRepository", + "publishIosX64PublicationToMavenRepository", + "publishWatchosX64PublicationToMavenRepository", + "publishWatchosArm64PublicationToMavenRepository", + "publishTvosX64PublicationToMavenRepository", + "publishTvosArm64PublicationToMavenRepository" + ) } afterEvaluate { diff --git a/buildSrc/src/main/kotlin/kord-native-module.gradle.kts b/buildSrc/src/main/kotlin/kord-native-module.gradle.kts new file mode 100644 index 00000000000..548c59f8950 --- /dev/null +++ b/buildSrc/src/main/kotlin/kord-native-module.gradle.kts @@ -0,0 +1,32 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + org.jetbrains.kotlin.multiplatform +} + +kotlin { + if(!Os.isFamily(Os.FAMILY_WINDOWS)) { + linuxX64() + // Waiting for Ktor + // https://youtrack.jetbrains.com/issue/KTOR-872 + //linuxArm64() + } + + + mingwX64() + + macosArm64() + macosX64() + + iosArm64() + iosX64() +// iosSimulatorArm64() + + watchosX64() + watchosArm64() +// watchosSimulatorArm64() + + tvosX64() + tvosArm64() +// tvosSimulatorArm64() +} diff --git a/buildSrc/src/main/kotlin/kord-publishing.gradle.kts b/buildSrc/src/main/kotlin/kord-publishing.gradle.kts index a001f7113cc..4f7222f05ef 100644 --- a/buildSrc/src/main/kotlin/kord-publishing.gradle.kts +++ b/buildSrc/src/main/kotlin/kord-publishing.gradle.kts @@ -75,5 +75,5 @@ signing { val secretKey = getenv("SIGNING_KEY")?.let { String(Base64.getDecoder().decode(it)) } val password = getenv("SIGNING_PASSWORD") useInMemoryPgpKeys(secretKey, password) - sign(publishing.publications) + //sign(publishing.publications) } diff --git a/common/api/common.api b/common/api/common.api index 98c6e53cc2d..2a8c470f335 100644 --- a/common/api/common.api +++ b/common/api/common.api @@ -2435,6 +2435,96 @@ public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetada public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord { + public static final field Companion Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord$Companion; + public synthetic fun (ILdev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;)V + public synthetic fun (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ldev/kord/common/entity/optional/Optional; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ldev/kord/common/entity/optional/Optional; + public final fun copy (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;)Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord; + public static synthetic fun copy$default (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord;Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;ILjava/lang/Object;)Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getDescriptionLocalizations ()Ldev/kord/common/entity/optional/Optional; + public final fun getKey ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getNameLocalizations ()Ldev/kord/common/entity/optional/Optional; + public final fun getType ()Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public static final synthetic fun write$Self (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field Companion Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$Companion; + public synthetic fun (ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun equals (Ljava/lang/Object;)Z + public final fun getValue ()I + public final fun hashCode ()I + public final fun toString ()Ljava/lang/String; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$BooleanEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$BooleanEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$BooleanNotEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$BooleanNotEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$Companion { + public final fun getEntries ()Ljava/util/List; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$DateTimeGreaterThanOrEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$DateTimeGreaterThanOrEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$DateTimeLessThanOrEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$DateTimeLessThanOrEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerGreaterThanOrEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerGreaterThanOrEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerLessThanOrEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerLessThanOrEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerNotEqual : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public static final field INSTANCE Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$IntegerNotEqual; +} + +public final class dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType$Unknown : dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType { + public fun (I)V +} + public final class dev/kord/common/entity/DiscordAttachment { public static final field Companion Ldev/kord/common/entity/DiscordAttachment$Companion; public synthetic fun (ILdev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ldev/kord/common/entity/optional/Optional;ILjava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/OptionalInt;Ldev/kord/common/entity/optional/OptionalInt;Ldev/kord/common/entity/optional/OptionalBoolean;Ldev/kord/common/entity/optional/Optional;Ldev/kord/common/entity/optional/Optional;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 1d592ab41f0..d98e91a26b4 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + `kord-native-module` `kord-multiplatform-module` `kord-publishing` alias(libs.plugins.buildconfig) @@ -39,6 +40,31 @@ kotlin { implementation(projects.kspAnnotations) } } + + nativeMain { + dependencies { + // Native does not have compileOnly + implementation(projects.kspAnnotations) + } + } + + mingwMain { + dependencies { + api(libs.ktor.client.winhttp) + } + } + + appleMain { + dependencies { + api(libs.ktor.client.darwin) + } + } + + findByName("linuxMain")?.apply { + dependencies { + api(libs.ktor.client.curl) + } + } } } diff --git a/common/build/generated/ksp/metadata/commonMain/kotlin/dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType.kt b/common/build/generated/ksp/metadata/commonMain/kotlin/dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType.kt new file mode 100644 index 00000000000..df3e645a038 --- /dev/null +++ b/common/build/generated/ksp/metadata/commonMain/kotlin/dev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType.kt @@ -0,0 +1,134 @@ +// THIS FILE IS AUTO-GENERATED, DO NOT EDIT! +@file:Suppress(names = arrayOf("RedundantVisibilityModifier", "IncorrectFormatting", + "ReplaceArrayOfWithLiteral", "SpellCheckingInspection", "GrazieInspection")) + +package dev.kord.common.entity + +import kotlin.LazyThreadSafetyMode.PUBLICATION +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * Type of [DiscordRoleConnectionMetadata] values + * + * See [DiscordApplicationRoleConnectionMetadataRecordType]s in the + * [Discord Developer Documentation](https://discord.com/developers/docs/resources/application-role-connection-metadata#application-role-connection-metadata-object-application-role-connection-metadata-type). + */ +@Serializable(with = DiscordApplicationRoleConnectionMetadataRecordType.Serializer::class) +public sealed class DiscordApplicationRoleConnectionMetadataRecordType( + /** + * The raw value used by Discord. + */ + public val `value`: Int, +) { + final override fun equals(other: Any?): Boolean = this === other || + (other is DiscordApplicationRoleConnectionMetadataRecordType && this.value == other.value) + + final override fun hashCode(): Int = value.hashCode() + + final override fun toString(): String = + "DiscordApplicationRoleConnectionMetadataRecordType.${this::class.simpleName}(value=$value)" + + /** + * An unknown [DiscordApplicationRoleConnectionMetadataRecordType]. + * + * This is used as a fallback for [DiscordApplicationRoleConnectionMetadataRecordType]s that + * haven't been added to Kord yet. + */ + public class Unknown( + `value`: Int, + ) : DiscordApplicationRoleConnectionMetadataRecordType(value) + + /** + * The metadata value (integer) is less than or equal to the guild's configured value (integer) + */ + public object IntegerLessThanOrEqual : DiscordApplicationRoleConnectionMetadataRecordType(1) + + /** + * The metadata value (integer) is greater than or equal to the guild's configured value + * (integer) + */ + public object IntegerGreaterThanOrEqual : DiscordApplicationRoleConnectionMetadataRecordType(2) + + /** + * The metadata value (integer) is equal to the guild's configured value (integer) + */ + public object IntegerEqual : DiscordApplicationRoleConnectionMetadataRecordType(3) + + /** + * The metadata value (integer) is not equal to the guild's configured value (integer) + */ + public object IntegerNotEqual : DiscordApplicationRoleConnectionMetadataRecordType(4) + + /** + * The metadata value (ISO8601 string) is less than or equal to the guild's configured value + * (integer; days before current date) + */ + public object DateTimeLessThanOrEqual : DiscordApplicationRoleConnectionMetadataRecordType(5) + + /** + * The metadata value (ISO8601 string) is greater than or equal to the guild's configured value + * (integer; days before current date) + */ + public object DateTimeGreaterThanOrEqual : DiscordApplicationRoleConnectionMetadataRecordType(6) + + /** + * The metadata value (integer) is equal to the guild's configured value (integer; 1) + */ + public object BooleanEqual : DiscordApplicationRoleConnectionMetadataRecordType(7) + + /** + * The metadata value (integer) is not equal to the guild's configured value (integer; 1) + */ + public object BooleanNotEqual : DiscordApplicationRoleConnectionMetadataRecordType(8) + + internal object Serializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("dev.kord.common.entity.DiscordApplicationRoleConnectionMetadataRecordType", + PrimitiveKind.INT) + + override fun serialize(encoder: Encoder, + `value`: DiscordApplicationRoleConnectionMetadataRecordType) { + encoder.encodeInt(value.value) + } + + override fun deserialize(decoder: Decoder): + DiscordApplicationRoleConnectionMetadataRecordType = + when (val value = decoder.decodeInt()) { + 1 -> IntegerLessThanOrEqual + 2 -> IntegerGreaterThanOrEqual + 3 -> IntegerEqual + 4 -> IntegerNotEqual + 5 -> DateTimeLessThanOrEqual + 6 -> DateTimeGreaterThanOrEqual + 7 -> BooleanEqual + 8 -> BooleanNotEqual + else -> Unknown(value) + } + } + + public companion object { + /** + * A [List] of all known [DiscordApplicationRoleConnectionMetadataRecordType]s. + */ + public val entries: List by + lazy(mode = PUBLICATION) { + listOf( + IntegerLessThanOrEqual, + IntegerGreaterThanOrEqual, + IntegerEqual, + IntegerNotEqual, + DateTimeLessThanOrEqual, + DateTimeGreaterThanOrEqual, + BooleanEqual, + BooleanNotEqual, + ) + } + + } +} diff --git a/common/src/appleMain/kotlin/HttpEngine.kt b/common/src/appleMain/kotlin/HttpEngine.kt new file mode 100644 index 00000000000..d43679d2357 --- /dev/null +++ b/common/src/appleMain/kotlin/HttpEngine.kt @@ -0,0 +1,9 @@ +package dev.kord.common.http + +import dev.kord.common.annotation.KordInternal +import io.ktor.client.engine.* +import io.ktor.client.engine.darwin.* + +/** @suppress */ +@KordInternal +public actual object HttpEngine: HttpClientEngineFactory by Darwin diff --git a/common/src/commonMain/kotlin/entity/DiscordRoleConnectionMetadata.kt b/common/src/commonMain/kotlin/entity/DiscordRoleConnectionMetadata.kt new file mode 100644 index 00000000000..c1150a053e7 --- /dev/null +++ b/common/src/commonMain/kotlin/entity/DiscordRoleConnectionMetadata.kt @@ -0,0 +1,75 @@ +@file:GenerateKordEnum( + name = "DiscordApplicationRoleConnectionMetadataRecordType", + valueType = GenerateKordEnum.ValueType.INT, + docUrl = "https://discord.com/developers/docs/resources/application-role-connection-metadata#application-role-connection-metadata-object-application-role-connection-metadata-type", + kDoc = "Type of [DiscordRoleConnectionMetadata] values", + entries = [ + GenerateKordEnum.Entry( + name = "IntegerLessThanOrEqual", + intValue = 1, + kDoc = "The metadata value (integer) is less than or equal to the guild's configured value (integer)" + ), + GenerateKordEnum.Entry( + name = "IntegerGreaterThanOrEqual", + intValue = 2, + kDoc = "The metadata value (integer) is greater than or equal to the guild's configured value (integer)" + ), + GenerateKordEnum.Entry( + name = "IntegerEqual", + intValue = 3, + kDoc = "The metadata value (integer) is equal to the guild's configured value (integer)" + ), + GenerateKordEnum.Entry( + name = "IntegerNotEqual", + intValue = 4, + kDoc = "The metadata value (integer) is not equal to the guild's configured value (integer)" + ), + GenerateKordEnum.Entry( + name = "DateTimeLessThanOrEqual", + intValue = 5, + kDoc = "The metadata value (ISO8601 string) is less than or equal to the guild's configured value (integer; days before current date)" + ), + GenerateKordEnum.Entry( + name = "DateTimeGreaterThanOrEqual", + intValue = 6, + kDoc = "The metadata value (ISO8601 string) is greater than or equal to the guild's configured value (integer; days before current date)" + ), + GenerateKordEnum.Entry( + name = "BooleanEqual", + intValue = 7, + kDoc = "The metadata value (integer) is equal to the guild's configured value (integer; 1)" + ), + GenerateKordEnum.Entry( + name = "BooleanNotEqual", + intValue = 8, + kDoc = "The metadata value (integer) is not equal to the guild's configured value (integer; 1)" + ) + ] +) + +package dev.kord.common.entity + +import dev.kord.common.Locale +import dev.kord.common.entity.optional.Optional +import dev.kord.ksp.GenerateKordEnum +import kotlinx.serialization.Serializable + +/** + * A representation of role connection metadata for an [application][DiscordApplication]. + * + * @property type [type][DiscordApplicationRoleConnectionMetadataRecordType] of metadata value + * @property key dictionary key for the metadata field (must be a-z, 0-9, or _ characters; 1-50 characters) + * @property name name of the metadata field (1-100 characters) + * @property nameLocalizations with keys in available locales translations of the name + * @property description description of the metadata field (1-200 characters) + * @property descriptionLocalizations with keys in available locales translations of the description + */ +@Serializable +public data class DiscordApplicationRoleConnectionMetadataRecord( + val type: DiscordApplicationRoleConnectionMetadataRecordType, + val key: String, + val name: String, + val nameLocalizations: Optional> = Optional.Missing(), + val description: String, + val descriptionLocalizations: Optional> = Optional.Missing() +) diff --git a/common/src/linuxMain/kotlin/HttpEngine.kt b/common/src/linuxMain/kotlin/HttpEngine.kt new file mode 100644 index 00000000000..f38a8a9aba8 --- /dev/null +++ b/common/src/linuxMain/kotlin/HttpEngine.kt @@ -0,0 +1,9 @@ +package dev.kord.common.http + +import dev.kord.common.annotation.KordInternal +import io.ktor.client.engine.* +import io.ktor.client.engine.curl.* + +/** @suppress */ +@KordInternal +public actual object HttpEngine : HttpClientEngineFactory by Curl diff --git a/common/src/mingwMain/kotlin/HttpEngine.kt b/common/src/mingwMain/kotlin/HttpEngine.kt new file mode 100644 index 00000000000..eb8489d3bed --- /dev/null +++ b/common/src/mingwMain/kotlin/HttpEngine.kt @@ -0,0 +1,9 @@ +package dev.kord.common.http + +import dev.kord.common.annotation.KordInternal +import io.ktor.client.engine.* +import io.ktor.client.engine.winhttp.* + +/** @suppress */ +@KordInternal +public actual object HttpEngine : HttpClientEngineFactory by WinHttp diff --git a/core/api/core.api b/core/api/core.api index 983cc33c83a..7da9480d095 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -2639,6 +2639,33 @@ public final class dev/kord/core/cache/data/ApplicationInteractionData$Companion public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class dev/kord/core/cache/data/ApplicationRoleConnectionMetadataData { + public static final field Companion Ldev/kord/core/cache/data/ApplicationRoleConnectionMetadataData$Companion; + public fun (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;)V + public synthetic fun (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ldev/kord/common/entity/optional/Optional; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ldev/kord/common/entity/optional/Optional; + public final fun copy (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;)Ldev/kord/core/cache/data/ApplicationRoleConnectionMetadataData; + public static synthetic fun copy$default (Ldev/kord/core/cache/data/ApplicationRoleConnectionMetadataData;Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType;Ljava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;ILjava/lang/Object;)Ldev/kord/core/cache/data/ApplicationRoleConnectionMetadataData; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getDescriptionLocalizations ()Ldev/kord/common/entity/optional/Optional; + public final fun getKey ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getNameLocalizations ()Ldev/kord/common/entity/optional/Optional; + public final fun getType ()Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/kord/core/cache/data/ApplicationRoleConnectionMetadataData$Companion { + public final fun from (Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecord;)Ldev/kord/core/cache/data/ApplicationRoleConnectionMetadataData; +} + public final class dev/kord/core/cache/data/AttachmentData { public static final field Companion Ldev/kord/core/cache/data/AttachmentData$Companion; public synthetic fun (ILdev/kord/common/entity/Snowflake;Ljava/lang/String;Ldev/kord/common/entity/optional/Optional;Ldev/kord/common/entity/optional/Optional;ILjava/lang/String;Ljava/lang/String;Ldev/kord/common/entity/optional/OptionalInt;Ldev/kord/common/entity/optional/OptionalInt;Ldev/kord/common/entity/optional/OptionalBoolean;Ldev/kord/common/entity/optional/Optional;Ldev/kord/common/entity/optional/Optional;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V @@ -7623,6 +7650,17 @@ public final class dev/kord/core/entity/application/MessageCommand$DefaultImpls public static fun isNsfw (Ldev/kord/core/entity/application/MessageCommand;)Z } +public final class dev/kord/core/entity/application/RoleConnectionMetadataRecord { + public fun (Ldev/kord/core/cache/data/ApplicationRoleConnectionMetadataData;)V + public final fun getData ()Ldev/kord/core/cache/data/ApplicationRoleConnectionMetadataData; + public final fun getDescription ()Ljava/lang/String; + public final fun getDescriptionLocalizations ()Ljava/util/Map; + public final fun getKey ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getNameLocalizations ()Ljava/util/Map; + public final fun getType ()Ldev/kord/common/entity/DiscordApplicationRoleConnectionMetadataRecordType; +} + public final class dev/kord/core/entity/application/UnknownGlobalApplicationCommand : dev/kord/core/entity/application/GlobalApplicationCommand { public fun (Ldev/kord/core/cache/data/ApplicationCommandData;Ldev/kord/rest/service/InteractionService;)V public fun compareTo (Ldev/kord/core/entity/Entity;)I diff --git a/core/build.gradle.kts b/core/build.gradle.kts index ac5deb38510..e70af650cfb 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + `kord-native-module` `kord-multiplatform-module` `kord-publishing` } diff --git a/core/src/commonMain/kotlin/cache/data/ApplicationRoleConnectionMetadataData.kt b/core/src/commonMain/kotlin/cache/data/ApplicationRoleConnectionMetadataData.kt new file mode 100644 index 00000000000..fd3faf8a2c5 --- /dev/null +++ b/core/src/commonMain/kotlin/cache/data/ApplicationRoleConnectionMetadataData.kt @@ -0,0 +1,39 @@ +package dev.kord.core.cache.data + +import dev.kord.common.Locale +import dev.kord.common.entity.DiscordApplication +import dev.kord.common.entity.DiscordApplicationRoleConnectionMetadataRecord +import dev.kord.common.entity.DiscordApplicationRoleConnectionMetadataRecordType +import dev.kord.common.entity.optional.Optional + +/** + * A representation of role connection metadata for an [application][DiscordApplication]. + * + * @property type [type][DiscordApplicationRoleConnectionMetadataRecordType] of metadata value + * @property key dictionary key for the metadata field (must be a-z, 0-9, or _ characters; 1-50 characters) + * @property name name of the metadata field (1-100 characters) + * @property nameLocalizations with keys in available locales translations of the name + * @property description description of the metadata field (1-200 characters) + * @property descriptionLocalizations with keys in available locales translations of the description + */ + +public data class ApplicationRoleConnectionMetadataData( + val type: DiscordApplicationRoleConnectionMetadataRecordType, + val key: String, + val name: String, + val nameLocalizations: Optional> = Optional.Missing(), + val description: String, + val descriptionLocalizations: Optional> = Optional.Missing() +) { + public companion object { + public fun from(record: DiscordApplicationRoleConnectionMetadataRecord): ApplicationRoleConnectionMetadataData = + ApplicationRoleConnectionMetadataData( + record.type, + record.key, + record.name, + record.nameLocalizations, + record.description, + record.descriptionLocalizations + ) + } +} diff --git a/core/src/commonMain/kotlin/entity/application/RoleConnectionMetadataRecord.kt b/core/src/commonMain/kotlin/entity/application/RoleConnectionMetadataRecord.kt new file mode 100644 index 00000000000..d3d9c77d673 --- /dev/null +++ b/core/src/commonMain/kotlin/entity/application/RoleConnectionMetadataRecord.kt @@ -0,0 +1,26 @@ +package dev.kord.core.entity.application + +import dev.kord.common.Locale +import dev.kord.common.entity.DiscordApplicationRoleConnectionMetadataRecordType +import dev.kord.core.cache.data.ApplicationRoleConnectionMetadataData + +/** + * A representation of role connection metadata for an [application][Application]. + * + * @property type [type][DiscordApplicationRoleConnectionMetadataRecordType] of metadata value + * @property key dictionary key for the metadata field (must be a-z, 0-9, or _ characters; 1-50 characters) + * @property name name of the metadata field (1-100 characters) + * @property nameLocalizations with keys in available locales translations of the name + * @property description description of the metadata field (1-200 characters) + * @property descriptionLocalizations with keys in available locales translations of the description + */ +public class RoleConnectionMetadataRecord(public val data: ApplicationRoleConnectionMetadataData) { + public val type: DiscordApplicationRoleConnectionMetadataRecordType get() = data.type + public val key: String get() = data.key + public val name: String get() = data.name + public val nameLocalizations: Map + get() = data.nameLocalizations.value ?: emptyMap() + public val description: String get() = data.description + public val descriptionLocalizations: Map + get() = data.nameLocalizations.value ?: emptyMap() +} diff --git a/core/src/nativeMain/kotlin/KordBuilder.kt b/core/src/nativeMain/kotlin/KordBuilder.kt new file mode 100644 index 00000000000..a3d196befb4 --- /dev/null +++ b/core/src/nativeMain/kotlin/KordBuilder.kt @@ -0,0 +1,3 @@ +package dev.kord.core.builder.kord + +public actual class KordBuilder actual constructor(token: String) : BaseKordBuilder(token) diff --git a/gateway/build.gradle.kts b/gateway/build.gradle.kts index 28e43c7aeb2..8f4a510dd86 100644 --- a/gateway/build.gradle.kts +++ b/gateway/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + `kord-native-module` `kord-multiplatform-module` `kord-publishing` } diff --git a/gateway/src/nativeMain/kotlin/DefaultGateway.kt b/gateway/src/nativeMain/kotlin/DefaultGateway.kt new file mode 100644 index 00000000000..2224a4ebe0c --- /dev/null +++ b/gateway/src/nativeMain/kotlin/DefaultGateway.kt @@ -0,0 +1,11 @@ +package dev.kord.gateway + +import dev.kord.common.annotation.KordInternal +import io.ktor.util.network.* +import kotlin.experimental.ExperimentalNativeApi + +@KordInternal +public actual fun Throwable.isTimeout(): Boolean = this is UnresolvedAddressException + +@OptIn(ExperimentalNativeApi::class) +internal actual val os: String get() = Platform.osFamily.name diff --git a/gateway/src/nativeMain/kotlin/Inflater.kt b/gateway/src/nativeMain/kotlin/Inflater.kt new file mode 100644 index 00000000000..4fbca05c13c --- /dev/null +++ b/gateway/src/nativeMain/kotlin/Inflater.kt @@ -0,0 +1,52 @@ +package dev.kord.gateway + +import io.ktor.utils.io.core.* +import io.ktor.websocket.* +import kotlinx.cinterop.* +import platform.zlib.* + +private const val MAX_WBITS = 15 // Maximum window size in bits +private const val CHUNK_SIZE = 256 * 1000 + +@OptIn(ExperimentalForeignApi::class) +internal actual class Inflater : Closeable { + private val zStream = nativeHeap.alloc().apply { + val initResponse = inflateInit2(ptr, MAX_WBITS) + if (initResponse != Z_OK) { + nativeHeap.free(this) + throw IllegalStateException("Could not initialize zlib: $initResponse") + } + } + + actual fun Frame.inflateData(): String { + val compressedData = data + var out = ByteArray(0) + memScoped { + val uncompressedDataSize = CHUNK_SIZE // allocate enough space for the uncompressed data + val uncompressedData = allocArray(uncompressedDataSize) + zStream.apply { + next_in = compressedData.refTo(0).getPointer(memScope).reinterpret() + avail_in = compressedData.size.convert() + } + + do { + zStream.apply { + next_out = uncompressedData + avail_out = uncompressedDataSize.convert() + } + val resultCode = inflate(zStream.ptr, Z_NO_FLUSH) + if (resultCode != Z_OK && resultCode != Z_STREAM_END) { + throw IllegalStateException("An error occurred during decompression of frame: $resultCode") + } + out += uncompressedData.readBytes(uncompressedDataSize - zStream.avail_out.toInt()) + } while (zStream.avail_out == 0u) + } + + return out.decodeToString() + } + + override fun close() { + inflateEnd(zStream.ptr) + nativeHeap.free(zStream) + } +} diff --git a/gradle.properties b/gradle.properties index 09f960ded7b..b2378e076cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,3 +10,7 @@ kotlin.code.style=official # https://github.com/Kotlin/kotlinx-atomicfu#atomicfu-compiler-plugin kotlinx.atomicfu.enableJvmIrTransformation=true kotlinx.atomicfu.enableJsIrTransformation=true + +# We are aware of these issues and their symptoms don't affect us +kotlin.native.ignoreIncorrectDependencies=true +kotlin.native.ignoreDisabledTargets=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8c9e24129c1..3673fdf51d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,8 @@ kotlinx-coroutines = "1.7.2" # https://github.com/Kotlin/kotlinx.coroutines kotlinx-serialization = "1.5.1" # https://github.com/Kotlin/kotlinx.serialization kotlinx-datetime = "0.4.0" # https://github.com/Kotlin/kotlinx-datetime kotlin-logging = "3.0.5" # https://github.com/oshai/kotlin-logging -kord-cache = "0.4.0" # https://github.com/kordlib/cache +# This is the specific release supporting native, until we setup CI +kord-cache = "0.5.x-20230411.160140-1" # https://github.com/kordlib/cache # implementation dependencies kotlin-node = "18.16.12-pre.591-compat" # https://github.com/JetBrains/kotlin-wrappers @@ -23,13 +24,14 @@ kotlinpoet = "1.14.2" # https://github.com/square/kotlinpoet junit5 = "5.9.3" # https://github.com/junit-team/junit5 mockk = "1.13.5" # https://github.com/mockk/mockk slf4j = "2.0.7" # https://www.slf4j.org +okio = "3.5.0" # plugins dokka = "1.8.20" # https://github.com/Kotlin/dokka kotlinx-atomicfu = "0.21.0" # https://github.com/Kotlin/kotlinx-atomicfu binary-compatibility-validator = "0.13.2" # https://github.com/Kotlin/binary-compatibility-validator buildconfig = "4.1.2" # https://github.com/gmazzo/gradle-buildconfig-plugin - +kotlinx-resources = "0.4.0" # https://github.com/goncalossilva/kotlinx-resources [libraries] @@ -42,6 +44,9 @@ ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negoti ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } +ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" } +ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } +ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" } ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" } @@ -75,6 +80,8 @@ kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", versi junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } +kotlinx-resources = { module = "com.goncalossilva:resources", version.ref = "kotlinx-resources" } +okio = { module = "com.squareup.okio:okio", version.ref = "okio" } # actually plugins, not libraries, but used is 'buildSrc/build.gradle.kts' as implementation dependencies: # https://docs.gradle.org/current/userguide/custom_plugins.html#applying_external_plugins_in_precompiled_script_plugins @@ -84,13 +91,13 @@ dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref atomicfu-plugin = { module = "org.jetbrains.kotlinx:atomicfu-gradle-plugin", version.ref = "kotlinx-atomicfu" } binary-compatibility-validator-plugin = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "binary-compatibility-validator" } ksp-plugin = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" } - +kordx-resources-plugin = { module = "com.goncalossilva:resources-plugin", version.ref = "kotlinx-resources" } [bundles] ktor-client-serialization = ["ktor-client-content-negotiation", "ktor-serialization-kotlinx-json"] -test-common = ["kotlin-test-annotations-common", "kotlin-test", "kotlinx-coroutines-test"] +test-common = ["kotlin-test-annotations-common", "kotlin-test", "kotlinx-coroutines-test", "kotlinx-resources"] test-js = ["kotlin-test-js", "kotlin-node"] test-jvm = ["kotlin-test-junit5", "junit-jupiter-engine", "slf4j-simple"] @@ -101,6 +108,7 @@ pluginsForBuildSrc = [ "atomicfu-plugin", "binary-compatibility-validator-plugin", "ksp-plugin", + "kordx-resources-plugin" ] diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 3fe9b3c8408..4e5f9c2940c 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -157,6 +157,14 @@ diff@5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +dukat@0.5.8-rc.4: + version "0.5.8-rc.4" + resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21" + integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA== + dependencies: + google-protobuf "3.12.2" + typescript "3.9.5" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -241,6 +249,11 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +google-protobuf@3.12.2: + version "3.12.2" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.2.tgz#50ce9f9b6281235724eb243d6a83e969a2176e53" + integrity sha512-4CZhpuRr1d6HjlyrxoXoocoGFnRYgKULgMtikMddA9ztRyYR59Aondv2FioyxWVamRo0rF2XpYawkTCBEQOSkA== + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -520,6 +533,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +typescript@3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" + integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== + typescript@5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" diff --git a/ksp-annotations/build.gradle.kts b/ksp-annotations/build.gradle.kts index 0c68a988730..74548672acb 100644 --- a/ksp-annotations/build.gradle.kts +++ b/ksp-annotations/build.gradle.kts @@ -1,6 +1,7 @@ import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask plugins { + `kord-native-module` `kord-internal-multiplatform-module` // workaround for https://youtrack.jetbrains.com/issue/KT-43500 (not intended to be published) diff --git a/rest/build.gradle.kts b/rest/build.gradle.kts index 21202494c78..0d0f79756fb 100644 --- a/rest/build.gradle.kts +++ b/rest/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + `kord-native-module` `kord-multiplatform-module` `kord-publishing` } diff --git a/rest/src/commonMain/kotlin/ratelimit/AbstractRateLimiter.kt b/rest/src/commonMain/kotlin/ratelimit/AbstractRateLimiter.kt index b7803871eb8..c623e61dea0 100644 --- a/rest/src/commonMain/kotlin/ratelimit/AbstractRateLimiter.kt +++ b/rest/src/commonMain/kotlin/ratelimit/AbstractRateLimiter.kt @@ -14,6 +14,8 @@ import kotlinx.datetime.Clock import mu.KLogger import kotlin.time.Duration.Companion.minutes +internal expect val useUpdate: Boolean + public abstract class AbstractRateLimiter internal constructor(public val clock: Clock) : RequestRateLimiter { internal abstract val logger: KLogger @@ -67,7 +69,11 @@ public abstract class AbstractRateLimiter internal constructor(public val clock: when (response) { is RequestResponse.GlobalRateLimit -> { logger.trace { "[RATE LIMIT]:[GLOBAL]:exhausted until ${response.reset.value}" } - globalSuspensionPoint.update { response.reset } + if (useUpdate) { + globalSuspensionPoint.update { response.reset } + } else { + globalSuspensionPoint.value = response.reset + } } is RequestResponse.BucketRateLimit -> { logger.trace { "[RATE LIMIT]:[BUCKET]:Bucket ${response.bucketKey.value} was exhausted until ${response.reset.value}" } @@ -93,7 +99,11 @@ public abstract class AbstractRateLimiter internal constructor(public val clock: } fun updateReset(newValue: Reset) { - reset.update { newValue } + if (useUpdate) { + reset.update { newValue } + } else { + reset.value = newValue + } } fun unlock() = mutex.unlock() diff --git a/rest/src/nativeMain/kotlin/ratelimit/AbstractRateLimiter.kt b/rest/src/nativeMain/kotlin/ratelimit/AbstractRateLimiter.kt new file mode 100644 index 00000000000..fcb7c50f4b1 --- /dev/null +++ b/rest/src/nativeMain/kotlin/ratelimit/AbstractRateLimiter.kt @@ -0,0 +1,4 @@ +package dev.kord.rest.ratelimit + +// https://github.com/Kotlin/kotlinx-atomicfu/issues/291 +internal actual val useUpdate: Boolean = false diff --git a/rest/src/nativeMain/kotlin/request/RecoveredStackTrace.kt b/rest/src/nativeMain/kotlin/request/RecoveredStackTrace.kt new file mode 100644 index 00000000000..091fca9aadd --- /dev/null +++ b/rest/src/nativeMain/kotlin/request/RecoveredStackTrace.kt @@ -0,0 +1,4 @@ +package dev.kord.rest.request + +// You cannot really modify stack traces in Native :( +internal actual fun RecoveredStackTrace.sanitizeStackTrace() = Unit diff --git a/rest/src/nativeTest/kotlin/dev/kord/rest/request/StackTrace.kt b/rest/src/nativeTest/kotlin/dev/kord/rest/request/StackTrace.kt new file mode 100644 index 00000000000..41e0a08d13a --- /dev/null +++ b/rest/src/nativeTest/kotlin/dev/kord/rest/request/StackTrace.kt @@ -0,0 +1,23 @@ +package dev.kord.rest.request + +import kotlin.test.assertEquals + +actual typealias StackTraceElement = String + +//kotlin.Exception +//at 0 ??? 7ff68473da75 kfun:kotlin.Throwable#(kotlin.String?){} + 117 +//at 1 ??? 7ff684f9ee89 kfun:dev.kord.rest.request.RecoveredStackTrace#(){} + 89 +//at 2 ??? 7ff684f9e939 kfun:dev.kord.rest.request.StackTraceRecoveringKtorRequestHandler.$handleCOROUTINE$23#invokeSuspend(kotlin.Result){}kotlin.Any? + 681 +//at 3 ??? 7ff684f9ed3c kfun:dev.kord.rest.request.StackTraceRecoveringKtorRequestHandler#handle(dev.kord.rest.request.Request<0:0,0:1>){0§;1§}0:1 + 300 +//at 4 ??? 7ff684fbd4c4 kfun:dev.kord.rest.request.StackTraceRecoveryTest.$test stack trace recovery$lambda$1COROUTINE$15.invokeSuspend#internal + 2740 +//-->at 5 ??? 7ff684fbdeca kfun:dev.kord.rest.request.StackTraceRecoveryTest.$test stack trace<-- +actual fun currentThreadStackTrace(): StackTraceElement = + Exception().stackTraceToString().lineSequence().filterNot(String::isBlank).drop(5).first().trim() + .substringAfter("???") + +internal actual fun RecoveredStackTrace.validate(expected: StackTraceElement) { + // The first few lines are artifacts from coroutines which are not present in expected + val actual = stackTraceToString().lineSequence().drop(6).first().trim() + .substringAfter("???") // index is off at call site + assertEquals(expected, actual) +} diff --git a/rest/src/nonNativeMain/kotlin/AbstractRateLimiter.kt b/rest/src/nonNativeMain/kotlin/AbstractRateLimiter.kt new file mode 100644 index 00000000000..f48a77ecc64 --- /dev/null +++ b/rest/src/nonNativeMain/kotlin/AbstractRateLimiter.kt @@ -0,0 +1,3 @@ +package dev.kord.rest.ratelimit + +internal actual val useUpdate: Boolean = true diff --git a/test-kit/build.gradle.kts b/test-kit/build.gradle.kts index 93ad034df43..40f52c38294 100644 --- a/test-kit/build.gradle.kts +++ b/test-kit/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + `kord-native-module` `kord-internal-multiplatform-module` } @@ -20,5 +21,10 @@ kotlin { api(libs.bundles.test.jvm) } } + nativeMain { + dependencies { + api(libs.okio) + } + } } } diff --git a/test-kit/src/commonMain/kotlin/Annotations.kt b/test-kit/src/commonMain/kotlin/Annotations.kt index bcc93e2653d..a2b8b3ae23d 100644 --- a/test-kit/src/commonMain/kotlin/Annotations.kt +++ b/test-kit/src/commonMain/kotlin/Annotations.kt @@ -14,3 +14,8 @@ expect annotation class IgnoreOnJs() @Target(CLASS, FUNCTION) @OptionalExpectation expect annotation class IgnoreOnJvm() + +/** Ignores this test on Native platforms. */ +@Target(CLASS, FUNCTION) +@OptionalExpectation +expect annotation class IgnoreOnNative() diff --git a/test-kit/src/commonMain/kotlin/Platform.kt b/test-kit/src/commonMain/kotlin/Platform.kt index fe9f5179e53..6b22bfdf063 100644 --- a/test-kit/src/commonMain/kotlin/Platform.kt +++ b/test-kit/src/commonMain/kotlin/Platform.kt @@ -1,10 +1,15 @@ package dev.kord.test import io.ktor.utils.io.* +import com.goncalossilva.resources.Resource expect object Platform { val IS_JVM: Boolean val IS_NODE: Boolean + val IS_BROWSER: Boolean + val IS_MINGW: Boolean + val IS_LINUX: Boolean + val IS_DARWIN: Boolean } expect fun getEnv(name: String): String? diff --git a/test-kit/src/jsMain/kotlin/Platform.kt b/test-kit/src/jsMain/kotlin/Platform.kt index bf7bcae63bb..c9cc7a4220a 100644 --- a/test-kit/src/jsMain/kotlin/Platform.kt +++ b/test-kit/src/jsMain/kotlin/Platform.kt @@ -9,6 +9,13 @@ actual object Platform { get() = js( "typeof process !== 'undefined' && process.versions != null && process.versions.node != null" ) as Boolean + actual val IS_BROWSER: Boolean + get() = js( + "typeof window !== 'undefined' && typeof window.document !== 'undefined' || typeof self !== 'undefined' && typeof self.location !== 'undefined'" + ) as Boolean + actual const val IS_MINGW: Boolean = false + actual const val IS_LINUX: Boolean = false + actual const val IS_DARWIN: Boolean = false } actual fun getEnv(name: String) = process.env[name] diff --git a/test-kit/src/jvmMain/kotlin/Platform.kt b/test-kit/src/jvmMain/kotlin/Platform.kt index 85e5784737c..d70783d045a 100644 --- a/test-kit/src/jvmMain/kotlin/Platform.kt +++ b/test-kit/src/jvmMain/kotlin/Platform.kt @@ -6,6 +6,10 @@ import io.ktor.utils.io.jvm.javaio.* actual object Platform { actual const val IS_JVM: Boolean = true actual const val IS_NODE: Boolean = false + actual val IS_BROWSER: Boolean = false + actual val IS_MINGW: Boolean = false + actual val IS_LINUX: Boolean = false + actual val IS_DARWIN: Boolean = false } actual fun getEnv(name: String): String? = System.getenv(name) diff --git a/test-kit/src/nativeMain/kotlin/IgnoreOnNative.kt b/test-kit/src/nativeMain/kotlin/IgnoreOnNative.kt new file mode 100644 index 00000000000..db53b213dce --- /dev/null +++ b/test-kit/src/nativeMain/kotlin/IgnoreOnNative.kt @@ -0,0 +1,5 @@ +package dev.kord.test + +import kotlin.test.Ignore + +actual typealias IgnoreOnNative = Ignore diff --git a/test-kit/src/nativeMain/kotlin/Platform.kt b/test-kit/src/nativeMain/kotlin/Platform.kt new file mode 100644 index 00000000000..82e6e61381b --- /dev/null +++ b/test-kit/src/nativeMain/kotlin/Platform.kt @@ -0,0 +1,49 @@ +@file:OptIn(ExperimentalNativeApi::class) + +package dev.kord.test + +import io.ktor.utils.io.* +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.toKString +import okio.BufferedSource +import okio.FileSystem +import okio.IOException +import okio.Path.Companion.toPath +import platform.posix.getenv +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.Platform + +private val darwinFamilies = listOf(OsFamily.WATCHOS, OsFamily.TVOS, OsFamily.MACOSX) + +actual object Platform { + actual val IS_JVM: Boolean = false + actual val IS_NODE: Boolean = false + actual val IS_BROWSER: Boolean = false + actual val IS_MINGW: Boolean = Platform.osFamily == OsFamily.WINDOWS + actual val IS_LINUX: Boolean = Platform.osFamily == OsFamily.LINUX + actual val IS_DARWIN: Boolean = Platform.osFamily in darwinFamilies +} + +@OptIn(ExperimentalForeignApi::class) +actual fun getEnv(name: String) = getenv(name)?.toKString() + +private fun actutalPath(path: String) = + "src/commonTest/resources/$path".toPath() + +actual suspend fun file(project: String, path: String): String = read(path, BufferedSource::readUtf8) + +actual suspend fun readFile(project: String, path: String): ByteReadChannel = + read(path) { ByteReadChannel(readByteArray()) } + +private inline fun read(path: String, readerAction: BufferedSource.() -> T): T { + val actualPath = actutalPath(path) + return try { + FileSystem.SYSTEM.read(actualPath, readerAction) + } catch (e: Throwable) { + val pwd = FileSystem.SYSTEM.canonicalize(".".toPath()) + val absolutePath = pwd / actualPath + throw FileNotFoundException(absolutePath.toString(), e) + } +} + +class FileNotFoundException(absolutePath: String, cause: Throwable) : IOException("Absolute Path: $absolutePath", cause)