From 2269273436ace54810773e7adf2298b35f626edd Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 13:16:17 +0200 Subject: [PATCH 01/10] Add role icon data --- common/src/main/kotlin/entity/DiscordRole.kt | 6 ++++++ core/src/main/kotlin/cache/data/RoleData.kt | 4 ++++ core/src/test/kotlin/live/LiveGuildTest.kt | 4 ++++ core/src/test/kotlin/live/LiveRoleTest.kt | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/common/src/main/kotlin/entity/DiscordRole.kt b/common/src/main/kotlin/entity/DiscordRole.kt index f823ea91a269..57f3523dbe6e 100644 --- a/common/src/main/kotlin/entity/DiscordRole.kt +++ b/common/src/main/kotlin/entity/DiscordRole.kt @@ -19,6 +19,9 @@ data class DiscordRole( val name: String, val color: Int, val hoist: Boolean, + val icon: Optional, + @SerialName("unicode_emoji") + val unicodeEmoji: Optional, val position: Int, val permissions: Permissions, val managed: Boolean, @@ -41,6 +44,9 @@ data class DiscordPartialRole( val name: Optional = Optional.Missing(), val color: OptionalInt = OptionalInt.Missing, val hoist: OptionalBoolean = OptionalBoolean.Missing, + val icon: Optional = Optional.Missing(), + @SerialName("unicode_emoji") + val unicodeEmoji: Optional = Optional.Missing(), val position: OptionalInt = OptionalInt.Missing, val permissions: Optional = Optional.Missing(), val managed: OptionalBoolean = OptionalBoolean.Missing, diff --git a/core/src/main/kotlin/cache/data/RoleData.kt b/core/src/main/kotlin/cache/data/RoleData.kt index 2d04e85e9f71..0dab0173b03e 100644 --- a/core/src/main/kotlin/cache/data/RoleData.kt +++ b/core/src/main/kotlin/cache/data/RoleData.kt @@ -16,6 +16,8 @@ data class RoleData( val name: String, val color: Int, val hoisted: Boolean, + val icon: Optional, + val unicodeEmoji: Optional, val position: Int, val permissions: Permissions, val managed: Boolean, @@ -32,6 +34,8 @@ data class RoleData( name, color, hoist, + icon, + unicodeEmoji, position, permissions, managed, diff --git a/core/src/test/kotlin/live/LiveGuildTest.kt b/core/src/test/kotlin/live/LiveGuildTest.kt index 6db7ebe8a7e2..9a2b2cfd0688 100644 --- a/core/src/test/kotlin/live/LiveGuildTest.kt +++ b/core/src/test/kotlin/live/LiveGuildTest.kt @@ -256,6 +256,8 @@ class LiveGuildTest : AbstractLiveEntityTest() { name = "", color = 0, hoist = false, + icon = Optional.Missing(), + unicodeEmoji = Optional.Missing(), position = 0, permissions = Permissions(Permission.BanMembers), managed = false, @@ -285,6 +287,8 @@ class LiveGuildTest : AbstractLiveEntityTest() { name = "", color = 0, hoist = false, + icon = Optional.Missing(), + unicodeEmoji = Optional.Missing(), position = 0, permissions = Permissions(Permission.BanMembers), managed = false, diff --git a/core/src/test/kotlin/live/LiveRoleTest.kt b/core/src/test/kotlin/live/LiveRoleTest.kt index 65bbb3bf07be..5afb714ae0cb 100644 --- a/core/src/test/kotlin/live/LiveRoleTest.kt +++ b/core/src/test/kotlin/live/LiveRoleTest.kt @@ -2,6 +2,7 @@ package live import dev.kord.common.annotation.KordPreview import dev.kord.common.entity.* +import dev.kord.common.entity.optional.Optional import dev.kord.core.cache.data.RoleData import dev.kord.core.entity.Role import dev.kord.core.event.guild.GuildDeleteEvent @@ -46,6 +47,8 @@ class LiveRoleTest : AbstractLiveEntityTest() { name = "test", color = 0, hoisted = false, + icon = Optional.Missing(), + unicodeEmoji = Optional.Missing(), position = 0, permissions = Permissions(Permission.CreateInstantInvite), managed = false, @@ -72,6 +75,8 @@ class LiveRoleTest : AbstractLiveEntityTest() { name = "", color = 0, hoist = false, + icon = Optional.Missing(), + unicodeEmoji = Optional.Missing(), position = 0, permissions = Permissions(Permission.BanMembers), managed = false, From f1ec9655e251e057dab76ab54100c926824d89e7 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 13:16:55 +0200 Subject: [PATCH 02/10] This triggers my OCD --- .../kotlin/cache/data/{GuildWdigetData.kt => GuildWidgetData.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/src/main/kotlin/cache/data/{GuildWdigetData.kt => GuildWidgetData.kt} (100%) diff --git a/core/src/main/kotlin/cache/data/GuildWdigetData.kt b/core/src/main/kotlin/cache/data/GuildWidgetData.kt similarity index 100% rename from core/src/main/kotlin/cache/data/GuildWdigetData.kt rename to core/src/main/kotlin/cache/data/GuildWidgetData.kt From 22104287f24f1e470e3ef4fd3d9b575862fa10f2 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 14:31:58 +0200 Subject: [PATCH 03/10] role icons supported in REST --- core/src/main/kotlin/entity/Role.kt | 4 +++ core/src/test/kotlin/rest/RestTest.kt | 28 +++++++++++++++++++ .../kotlin/builder/role/RoleCreateBuilder.kt | 10 +++++++ .../kotlin/builder/role/RoleModifyBuilder.kt | 10 +++++++ .../main/kotlin/json/request/GuildRequests.kt | 6 ++++ 5 files changed, 58 insertions(+) diff --git a/core/src/main/kotlin/entity/Role.kt b/core/src/main/kotlin/entity/Role.kt index 23c7a90c2780..a9869ad60a68 100644 --- a/core/src/main/kotlin/entity/Role.kt +++ b/core/src/main/kotlin/entity/Role.kt @@ -27,6 +27,10 @@ data class Role( val hoisted: Boolean get() = data.hoisted + val iconUrl: String? get() = data.icon.value?.let { "https://cdn.discordapp.com/role-icons/${data.id}/$it" } + + val unicodeEmoji: String? = data.unicodeEmoji.value + val managed: Boolean get() = data.managed val mentionable: Boolean get() = data.mentionable diff --git a/core/src/test/kotlin/rest/RestTest.kt b/core/src/test/kotlin/rest/RestTest.kt index 0f0371da03f4..44ac9798f58d 100644 --- a/core/src/test/kotlin/rest/RestTest.kt +++ b/core/src/test/kotlin/rest/RestTest.kt @@ -40,6 +40,9 @@ fun imageBinary(path: String): Image { @EnabledIfEnvironmentVariable(named = "KORD_TEST_TOKEN", matches = ".+") class RestServiceTest { + // should we run checks which require boosts on the public guild? + private val boostEnabled = System.getenv("KORD_BOOST_ENABLED")?.equals("true", ignoreCase = true) ?: false + private val publicGuildId = Snowflake(322850917248663552u) private val token = System.getenv("KORD_TEST_TOKEN") @@ -545,4 +548,29 @@ class RestServiceTest { Unit } + @Test + fun `create role with image icon`() = runBlocking { + if (!boostEnabled) + return@runBlocking Unit + val guild = kord.getGuild(publicGuildId)!! + guild.createRole { + name = "Test Image Icon" + hoist = true + icon = imageBinary("images/kord_icon.png") + } + return@runBlocking Unit + } + + @Test + fun `create role with unicode icon`() = runBlocking { + if (!boostEnabled) + return@runBlocking Unit + val guild = kord.getGuild(publicGuildId)!! + guild.createRole { + name = "Test Unicode Icon" + hoist = true + unicodeEmoji = "\uD83D\uDE04" + } + return@runBlocking Unit + } } diff --git a/rest/src/main/kotlin/builder/role/RoleCreateBuilder.kt b/rest/src/main/kotlin/builder/role/RoleCreateBuilder.kt index b8cebef5eee7..8f2647848106 100644 --- a/rest/src/main/kotlin/builder/role/RoleCreateBuilder.kt +++ b/rest/src/main/kotlin/builder/role/RoleCreateBuilder.kt @@ -6,6 +6,8 @@ import dev.kord.common.entity.Permissions import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.delegate.delegate +import dev.kord.common.entity.optional.map +import dev.kord.rest.Image import dev.kord.rest.builder.AuditRequestBuilder import dev.kord.rest.json.request.GuildRoleCreateRequest @@ -19,6 +21,12 @@ class RoleCreateBuilder : AuditRequestBuilder { private var _hoist: OptionalBoolean = OptionalBoolean.Missing var hoist: Boolean? by ::_hoist.delegate() + private var _icon: Optional = Optional.Missing() + var icon: Image? by ::_icon.delegate() + + private var _unicodeEmoji: Optional = Optional.Missing() + var unicodeEmoji: String? by ::_unicodeEmoji.delegate() + private var _name: Optional = Optional.Missing() var name: String? by ::_name.delegate() @@ -31,6 +39,8 @@ class RoleCreateBuilder : AuditRequestBuilder { override fun toRequest(): GuildRoleCreateRequest = GuildRoleCreateRequest( color = _color, separate = _hoist, + icon = _icon.map { it.dataUri }, + unicodeEmoji = _unicodeEmoji, name = _name, mentionable = _mentionable, permissions = _permissions diff --git a/rest/src/main/kotlin/builder/role/RoleModifyBuilder.kt b/rest/src/main/kotlin/builder/role/RoleModifyBuilder.kt index 9b12acad0936..293178f77340 100644 --- a/rest/src/main/kotlin/builder/role/RoleModifyBuilder.kt +++ b/rest/src/main/kotlin/builder/role/RoleModifyBuilder.kt @@ -6,6 +6,8 @@ import dev.kord.common.entity.Permissions import dev.kord.common.entity.optional.Optional import dev.kord.common.entity.optional.OptionalBoolean import dev.kord.common.entity.optional.delegate.delegate +import dev.kord.common.entity.optional.map +import dev.kord.rest.Image import dev.kord.rest.builder.AuditRequestBuilder import dev.kord.rest.json.request.GuildRoleModifyRequest @@ -19,6 +21,12 @@ class RoleModifyBuilder : AuditRequestBuilder { private var _hoist: OptionalBoolean? = OptionalBoolean.Missing var hoist: Boolean? by ::_hoist.delegate() + private var _icon: Optional = Optional.Missing() + var icon: Image? by ::_icon.delegate() + + private var _unicodeEmoji: Optional = Optional.Missing() + var unicodeEmoji: String? by ::_unicodeEmoji.delegate() + private var _name: Optional = Optional.Missing() var name: String? by ::_name.delegate() @@ -32,6 +40,8 @@ class RoleModifyBuilder : AuditRequestBuilder { name = _name, color = _color, separate = _hoist, + icon = _icon.map { it.dataUri }, + unicodeEmoji = _unicodeEmoji, mentionable = _mentionable, permissions = _permissions ) diff --git a/rest/src/main/kotlin/json/request/GuildRequests.kt b/rest/src/main/kotlin/json/request/GuildRequests.kt index 65beb605b5ae..3592e13b2f6f 100644 --- a/rest/src/main/kotlin/json/request/GuildRequests.kt +++ b/rest/src/main/kotlin/json/request/GuildRequests.kt @@ -123,6 +123,9 @@ data class GuildRoleCreateRequest( val color: Optional = Optional.Missing(), @SerialName("hoist") val separate: OptionalBoolean = OptionalBoolean.Missing, + val icon: Optional = Optional.Missing(), + @SerialName("unicode_emoji") + val unicodeEmoji: Optional = Optional.Missing(), val mentionable: OptionalBoolean = OptionalBoolean.Missing, val id: OptionalSnowflake = OptionalSnowflake.Missing, ) @@ -160,6 +163,9 @@ data class GuildRoleModifyRequest( val color: Optional = Optional.Missing(), @SerialName("hoist") val separate: OptionalBoolean? = OptionalBoolean.Missing, + val icon: Optional = Optional.Missing(), + @SerialName("unicode_emoji") + val unicodeEmoji: Optional = Optional.Missing(), val mentionable: OptionalBoolean? = OptionalBoolean.Missing, ) From a6e7803111020ab6f56bb0bec38b83ce16ea19f5 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 15:37:51 +0200 Subject: [PATCH 04/10] Member Avatars & Avatar->Icon refactor --- common/src/main/kotlin/entity/Member.kt | 12 +- core/src/main/kotlin/cache/data/MemberData.kt | 11 +- core/src/main/kotlin/entity/GuildEmoji.kt | 5 + core/src/main/kotlin/entity/Icon.kt | 75 ++++++++++++ core/src/main/kotlin/entity/Member.kt | 9 ++ core/src/main/kotlin/entity/User.kt | 111 ++---------------- core/src/test/kotlin/live/LiveMemberTest.kt | 3 +- 7 files changed, 113 insertions(+), 113 deletions(-) create mode 100644 core/src/main/kotlin/entity/Icon.kt diff --git a/common/src/main/kotlin/entity/Member.kt b/common/src/main/kotlin/entity/Member.kt index 1ad913d510a0..815c7dd71398 100644 --- a/common/src/main/kotlin/entity/Member.kt +++ b/common/src/main/kotlin/entity/Member.kt @@ -21,7 +21,8 @@ data class DiscordGuildMember( val premiumSince: Optional = Optional.Missing(), val deaf: OptionalBoolean = OptionalBoolean.Missing, val mute: OptionalBoolean = OptionalBoolean.Missing, - val pending: OptionalBoolean = OptionalBoolean.Missing + val pending: OptionalBoolean = OptionalBoolean.Missing, + val avatar: Optional = Optional.Missing(), ) @@ -39,7 +40,8 @@ data class DiscordInteractionGuildMember( @SerialName("premium_since") val premiumSince: Optional = Optional.Missing(), val permissions: Permissions, - val pending: OptionalBoolean = OptionalBoolean.Missing + val pending: OptionalBoolean = OptionalBoolean.Missing, + val avatar: Optional = Optional.Missing(), ) @@ -60,7 +62,8 @@ data class DiscordAddedGuildMember( val mute: Boolean, @SerialName("guild_id") val guildId: Snowflake, - val pending: OptionalBoolean = OptionalBoolean.Missing + val pending: OptionalBoolean = OptionalBoolean.Missing, + val avatar: Optional = Optional.Missing(), ) @Serializable @@ -81,7 +84,8 @@ data class DiscordUpdatedGuildMember( val joinedAt: String, @SerialName("premium_since") val premiumSince: Optional = Optional.Missing(), - val pending: OptionalBoolean = OptionalBoolean.Missing + val pending: OptionalBoolean = OptionalBoolean.Missing, + val avatar: Optional = Optional.Missing(), ) @Serializable diff --git a/core/src/main/kotlin/cache/data/MemberData.kt b/core/src/main/kotlin/cache/data/MemberData.kt index 3f76c46f52fd..9544cf64c843 100644 --- a/core/src/main/kotlin/cache/data/MemberData.kt +++ b/core/src/main/kotlin/cache/data/MemberData.kt @@ -17,27 +17,28 @@ data class MemberData( val roles: List, val joinedAt: String, val premiumSince: Optional = Optional.Missing(), - val pending: OptionalBoolean = OptionalBoolean.Missing + val pending: OptionalBoolean = OptionalBoolean.Missing, + val avatar: Optional = Optional.Missing() ) { companion object { val description = description(MemberData::id) fun from(userId: Snowflake, guildId: Snowflake, entity: DiscordGuildMember) = with(entity) { - MemberData(userId = userId, guildId = guildId, nick, roles, joinedAt, premiumSince) + MemberData(userId = userId, guildId = guildId, nick, roles, joinedAt, premiumSince, avatar = avatar) } fun from(userId: Snowflake, guildId: Snowflake, entity: DiscordInteractionGuildMember) = with(entity) { - MemberData(userId = userId, guildId = guildId, nick, roles, joinedAt, premiumSince) + MemberData(userId = userId, guildId = guildId, nick, roles, joinedAt, premiumSince, avatar = avatar) } fun from(userId: Snowflake, entity: DiscordAddedGuildMember) = with(entity) { - MemberData(userId = userId, guildId = guildId, nick, roles, joinedAt, premiumSince) + MemberData(userId = userId, guildId = guildId, nick, roles, joinedAt, premiumSince, avatar = avatar) } fun from(entity: DiscordUpdatedGuildMember) = with(entity) { - MemberData(userId = user.id, guildId = guildId, nick, roles, joinedAt, premiumSince, pending) + MemberData(userId = user.id, guildId = guildId, nick, roles, joinedAt, premiumSince, pending, avatar = avatar) } } diff --git a/core/src/main/kotlin/entity/GuildEmoji.kt b/core/src/main/kotlin/entity/GuildEmoji.kt index b9d97cc5ccba..ddd903ce5214 100644 --- a/core/src/main/kotlin/entity/GuildEmoji.kt +++ b/core/src/main/kotlin/entity/GuildEmoji.kt @@ -103,6 +103,11 @@ class GuildEmoji( */ val user: UserBehavior? get() = userId?.let { UserBehavior(it, kord) } + /** + * The image as [Icon] object for the emoji + */ + val image: Icon get() = Icon.EmojiIcon(kord, data) + /** * Requests to delete this emoji, with the given [reason]. * diff --git a/core/src/main/kotlin/entity/Icon.kt b/core/src/main/kotlin/entity/Icon.kt new file mode 100644 index 000000000000..5d4dcb3003c0 --- /dev/null +++ b/core/src/main/kotlin/entity/Icon.kt @@ -0,0 +1,75 @@ +package dev.kord.core.entity + +import dev.kord.core.Kord +import dev.kord.core.KordObject +import dev.kord.core.cache.data.EmojiData +import dev.kord.core.cache.data.MemberData +import dev.kord.core.cache.data.UserData +import dev.kord.rest.Image + +sealed class Icon(override val kord: Kord, val animated: Boolean, private val rawAssetUri: String) : KordObject { + + val format: Image.Format + get() = when { + animated -> Image.Format.GIF + else -> Image.Format.PNG + } + + val url: String + get() = "$rawAssetUri.${format.extension}" + + fun getUrl(format: Image.Format): String? { + if (format == Image.Format.GIF && !animated) return null + return "$rawAssetUri.${format.extension}" + } + + fun getUrl(size: Image.Size): String { + return "$url?size=${size.maxRes}" + } + + fun getUrl(format: Image.Format, size: Image.Size): String? { + if (format == Image.Format.GIF && !animated) return null + return "$rawAssetUri.${format.extension}?size=${size.maxRes}" + } + + suspend fun getImage(): Image = Image.fromUrl(kord.resources.httpClient, url) + + suspend fun getImage(size: Image.Size): Image = Image.fromUrl(kord.resources.httpClient, getUrl(size)) + + suspend fun getImage(format: Image.Format): Image? = + getUrl(format)?.let { Image.fromUrl(kord.resources.httpClient, it) } + + suspend fun getImage(format: Image.Format, size: Image.Size): Image? = + getUrl(format, size)?.let { Image.fromUrl(kord.resources.httpClient, it) } + + override fun toString(): String { + return "Icon(type=${javaClass.name},animated=$animated,rawAssetUri=$rawAssetUri,kord=$kord)" + } + + class EmojiIcon(kord: Kord, data: EmojiData) : Icon( + kord, + data.animated.discordBoolean, + "$CDN_BASE_URL/emojis/${data.id.asString}" + ) + + class DefaultUserAvatar(kord: Kord, data: UserData) : + Icon(kord, false, "$CDN_BASE_URL/embed/avatars/${data.discriminator.toInt() % 5}") + + class UserAvatar(kord: Kord, data: UserData) : + Icon( + kord, + data.avatar!!.startsWith("a_"), + "$CDN_BASE_URL/avatars/${data.id.asString}/${data.avatar}" + ) + + class MemberAvatar(kord: Kord, data: MemberData) : + Icon( + kord, + data.avatar.value!!.startsWith("a_"), + "$CDN_BASE_URL/guilds/${data.guildId.asString}/users/${data.userId.asString}/avatars/${data.avatar.value!!}" + ) + + companion object { + private const val CDN_BASE_URL = "https://cdn.discordapp.com" + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/entity/Member.kt b/core/src/main/kotlin/entity/Member.kt index 58272e7e3ea9..e18ff0c826b6 100644 --- a/core/src/main/kotlin/entity/Member.kt +++ b/core/src/main/kotlin/entity/Member.kt @@ -35,6 +35,15 @@ class Member( */ val displayName: String get() = nickname ?: username + /** + * The members guild avatar as [Icon] object + */ + val memberAvatar: Icon? + get() = when { + memberData.avatar.value != null -> Icon.MemberAvatar(kord, memberData) + else -> null + } + /** * When the user joined this [guild]. */ diff --git a/core/src/main/kotlin/entity/User.kt b/core/src/main/kotlin/entity/User.kt index 0fb5da76449d..e78f6195ae67 100644 --- a/core/src/main/kotlin/entity/User.kt +++ b/core/src/main/kotlin/entity/User.kt @@ -6,7 +6,6 @@ import dev.kord.common.entity.Snowflake import dev.kord.common.entity.UserFlags import dev.kord.common.entity.UserPremium import dev.kord.core.Kord -import dev.kord.core.KordObject import dev.kord.core.behavior.UserBehavior import dev.kord.core.cache.data.UserData import dev.kord.core.supplier.EntitySupplier @@ -24,7 +23,14 @@ open class User( override val id: Snowflake get() = data.id - val avatar: Avatar get() = Avatar(data, kord) + /** + * The users avatar as [Icon] object + */ + val avatar: Icon + get() = when { + data.avatar != null -> Icon.UserAvatar(kord, data) + else -> Icon.DefaultUserAvatar(kord, data) + } /** * The username of this user. @@ -97,105 +103,4 @@ open class User( return "User(data=$data, kord=$kord, supplier=$supplier)" } - data class Avatar(val data: UserData, override val kord: Kord) : KordObject { - - /** - * The default avatar url for this user. Discord uses this for users who don't have a custom avatar set. - */ - val defaultUrl: String get() = "https://cdn.discordapp.com/embed/avatars/${data.discriminator.toInt() % 5}.png" - - /** - * Whether the user has set their avatar. - */ - val isCustom: Boolean get() = data.avatar != null - - /** - * Whether the user has an animated avatar. - */ - val isAnimated: Boolean get() = data.avatar?.startsWith("a_") ?: false - - /** - * A supported format, prioritizing [Image.Format.GIF] for animated avatars and [Image.Format.PNG] for others. - */ - val supportedFormat: Image.Format - get() = when { - isAnimated -> Image.Format.GIF - else -> Image.Format.PNG - } - - /** - * Gets the avatar url in a supported format (defined by [supportedFormat]) and default size. - */ - val url: String - get() = getUrl(supportedFormat) ?: defaultUrl - - /** - * Gets the avatar url in given [format], or returns null if the [format] is not supported. - */ - fun getUrl(format: Image.Format): String? { - val hash = data.avatar ?: return defaultUrl - if (!isAnimated && format == Image.Format.GIF) return null - - return "https://cdn.discordapp.com/avatars/${data.id.value}/$hash.${format.extension}" - } - - /** - * Gets the avatar url in a supported format and given [size]. - */ - fun getUrl(size: Image.Size): String { - return getUrl(supportedFormat, size)!! - } - - /** - * Gets the avatar url in given [format] and [size], or returns null if the [format] is not supported. - */ - fun getUrl(format: Image.Format, size: Image.Size): String? { - val hash = data.avatar ?: return defaultUrl - if (!isAnimated && format == Image.Format.GIF) return null - - return "https://cdn.discordapp.com/avatars/${data.id.value}/$hash.${format.extension}?size=${size.maxRes}" - } - - /** - * Requests to get the [defaultUrl] as an [Image]. - */ - suspend fun getDefaultImage(): Image = Image.fromUrl(kord.resources.httpClient, defaultUrl) - - /** - * Requests to get the avatar of the user as an [Image], prioritizing gif for animated avatars and png for others. - */ - suspend fun getImage(): Image = Image.fromUrl(kord.resources.httpClient, url) - - /** - * Requests to get the avatar of the user as an [Image] given [format], or returns null if the format is not supported. - */ - suspend fun getImage(format: Image.Format): Image? { - val url = getUrl(format) ?: return null - - return Image.fromUrl(kord.resources.httpClient, url) - } - - /** - * Requests to get the avatar of the user as an [Image] in given [size]. - */ - suspend fun getImage(size: Image.Size): Image { - return Image.fromUrl(kord.resources.httpClient, getUrl(size)) - } - - /** - * Requests to get the avatar of the user as an [Image] given [format] and [size], or returns null if the - * [format] is not supported. - */ - suspend fun getImage(format: Image.Format, size: Image.Size): Image? { - val url = getUrl(format, size) ?: return null - - return Image.fromUrl(kord.resources.httpClient, url) - } - - override fun toString(): String { - return "Avatar(data=$data, kord=$kord)" - } - - } - } diff --git a/core/src/test/kotlin/live/LiveMemberTest.kt b/core/src/test/kotlin/live/LiveMemberTest.kt index dfb183f6fbc2..2f61140ee777 100644 --- a/core/src/test/kotlin/live/LiveMemberTest.kt +++ b/core/src/test/kotlin/live/LiveMemberTest.kt @@ -50,7 +50,8 @@ class LiveMemberTest : AbstractLiveEntityTest() { guildId = guildId, roles = emptyList(), joinedAt = "", - premiumSince = Optional.Missing() + premiumSince = Optional.Missing(), + avatar = Optional.Missing(), ), userData = UserData( id = userId, From 25b9b85f6c3be1baa90942b7498eb14d9c8df596 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 17:11:21 +0200 Subject: [PATCH 05/10] Default to webp instead --- core/src/main/kotlin/entity/Icon.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/entity/Icon.kt b/core/src/main/kotlin/entity/Icon.kt index 5d4dcb3003c0..397efb185581 100644 --- a/core/src/main/kotlin/entity/Icon.kt +++ b/core/src/main/kotlin/entity/Icon.kt @@ -12,7 +12,7 @@ sealed class Icon(override val kord: Kord, val animated: Boolean, private val ra val format: Image.Format get() = when { animated -> Image.Format.GIF - else -> Image.Format.PNG + else -> Image.Format.WEBP } val url: String From 289f2d496a43d4ba561b25bbfed19d9ca86496d5 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 20:28:44 +0200 Subject: [PATCH 06/10] Make user avatar optional, add defaultAvatar --- core/src/main/kotlin/entity/User.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/entity/User.kt b/core/src/main/kotlin/entity/User.kt index e78f6195ae67..324b0ed7209a 100644 --- a/core/src/main/kotlin/entity/User.kt +++ b/core/src/main/kotlin/entity/User.kt @@ -26,12 +26,14 @@ open class User( /** * The users avatar as [Icon] object */ - val avatar: Icon + val avatar: Icon? get() = when { data.avatar != null -> Icon.UserAvatar(kord, data) - else -> Icon.DefaultUserAvatar(kord, data) + else -> null } + val defaultAvatar: Icon get() = Icon.DefaultUserAvatar(kord, data) + /** * The username of this user. */ From 298eeb1097bfcb4ccff7e27522bf49cbf1d48399 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 21:49:56 +0200 Subject: [PATCH 07/10] Move CDN urls to DiscordCDN --- core/src/main/kotlin/entity/GuildEmoji.kt | 2 +- core/src/main/kotlin/entity/Icon.kt | 77 +++++++++-------------- core/src/main/kotlin/entity/Member.kt | 5 +- core/src/main/kotlin/entity/User.kt | 7 +-- rest/src/main/kotlin/route/DiscordCDN.kt | 35 +++++++++++ 5 files changed, 67 insertions(+), 59 deletions(-) create mode 100644 rest/src/main/kotlin/route/DiscordCDN.kt diff --git a/core/src/main/kotlin/entity/GuildEmoji.kt b/core/src/main/kotlin/entity/GuildEmoji.kt index ddd903ce5214..d206d87612a5 100644 --- a/core/src/main/kotlin/entity/GuildEmoji.kt +++ b/core/src/main/kotlin/entity/GuildEmoji.kt @@ -106,7 +106,7 @@ class GuildEmoji( /** * The image as [Icon] object for the emoji */ - val image: Icon get() = Icon.EmojiIcon(kord, data) + val image: Icon get() = Icon.EmojiIcon(data.animated.discordBoolean, data.id, kord) /** * Requests to delete this emoji, with the given [reason]. diff --git a/core/src/main/kotlin/entity/Icon.kt b/core/src/main/kotlin/entity/Icon.kt index 397efb185581..33700ca76726 100644 --- a/core/src/main/kotlin/entity/Icon.kt +++ b/core/src/main/kotlin/entity/Icon.kt @@ -1,13 +1,13 @@ package dev.kord.core.entity +import dev.kord.common.entity.Snowflake import dev.kord.core.Kord import dev.kord.core.KordObject -import dev.kord.core.cache.data.EmojiData -import dev.kord.core.cache.data.MemberData -import dev.kord.core.cache.data.UserData import dev.kord.rest.Image +import dev.kord.rest.route.CDNUrl +import dev.kord.rest.route.DiscordCDN -sealed class Icon(override val kord: Kord, val animated: Boolean, private val rawAssetUri: String) : KordObject { +sealed class Icon(val animated: Boolean, val cdnUrl: CDNUrl, override val kord: Kord) : KordObject { val format: Image.Format get() = when { @@ -16,60 +16,39 @@ sealed class Icon(override val kord: Kord, val animated: Boolean, private val ra } val url: String - get() = "$rawAssetUri.${format.extension}" - - fun getUrl(format: Image.Format): String? { - if (format == Image.Format.GIF && !animated) return null - return "$rawAssetUri.${format.extension}" - } - - fun getUrl(size: Image.Size): String { - return "$url?size=${size.maxRes}" - } - - fun getUrl(format: Image.Format, size: Image.Size): String? { - if (format == Image.Format.GIF && !animated) return null - return "$rawAssetUri.${format.extension}?size=${size.maxRes}" - } + get() = cdnUrl.toUrl { + this.format = this@Icon.format + } - suspend fun getImage(): Image = Image.fromUrl(kord.resources.httpClient, url) + suspend fun getImage(): Image = Image.fromUrl(kord.resources.httpClient, cdnUrl.toUrl()) - suspend fun getImage(size: Image.Size): Image = Image.fromUrl(kord.resources.httpClient, getUrl(size)) + suspend fun getImage(size: Image.Size): Image = + Image.fromUrl(kord.resources.httpClient, cdnUrl.toUrl { + this.size = size + }) - suspend fun getImage(format: Image.Format): Image? = - getUrl(format)?.let { Image.fromUrl(kord.resources.httpClient, it) } + suspend fun getImage(format: Image.Format): Image = + Image.fromUrl(kord.resources.httpClient, cdnUrl.toUrl { + this.format = format + }) - suspend fun getImage(format: Image.Format, size: Image.Size): Image? = - getUrl(format, size)?.let { Image.fromUrl(kord.resources.httpClient, it) } + suspend fun getImage(format: Image.Format, size: Image.Size): Image = + Image.fromUrl(kord.resources.httpClient, cdnUrl.toUrl { + this.format = format + this.size = size + }) override fun toString(): String { - return "Icon(type=${javaClass.name},animated=$animated,rawAssetUri=$rawAssetUri,kord=$kord)" + return "Icon(type=${javaClass.name},animated=$animated,cdnUrl=$cdnUrl,kord=$kord)" } - class EmojiIcon(kord: Kord, data: EmojiData) : Icon( - kord, - data.animated.discordBoolean, - "$CDN_BASE_URL/emojis/${data.id.asString}" - ) + class EmojiIcon(animated: Boolean, emojiId: Snowflake, kord: Kord) : Icon(animated, DiscordCDN.emoji(emojiId), kord) - class DefaultUserAvatar(kord: Kord, data: UserData) : - Icon(kord, false, "$CDN_BASE_URL/embed/avatars/${data.discriminator.toInt() % 5}") + class DefaultUserAvatar(discriminator: Int, kord: Kord) : Icon(false, DiscordCDN.defaultAvatar(discriminator), kord) - class UserAvatar(kord: Kord, data: UserData) : - Icon( - kord, - data.avatar!!.startsWith("a_"), - "$CDN_BASE_URL/avatars/${data.id.asString}/${data.avatar}" - ) + class UserAvatar(userId: Snowflake, avatarHash: String, kord: Kord) : + Icon(avatarHash.startsWith("a_"), DiscordCDN.userAvatar(userId, avatarHash), kord) - class MemberAvatar(kord: Kord, data: MemberData) : - Icon( - kord, - data.avatar.value!!.startsWith("a_"), - "$CDN_BASE_URL/guilds/${data.guildId.asString}/users/${data.userId.asString}/avatars/${data.avatar.value!!}" - ) - - companion object { - private const val CDN_BASE_URL = "https://cdn.discordapp.com" - } + class MemberAvatar(guildId: Snowflake, userId: Snowflake, avatarHash: String, kord: Kord) : + Icon(avatarHash.startsWith("a_"), DiscordCDN.memberAvatar(guildId, userId, avatarHash), kord) } \ No newline at end of file diff --git a/core/src/main/kotlin/entity/Member.kt b/core/src/main/kotlin/entity/Member.kt index e18ff0c826b6..30162634834b 100644 --- a/core/src/main/kotlin/entity/Member.kt +++ b/core/src/main/kotlin/entity/Member.kt @@ -39,10 +39,7 @@ class Member( * The members guild avatar as [Icon] object */ val memberAvatar: Icon? - get() = when { - memberData.avatar.value != null -> Icon.MemberAvatar(kord, memberData) - else -> null - } + get() = memberData.avatar.value?.let { Icon.MemberAvatar(memberData.guildId, data.id, it, kord) } /** * When the user joined this [guild]. diff --git a/core/src/main/kotlin/entity/User.kt b/core/src/main/kotlin/entity/User.kt index 324b0ed7209a..493e1a05b21a 100644 --- a/core/src/main/kotlin/entity/User.kt +++ b/core/src/main/kotlin/entity/User.kt @@ -27,12 +27,9 @@ open class User( * The users avatar as [Icon] object */ val avatar: Icon? - get() = when { - data.avatar != null -> Icon.UserAvatar(kord, data) - else -> null - } + get() = data.avatar?.let { Icon.UserAvatar(data.id, it, kord) } - val defaultAvatar: Icon get() = Icon.DefaultUserAvatar(kord, data) + val defaultAvatar: Icon get() = Icon.DefaultUserAvatar(data.discriminator.toInt(), kord) /** * The username of this user. diff --git a/rest/src/main/kotlin/route/DiscordCDN.kt b/rest/src/main/kotlin/route/DiscordCDN.kt new file mode 100644 index 000000000000..68c3b4d80d66 --- /dev/null +++ b/rest/src/main/kotlin/route/DiscordCDN.kt @@ -0,0 +1,35 @@ +package dev.kord.rest.route + +import dev.kord.common.entity.Snowflake +import dev.kord.rest.Image + +object DiscordCDN { + + private const val BASE_URL = "https://cdn.discordapp.com" + + fun emoji(emojiId: Snowflake): CDNUrl = CDNUrl("$BASE_URL/emojis/${emojiId.asString}") + + fun defaultAvatar(discriminator: Int): CDNUrl = CDNUrl("$BASE_URL/embed/avatars/${discriminator % 5}") + + fun userAvatar(userId: Snowflake, hash: String): CDNUrl = CDNUrl("$BASE_URL/avatars/${userId.asString}/$hash") + + fun memberAvatar(guildId: Snowflake, userId: Snowflake, hash: String) = + CDNUrl("$BASE_URL/guilds/${guildId.asString}/users/${userId.asString}/avatars/$hash") + +} + +class CDNUrl(private val rawAssetUri: String) { + + fun toUrl(builder: UrlBuilder.() -> Unit = {}): String { + val config = UrlBuilder().apply(builder) + var cdnUrl = "$rawAssetUri.${config.format.extension}" + config.size?.let { cdnUrl += "?size=${it.maxRes}" } + return cdnUrl + } + + override fun toString(): String { + return "CdnUrl(rawAssetUri=$rawAssetUri)" + } + + data class UrlBuilder(var format: Image.Format = Image.Format.WEBP, var size: Image.Size? = null) +} \ No newline at end of file From e15613cd446c0cb0a3c852f1fea05059ae5a184b Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Thu, 30 Sep 2021 21:58:42 +0200 Subject: [PATCH 08/10] Refactor Role#iconUrl to Role#icon with new Icon type --- core/src/main/kotlin/entity/Icon.kt | 3 +++ core/src/main/kotlin/entity/Role.kt | 2 +- rest/src/main/kotlin/route/DiscordCDN.kt | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/entity/Icon.kt b/core/src/main/kotlin/entity/Icon.kt index 33700ca76726..4da7548e1f7e 100644 --- a/core/src/main/kotlin/entity/Icon.kt +++ b/core/src/main/kotlin/entity/Icon.kt @@ -51,4 +51,7 @@ sealed class Icon(val animated: Boolean, val cdnUrl: CDNUrl, override val kord: class MemberAvatar(guildId: Snowflake, userId: Snowflake, avatarHash: String, kord: Kord) : Icon(avatarHash.startsWith("a_"), DiscordCDN.memberAvatar(guildId, userId, avatarHash), kord) + + class RoleIcon(roleId: Snowflake, iconHash: String, kord: Kord) : + Icon(iconHash.startsWith("a_"), DiscordCDN.roleIcon(roleId, iconHash), kord) } \ No newline at end of file diff --git a/core/src/main/kotlin/entity/Role.kt b/core/src/main/kotlin/entity/Role.kt index a9869ad60a68..c2fab024c5d9 100644 --- a/core/src/main/kotlin/entity/Role.kt +++ b/core/src/main/kotlin/entity/Role.kt @@ -27,7 +27,7 @@ data class Role( val hoisted: Boolean get() = data.hoisted - val iconUrl: String? get() = data.icon.value?.let { "https://cdn.discordapp.com/role-icons/${data.id}/$it" } + val icon: Icon? get() = data.icon.value?.let { Icon.RoleIcon(data.id, it, kord) } val unicodeEmoji: String? = data.unicodeEmoji.value diff --git a/rest/src/main/kotlin/route/DiscordCDN.kt b/rest/src/main/kotlin/route/DiscordCDN.kt index 68c3b4d80d66..12ee14763b37 100644 --- a/rest/src/main/kotlin/route/DiscordCDN.kt +++ b/rest/src/main/kotlin/route/DiscordCDN.kt @@ -16,6 +16,7 @@ object DiscordCDN { fun memberAvatar(guildId: Snowflake, userId: Snowflake, hash: String) = CDNUrl("$BASE_URL/guilds/${guildId.asString}/users/${userId.asString}/avatars/$hash") + fun roleIcon(roleId: Snowflake, hash: String) = CDNUrl("$BASE_URL/role-icons/${roleId.asString}/$hash") } class CDNUrl(private val rawAssetUri: String) { From b25841ccba69de84c8226d61966f3db475d5f534 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Fri, 1 Oct 2021 13:13:17 +0200 Subject: [PATCH 09/10] Fix merge --- core/src/main/kotlin/entity/Icon.kt | 3 +-- rest/src/main/kotlin/route/DiscordCdn.kt | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/entity/Icon.kt b/core/src/main/kotlin/entity/Icon.kt index c2cb8c6cf0c7..bdf625bb87ae 100644 --- a/core/src/main/kotlin/entity/Icon.kt +++ b/core/src/main/kotlin/entity/Icon.kt @@ -5,7 +5,6 @@ import dev.kord.core.Kord import dev.kord.core.KordObject import dev.kord.rest.Image import dev.kord.rest.route.CdnUrl -import dev.kord.rest.route.DiscordCDN import dev.kord.rest.route.DiscordCdn sealed class Icon(val animated: Boolean, val cdnUrl: CdnUrl, override val kord: Kord) : KordObject { @@ -54,6 +53,6 @@ sealed class Icon(val animated: Boolean, val cdnUrl: CdnUrl, override val kord: Icon(avatarHash.startsWith("a_"), DiscordCdn.memberAvatar(guildId, userId, avatarHash), kord) class RoleIcon(roleId: Snowflake, iconHash: String, kord: Kord) : - Icon(iconHash.startsWith("a_"), DiscordCDN.roleIcon(roleId, iconHash), kord) + Icon(iconHash.startsWith("a_"), DiscordCdn.roleIcon(roleId, iconHash), kord) } \ No newline at end of file diff --git a/rest/src/main/kotlin/route/DiscordCdn.kt b/rest/src/main/kotlin/route/DiscordCdn.kt index dbc8df49f1fd..3eff065bc952 100644 --- a/rest/src/main/kotlin/route/DiscordCdn.kt +++ b/rest/src/main/kotlin/route/DiscordCdn.kt @@ -15,4 +15,5 @@ object DiscordCdn { fun memberAvatar(guildId: Snowflake, userId: Snowflake, hash: String) = CdnUrl("$BASE_URL/guilds/${guildId.asString}/users/${userId.asString}/avatars/$hash") + fun roleIcon(roleId: Snowflake, hash: String) = CdnUrl("$BASE_URL/role-icons/${roleId.asString}/$hash") } \ No newline at end of file From f1a2e2c93beec061d3d629d8cffd4d14481ba217 Mon Sep 17 00:00:00 2001 From: ByteAlex Date: Fri, 1 Oct 2021 13:16:06 +0200 Subject: [PATCH 10/10] Make git happy --- core/src/test/resources/images/kord_icon.png | Bin 0 -> 12685 bytes rest/src/main/kotlin/route/DiscordCDN.kt | 36 ------------------- 2 files changed, 36 deletions(-) create mode 100644 core/src/test/resources/images/kord_icon.png delete mode 100644 rest/src/main/kotlin/route/DiscordCDN.kt diff --git a/core/src/test/resources/images/kord_icon.png b/core/src/test/resources/images/kord_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..045f9247564f7ac902f356ef1842d34146cbd3d7 GIT binary patch literal 12685 zcmV;8F>=m{P)7p1z7@!k}&LH6Eu*mm#Vv*=lT6{@2#$?bUK}+)78G?_rv)l zd{Xz`bIx;~=RDha4&kp1S_Weg2mq)buI=9}=3*Bl*dGZF0Am3Zdjr@5ig_UH0>T_9 ze4x=H01*fg6dR#<9)z_3o`K>CFdhTrL0CM5b$FgCNLs!*2m=6E38e~T2Bu)zK%MTu zRL~%J+XQPFJEMSoVQ?4}uK{oX2>YpGE*TJP_Co=nS<9c+rVr$^z}ep^`#S(6fI28C z)`Rd6fCm8F1I6!9!+%o+>$lX;*@{_Bd>w?@gy3Jq{s?e56vu#ZI0*CMAsvYWkN^-s zwTaY}jZ@RV(FEgGAbo+4`Oakd33`!89*|7*ZPW*OG*D`w0gAxcQ zfpHiNQi8h%!Fg*rTbqUWWD~F1dpEN+OyL^>0A#X-kZwVDL2x+~->1{CAseGb1+LSQ zprgds5)4%{PQMV{Ae;!rDe$o`1kkXtZ>Rh2mfkn6)+rPqf~tauU|N*9iQc45E!7!Z z{`~lonA(=oq;D|!plks@0^m~kxST$XjqT&_1Uf=|EvKTIDiAHhM>jqS!e`*)rAPpc z;if9mTSPCxzydHB*VrAPA%uWY05t%0C}I#I5F8W+3LgXyKmkBefo=-GFc`NKGG$VA z>$aS{f8@cy0}$d-F#Zi2aRpTnHP6M?&g1rC#)+?kFpYiv0=yl-IVj+uts#07!N-*@ z0(1;YjMY#)4&V*$i0AWukUIySG z5cXEZ0xD)3hXXXqID9MmOkTD?>$n%d*>oyyXrGsv!i*8Wh1tQH<2gVQ*`~kgqfIfwhY486f=)o`!fWbjf^nq~*fCCW#*}_!C(CAHGXkKv2ZEo1 z@n2BfN2g-A<(YA33tKDvAXVumQnWNz1qZS@X?wn%Ao^_-@Mjzh#@hfK1Hvm2 zr2W%m3X3_j0H^>)gtZWyL8szd?en!GvBQa<4@A$!i{ay91b9cTy%S}w(TSeRC&$b7 z-U!b^@FOU$#$4P7XE+umpF5 za2X=}kWR<*jo+1Fu?mX8&e+Y3#jp_6=8&2TgMJWBhQ%uZK%>bQLB6F?5e5mKM1+5$ zFW@Iy24euaqsF&Gh_7XEE++K?oDGXgrX(dm!zb4Ij6#Xv;K70c#JB^%g}4LPHO#@x z-HQse(>8K5GG9gN^~?PgB^bO4?*!w2!{P`?rrS(rwki^Mu$guMPC3Q#7Zd6$0)98a{O2fq;~go_jkIHNxX5V6+pNE$ANJ= zd@SD5mZzywo9Y4~su;nERKdm_;S-ov;x`C?KK6#eH3)DxYDg3?69!Y7#a)DT5PTJ5 zxR@%L6J(b9#HWHy+|?JN8xEg_!Dr!P?v}Pc011k)2yrLsDA8GXe7g03X(PUtVX1;d zzl4KPzz<=t59&yoLLaTa&$-Z8z~7gg+lgz{8IL?5gp^gUQ08 zshAJ(E7b5QIve*lTL#rPvG{=q1let z3-BQ@z6C~M5(Y}=(cv$M(MM-s)%I8c+mr~QWiVCY^a8vH7T3dWf$*sx`&lG7hCYX@ zw45q||C0$1WaFny3Y?0okl+{y{!9T(BYN2UdxFsz4jy(#fFJ7xcu^)jw~3Bh&G`y2 z6pHU4z#9Al0bYyx#Lk;0W#wTwyq!+P{r^+N{Fcz9G%rGtuY<+@Qimhnl%WVn5Wq@^ z-}0Ee5d#RbSi)8@)7Gz;GMEU8ztH6<(rZzt1R&d*DM1h=M*$fmbU$Le>2HLe9NGSF z>1?b-UEYL*R&hW^9l6w>Io?#sYMl};(2*#Z%ZZ@a2JYXwRHaP8aOd%7xf>U1E$8^w z*Ql!FfD96S0Fk_r&cu`dui5T93gb$2;l=7FH=b{LG z!5Q&=f8+RVLxb7A)V)}(8v7`=}Ya{|B#5kr&} z%78Ld83g7eWXC@3{ z`aQddk-I^(r#RL2(5xt&S0v+V`Yn6L*eCu*_~}JHpYgff-?Cli=ne}@NYa1heQ(u&Q#(2E;8|`F7?maaPpRt zKsKLcnu+`9yxzl^>9?95WN10GAPO}GN`x^N@sa-7>+c;b!Sv67W}lwP_jj5 z964v_gNi*fs1vWWBAakb zKT|ks{Y5jS+2<1xo;&w=-^W|ySQP12B%aS4*7erHS?j;CJqsi|>7)A9y^0c!o(+=u zPSUJg2*dM`#G(6!fA+>ZcT}2HJBG@zeHA42bLV{~DA3o!+Qc*o0y0sk%l%~S-vgT$ z{SzlqW<(6nncMFd>Do9{2g>ryGl)Wh0&yJbuf3P9OFuG$gxB+Sd0C{g(k~LX42=K? zkVJBncgFhV+qxk0=i)ET{hXa+zJj`r&z1u4fpDWd5F~RB#=zs@=2F6^8KmhJM3LQP znO~sS*6R>%ivp^`1qCuui2mTrHPy|{!KR_ZG%GbdMn!a{3aKD9D{}2pz{-Y^IMg8e z&(sgM$)335kV+)M|MCllqcIAjIwet^lDJMuRHHhW#X0kC0BJh+wxB;tKvgQ~7+h3G z6xESLbtF-UB-)6?8goDrj2%J*MQKJ(3+HDD5@P;T5>uUtAWEvK+c#|>s3TRUXX<6} zP=e?LjJ|1y2UD9q@^|U?3+ClfL`opj;=5N8FJN{tU+SN|{$4G?%vgkOQM$+&G+&hz zN(M?6N>-JuD0y(iO0)m~1}0Jnw}5=R&~MK#h?*CXV`4K92ZXeqb2Nl9DoQ1U6wr8Wg-&M1S7BLThkYosHcf2a zR2d*95PRD@q^-rkLa3I(&ZG0f8a-o;VO5r9Iw=?`q)z$N6(_;VC0R3C_liLn0(9)z z-QN;;=H+2T3NW{Ey;{$Z6aETSo_P+7QpISd2A)GSuaVM5l0!il@i(GrEsOwV zKxV!+M5O}3B2e!c&#n>hSFC49l(lUxxE_%PzJ0}kd%frw0<_)n)7tY=x;hwq3c*0t zDAYi+CThg9hU;T^G$`T<)l@&2sX=doHe#q6Aa7fCa7_`My(^#8lnKVAL5EOE%fpPg z=<<3EkNMWHsF9pjsr%O`!+^w|l0Vb@ibn5s5QYdkdd?n)7-N^zLsEcEix^Q>V+;|Q zuhQrKJevFbXL#6Ye}x+U?eUg#DMMvCh(E7oVKukhzdn77Z0nHw@OQ7Q8BwLLS!+m@ zRMEr;U{i~r#_&<2<-Pxo2&$#=4v#P0Y%e9}eZcdrRj_N@*spQ6XvGrd8SbzO}^m{q2u4kU2D} zaO+Rod#-}sn?!(sxJA(uR_ht4(v|a0dvpT^!JYN6O~m>6UTjhcFdb;#+jWB0V5B9} z^S!MAA?5zLmB-gZafN3M1R3`?Bmi?90U{`^E6?AVt8jUUK3%q12+%K@zb_W-p7n@? zbX?I>kXmL_uVIXn;M)K&gLeI9kaK=sf2F-mk2u~O*LQr+&ymp~hW$>hj+!N7G!Ofx z{p5N~(&c-58TR`WXwbW*_fGh|1Xd7h^B@WA3Z8)|{jB(<2Opa$cjly+aL;ej!gZPm zqvPLc<@oGR*O@&(M^@K}cdk72s0073=zAcHhkY|{K}+wgm7W0bl@2wk4}o z%VjVvy(@;A2Q6ELQldf*N(m{&hDquP07riWfTOUyb8}Lv3II3?hQ=a1?VC2U4~D~- zq|37}?16U}0H9i8#>&4C0Fd&Vhz%5^Eq;6@^{rJ0bMw3l9^IgWCDTzwud>`JKmvEe za7~`FxG|(Yg*R;rrk(SZ-Yco9W0r-m3!AarJDn5RTZs@A6S_U$nl#1VA;yQkVR#=d+?O~ z0#SZlk`SPriOKX2RggF)A-W#Gwg*b4352vT;v9PLx%!z09xai3{b&Rd5dxF;1dLkD zckg!I$_EwOejJSYs}2CV&-w4`oK?kW`YfBo2*m;ptrSbZqG!_Fyyl|uQ z=kEkOWC1`EI;1D?%y8)1PKc@m#+ul<6+LI%`{z!JH&qb&OPm(n0(9YD2w1b}p8iZx ztLyfW!Rh+$8y;o+skVvYxD30=npL|u2w$pJJ61&`*ZQe)*E8$S_`ACTW8WCLM4*(L zBP0sx4U8SB$A=&%z7QSG#(+pfn+^sLRGAGFvK&CBRd!D9r>^Ac6caPokl#!Xo3rITQ#-n#ugU3lwor}GOn!JQ!kBFsch^n_9(45z$U0kZ2(J7f+l?;j=v zRl`lGN@5N1SR`v*>{bD2m=rUPYKeiW)5!m)o-X^@^|b`x>GR)D36qdgLGXyN(Zojp z_#H|mMs=zZY8sKzN10cS#JbKmc5dQSKn*uOhY3L7{hu$ty4S+<_9oFiVh4;L@<|1T z&jyL3CwIN@&Sw;ziQ6|rz~Y1<%1(tQajng)4@vB$&Y$mv}N!#Q|^Mr2T z>29+E<#L*WrhLC4gY;wt(@9`fkIz*zR4p42>3mCS_DqD(>=DZ5)C@<8gy~NJ za8#2$+bM|HrYsf0_EEn%34e3{HraDI(lVkc<8wt3nNfk^~`O{{jG5xUh4?Z@3onL=C}AD0Q3rg0}su0%`I{G7I+oj7~1` z{vzxmL&kTS$|V5li4I2~@H_@E*JYgFtQb+6qEvUNZ2$laR65T2Sz-xx^xoYZBA&7i zp$uTgv$!|GKw799PQwkVTqDYI8yo#Ml%y&6^H^c#Iw7Qvz41X&y5H9eW4-GE%(V41 zAw*h|Nn4UOTicG;>^Et%VS)e*q{Zz~ux34h>jcwk0f8U_sPa4(p)N@^d}j2dP3@)+iQJHF9^_Q!69H$dN!G%s(>m2t6-3}`fbxR5R?9D7D0ufYNuV> z*G>Mf`Q2OoPOj}IN&4Fk=esnJo5d+JkB@_p(%2Fx1aYx-2C^%o!9k}ZRIO;%xZB0m zj`-fx7Ffg7v$JHHkS3u#3lmHwKw6x|zch z)6q;@`|DHqkkhGLHmYPrsC}-|^Os^G3$#MqpFus7%;^P6pAb@m?6}*nfWcz7L0063{Q16O% zPM?|$BCVN2x7T4~n%dR{iY9JmRs?ni%H=e6n|M2`av|mXrh}!zV6sr*brwo%({elG zDq(slECJAhiQG=ZR416mtUDih&hE74v-Y3oI{gfrx)v+U_8Qx9ifGVI2 zW9@tZz-$QVs_BLpK&QfCJASQ>pKnN=Vop0$si6tuAzg3&xNg+q;Gx(N9Qbid;MJi^ zf8M=#7B2z-^!Infjg?loEb+@OymFY;p5q&z0HdO;+Y^P`x_G_WqzTA*?3)esWPK9a z3l*T1ofP>QeF`WpEZD-l$Uzi_$IW9S9(z#mL;GKR^)M5Wit0Zk>_+FE9Yrc5hf){97Dlr zwV}=`eTqs&wOrILx$%0G1{=v2N@N|#7u*WMJ6%tW$+rJgGa(IIggj9tb zk)@sdF6%@et;NYP`+xJ2E3{NH)QRI+O(>VkOqGhPKIt=isPLg-A`oq1W+gH_7P-U` z-vy+UrhRtZb#HFhz+Txv4ke2em^tg0jcCn6-C@oA@^+ZdRU(J1-%jaFav(r;e; z^VvXnV4NP0-4`f_EgEXmGdyl zWR+pw(c$qmK!6gFYN=%Qz4YsM&cEcV$J7!WGZMzvt8%T^FPdJzXuPyG52><)a-zzv zH6+};-)G#R9IKEJ40*=q9L`pFwt&#D7gwv%vU1tjqgE|a01z=hON2kELezY$7YYg!AqD-zJ)i#c&UDd5 z>t{=X2UV+1%VoyEfD+M70B*Yf;qS*1|~pVYQNm;8|OctFP3!hO)IBp{T~iTp)j@F z+p0uFs#+6D79a4<^B>hryPV-+ZWRyS+h&*oHim^ zs^VLZBbPk!be#^s0wilztG1;|t=Hduz~$#$ zm(x29^;Ju0o&7QMtKmZYv~T@A<0KJ`i+&PSw^Ptn+!)qB4B#v*TGa6n@0LIyMHNl~ z=>t?xx~#qyW6v?{E~&FROJ0GJOY`}=U)U3LA6Pm4`FuO<^cc2?j1KDTN4kTcxG}6b z0ymH|e9K#|_ZyKK@rv!p2ChTTS=F%2X8e{@#7z7{e^}F^66Lku1_0odP zSQsY){a*G^m_IRDkeKEeB?;hqyuge`^%nxbZVMOAYT~OZfgtyN@{%5nM*k&@ zMaQeB0_llnb=`hoM#B2CMOU18cSfs*mDb9+{ndOMxWq{s3vPCK0*33cNRQY35{@>W zNFdFhneFI(=9!f9#XJqo^8m+#IV46c`ylc5>~vqJg#IcMNU2^ssmJeLP){T+nBDBD zR3;Gu!+t$~ruh{u|3lS&rvQ!6>Yt9r$sdZgKq@TFb?pH|^;m>qKL6l{zqH%H!j;-d zfB#f*$htSAWy*l3LZ_+UyE&Lc3?~F(-Uk5ItXef|rCGUL=7EJP_2G|SxI44?Y%Puz zFw_={kfJT{XcXW3vb{!rssOe7W;P{SX^;RqzrtimHjQ?1D55<<&mIwbU#L_n zvS`)p*Ziy52|9&+EOY`i#ktc6CR19Zg7yc1j>hG;fJLhoQKeFmTFj^S_+5L1q5?3t zo>MSj@?bc)fGU+_BK>Sr;#d1cp_Hs2t`0}hAG&OTa-Vp<1waiChq3q^eB!>3eeK}> zYJcixoyEwBDT%C3oJK?j+K6Bps2WNH0L)9YG-zf+Tz(>Gxy=35e)s#2e*Kj`7d}25 z#sY0!$g1p$wn!VJ=-yEdexOi7wSV$U6@e^jR;u#iY9%7rbBXfEP|Nu>N}R&upr8&H zYiZD6mUI6=Wk3O7oNkfaxgQ%hu04G4x<&AEpzJvm0HI^ktl6Aq3tPF}h3mLCm=@$#bWHqyfV3?joL zap>mwJxBhb_t{gZ+V8Y9*zwfm&4EBr1?{=CGA6j!>$y>#|*DBnp=;+I!89v|Q%R#bGwTvR5sl z{%X~!&}m^JLTV_Dakr#2@o1Dt!2Y@SefaAKQMKO<&fW!}7$_S6VB!avJP=r$xP=JD z1*QNceR-!l+m6A~py^v)N$&a3B?o)#pF0-EPPOGa9H^3#8j3`mF@;m9QjzK;3wX1{ zPq|!B3_XviPG$_)o@UD3BU%px_EnKPTsnA)7EPM^vdt;|? zZ5I)VGD+(DdJQm(iY!2>v~Z=aIO(!3!@(7X{H|CO1ViKbc?OkOWlIKT zC--O3FG|c^b>{#w-0H$M6sMJ{UisjgFA7T4{#oI|UbbYJp-M$ejV|i(y8o^gCkfP= z=4)L=S$r<=NEkj@^l?_XT;_qwz@+b;l=u~-X50ZwPkt_O^jVL5Vz9KQKoP7N3B#n@ z>-pH-A6WYFzU7r>c=)lz>C+zj5T%BXU%^yQoHE&cqdj`}B|woK zdF~X3mXvMZ^1kHm4_xu_o}l-{kuXXWY};wTFk;E41p3(nmY)3FqE(Big2^qGEo!s< z**<>oe|+rPkudyTx7TAu;o37|6D3oNV+m~V^?N>e`9Jh6?@NZ3lxNifAR^UrnO}L; zzpMxB?|lw{ro{{}i9(9DJBS>P0Dw+fF+=4c+qb-utoY#N{}9;1*K1KCPRX=qB8$Rx zdp&kI3cvfxt3EoYr9m^9xX&>qQ>HXn5`~iW@#&E$dbD7=j3`NS!Xv?Eb~1Z`3I21% zKVJ6ozU7rrCZy}(P-0zGWZw(z`{~ToIOKJhHPJ@FIjQIl`k()_tmQG zC8<8r6ty!@B`KILGZNPy3H;^Z?6dUqXl$x}E z0$?~yoB(#0K)Zi(>-#U?Z?;(gM;(=35XEGERZ9|8&G-H66pk9MCkdRI*WL2||Ja=> zBr~nR=AwSZ30JPF!0pRe7p`wkbO?`ZGKJ zRabp@Sv^ipE!u7awJn}MO;7o7Es34Sh56mw^?!f!fvfiJpG`i1%-I7ce~g?w7MKDd zy_GXNrDP};aot|`Zob;10RW|$3tr`-{*DvAcW_|m+)OZg*OS;0S#F+ZRR*bXMcZv^ zNqp+7zk9;+sfzm?TQw-E_Pe1aWqZi?KKQkb;n?@)_&rt|RUUJnGK1imktB4!DZF$i zD&BO*30LovKfAS)G2KXW76;8i-}~Sw5e@kqpmB?6i}*KS;u8SOnCQ<%{hcRV^V+}` zZzi%Y7)>Gv*fh7Id7hP%UVn}q4#&Rxs_(t;Yul1kyVWat*>Z?NX`_ihwlS>zw%hBr zDx7R00o0n&ICP!~4lLN>&u%&Pn#Fy~E6Gr~^HxB^ei!eDu_#p2eYEMMk{OL7HSpee z*9U%dAS%!u8M{y@rB0l_WX1b`@J`JxPQq+vA%et1eUUYDxTH!4&#mdhuIA3=C{jzTq|xXfWGfZhPP0!T|m+ zjWI6_UF4`K^C$m=KwZF|QD{AJ@`*$L^IhLjD4`6Z3UwrUmiPb86R!Kp9KYvt8>7(# zq~lUap`7Ngj&LHcIQZIkJ`R8g)7Rgja;DLrbVC=R8Q2D<1hrVX*Tx&LoT)Xdn4isfppoDWz!=1wXCdA z>Z|l6w;%hXgJ{$ab=#hgYz%8I4VqcfmK;L^QF0y!3^ij8^q#}7efRxZ8Z_I(Jli#x zT#G+^@I7~rx!T)Rd5v$1=>ljgatJb<2(*?YuFLECm$&bE=r4YI{0)oxmiHy9N;91g zke@hy_4nVrG7;R^WqPtRed8x|q0A$3=mJytz-`C>-YTl72QGM zN4^R69gW8l0_oJH$U9;F2M|(mMNFZT&Yy#cgOwoN4;M6 zyN2U2$AD=mK@W9Zw#!5=S}}q($Mk*wgy)8q4B4ZWALTZuXepFXsxlUqG#AacmVUCQ zXNsR@rce6pb&tej4gj~Fj0GBXaurFJSG0*FPu0S)cOCh|x30+bgy|Ujv~pR#0G1zj z{foP~_nM+D9x)saCrWB)f=lPJLo?Ftb(zRT>mo@mBICYx*wt^@*z~>~x(Kwaj0*Jb zV{d+$V{?Tb^Bxs^@Kx9L-3R1H!m7O6Uv?t^ShM>Y?QiSzZO8w7 ze^UF844hc>io1_SqmZa#?%q&e-+%P=?|Nit$&l?^K0SnQ z{o*tsUjRd;!Jclr>lH;)>>rIsMbfLVc_KP0;4B%_{op+RmUKks9q*X2v z@a8VBYtC3wL!yZTh-ou(uc|^I^ai~=7LWbl>Dua#z3ayxUauuJRGr=<;X7o1Z9c?# z`>{i37{lk10+GZCHgge8TJoKoZ+z>qMVLf)isHf>pB=icvb>T2fR@x?AruwpbS{LP zdlzD%8RkF#s@p5Rb|f5i#G78$tg2$zaF;D&Bp%%e#qFy42QaNAC6AylQldo!99Xcy zJSKx$QiD^Miug&c-Bcj)*|P*7lB9C@wMP#CsdU;ACh-nauA1%m-}L|NZC$o-vCsbe z;ds27S|J~4%A-iVxHCn2cWI7f?KEbz_ zmcN9p{*H-WP)|bP?3{bs*c5j1sl+j-s{hb}!dZvI9IsoQpY7DL|G+mHRl>x}V!3PpD$(PsOZ zNvm{!S5iO;X|+m5S|fCe^Q%y2$(n*KAawQX$29!sCD*+9$-%7*(rgQ6#H?zzNpC;y zmmgQ9FLrxfyN*O7qR4fD%{-e)0*|dJngYg>8kF=NCvp>r{!7M&|9IpL?;LJ1OhUG~tWhsM7y{q(H`TdW4gD~?2?ntI%(aZXeP z1nTvAX)GRF3(_-QbIsA;YM-wii5au0%jGf;Aa$-RKklX%ccJhV!{#^wP;+&cmVem1 z-yzot6l#j5K$a~+7eUlLoT$5v$qa#UZ%<4QEUxyAOh=z@dC$*Y=6nA4m2glZ38uoy zTZE?91{7_X|KZnQ0&glE`F{?G3qnSD|GULIOW^s0F|1aN0r04W_-3Lb!BXpr7 zrR*-(%_ElvfO230^4J4|urVG{2Co)%PZ04RjM9HQ)~AffGcoa6C*j6<*n7j`>f)&V zoj1Pu#-OJcbGkT~6RON3=Dk?3vm^ZnkNCY#CBM3R+mI36XWs|spiZC|Dlv!>s9FX2Ot1ni5>QY@cemHYzVT6aA?iteo5z}o_l)QP zA{Ra+a9^d~IaeOK^w3z#$^#gfF>Y^0M>A8KSNn?pbwii*^qfebQvw_8Cn^|=YYKv3 z3no$%6xo%4Sy38A8i_MZ1R@3#Wp`-OCXc0JSkgaJCyZ)pnQWCCJhuj*;G!*(WrKQB ze;A-k!!CRIkyjo$+&=zJpyN5|3Z-RBmYIe_r!?5pWp;fJ0ViAb4)@rimei4G;!;oB z=W<6o zt)_~O6F*1l3kNrOvFLaGHy>@-KThG^)n&Wp2?X_|4pDImfD~KQ4|Z^;MJbI$F|jGw z0vL?3WNa-H-I%!Ks>280@JnPwo-IVrF>8pQqg@gJz%3{Ia=sBdTE)GW$-KVk6}kz8 zR6mH706`|Ar)pnsL%$RXB_aVp3gv)(@{JD#FdB`G0(uKGU1ywsm}{Ba#>*4l?e5L3#rPK{HbV6OUW7 zfQn|mhkScr1`)G?&mKJX;8YO05F+xJLRT0vH<7yKhhBU1BaN4pm8+|&)E?xSO_Sdlcz5cXJ4(5BLF%O(ER|+_1KcfK8OI9E(VASLQ@163fF2(cszU>%?jlkx+O^e#{JR`GftIPB`fHg2h(vlV|8#BYX{ z%bXvfYWIQ-Eg7Ffw&VW?gdko#p6h!b00000NkvXX Hu0mjfOUO?a literal 0 HcmV?d00001 diff --git a/rest/src/main/kotlin/route/DiscordCDN.kt b/rest/src/main/kotlin/route/DiscordCDN.kt deleted file mode 100644 index 12ee14763b37..000000000000 --- a/rest/src/main/kotlin/route/DiscordCDN.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.kord.rest.route - -import dev.kord.common.entity.Snowflake -import dev.kord.rest.Image - -object DiscordCDN { - - private const val BASE_URL = "https://cdn.discordapp.com" - - fun emoji(emojiId: Snowflake): CDNUrl = CDNUrl("$BASE_URL/emojis/${emojiId.asString}") - - fun defaultAvatar(discriminator: Int): CDNUrl = CDNUrl("$BASE_URL/embed/avatars/${discriminator % 5}") - - fun userAvatar(userId: Snowflake, hash: String): CDNUrl = CDNUrl("$BASE_URL/avatars/${userId.asString}/$hash") - - fun memberAvatar(guildId: Snowflake, userId: Snowflake, hash: String) = - CDNUrl("$BASE_URL/guilds/${guildId.asString}/users/${userId.asString}/avatars/$hash") - - fun roleIcon(roleId: Snowflake, hash: String) = CDNUrl("$BASE_URL/role-icons/${roleId.asString}/$hash") -} - -class CDNUrl(private val rawAssetUri: String) { - - fun toUrl(builder: UrlBuilder.() -> Unit = {}): String { - val config = UrlBuilder().apply(builder) - var cdnUrl = "$rawAssetUri.${config.format.extension}" - config.size?.let { cdnUrl += "?size=${it.maxRes}" } - return cdnUrl - } - - override fun toString(): String { - return "CdnUrl(rawAssetUri=$rawAssetUri)" - } - - data class UrlBuilder(var format: Image.Format = Image.Format.WEBP, var size: Image.Size? = null) -} \ No newline at end of file