Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Threads #349

Merged
merged 55 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2c0bce6
thread gateway/rest implementation
HopeBaron Jul 9, 2021
544f742
add service endpoints and channel requests
HopeBaron Jul 9, 2021
58a91a3
split invite from categorizables
HopeBaron Jul 9, 2021
816e7a2
Add threads, thread users, missing routes
HopeBaron Jul 9, 2021
844cb35
more work on this
HopeBaron Jul 12, 2021
31e2ec6
core events
HopeBaron Jul 12, 2021
618a256
Almost done (behavior/event handling)
HopeBaron Jul 13, 2021
e047fa7
Fix compilation errors
HopeBaron Jul 14, 2021
ce9ae39
Consider channels with unknown but same structure
HopeBaron Jul 14, 2021
44dd2ba
implement thread parents behavior into news and text channels
HopeBaron Jul 14, 2021
74c4fc6
implement missing store functions and enable v9 for gateway
HopeBaron Jul 14, 2021
d431010
documentation
HopeBaron Jul 20, 2021
fe38a32
fix core functions and names
HopeBaron Jul 21, 2021
7d2e9e5
rename publicActiveThreads to activeThreads
HopeBaron Jul 21, 2021
5110d19
remove list prefix from routes
HopeBaron Jul 22, 2021
3e26503
Fix json incorrect and missing fields
HopeBaron Jul 22, 2021
1815672
Fix incorrect jsons
HopeBaron Jul 22, 2021
f060237
make a thread modify request a part of channel modify request
HopeBaron Jul 22, 2021
d2f6e3e
remodel thread user
HopeBaron Jul 23, 2021
1e745af
remodel thread user & add missing properties/functions
HopeBaron Jul 25, 2021
ccf26de
proper startXThread support
HopeBaron Jul 26, 2021
0caffb6
move startPublicThreadWithMessage to thread parents
HopeBaron Jul 26, 2021
019c043
Cleanup
HopeBaron Jul 26, 2021
d9e05c2
narrow down the thread channel types
HopeBaron Jul 26, 2021
570dde4
add rest tests
HopeBaron Jul 26, 2021
de6b106
fix rest tests
HopeBaron Jul 26, 2021
50bc255
fix user thread data conversion
HopeBaron Jul 27, 2021
9e5800f
add description
HopeBaron Jul 27, 2021
54436f5
register thread user data
HopeBaron Jul 27, 2021
fc9588e
add missing behaviors and clean up code
HopeBaron Jul 27, 2021
2faf1da
Add missing getter functions
HopeBaron Jul 27, 2021
a6b4097
fix withStrategy signiture
HopeBaron Jul 27, 2021
37734e7
remove has_more from ListThreadResponse
HopeBaron Jul 27, 2021
b535b6b
paginate thread suppliers
HopeBaron Jul 27, 2021
618ec26
Fix errors and correct method signitures
HopeBaron Jul 27, 2021
d08e11e
optimize imports
HopeBaron Jul 27, 2021
53fedc6
add ThreadParentChannel and cleanup its behavior
HopeBaron Jul 28, 2021
cb2998c
add core sync/updates events
HopeBaron Jul 28, 2021
42eb797
handle thread events (broken)
HopeBaron Jul 28, 2021
2c1f4cf
cleanup ChannelData
HopeBaron Jul 28, 2021
5469f5e
handle unknown channel type
HopeBaron Jul 28, 2021
1186df2
move thread related events under the same package
HopeBaron Jul 28, 2021
281ffce
correct typo
HopeBaron Jul 28, 2021
2534a79
remove thread events from the channel handlers
HopeBaron Jul 28, 2021
32d75a2
clean up and document threads
HopeBaron Jul 28, 2021
a717883
further documentation
HopeBaron Jul 28, 2021
a4b103a
further documentation (2)
HopeBaron Jul 28, 2021
ceb7509
Fix markdowns
HopeBaron Jul 28, 2021
b8aa5a4
rename the mis-leading thread-starting functions
HopeBaron Jul 28, 2021
26b4d49
Fix cache querying for threads
HopeBaron Jul 28, 2021
9e3b3cb
invert boolean check for public archived threads
HopeBaron Jul 28, 2021
607c023
apply suggestions
HopeBaron Jul 28, 2021
c7b3117
provide default values for durations
HopeBaron Jul 28, 2021
79ac641
provide unsafe thread parent behaviors
HopeBaron Jul 28, 2021
e34803b
support X-Audit-Log
HopeBaron Jul 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion common/src/main/kotlin/entity/DiscordChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ data class DiscordChannel(
val parentId: OptionalSnowflake? = OptionalSnowflake.Missing,
@SerialName("last_pin_timestamp")
val lastPinTimestamp: Optional<String?> = Optional.Missing(),
val permissions: Optional<Permissions> = Optional.Missing()
val permissions: Optional<Permissions> = Optional.Missing(),
@SerialName("message_count")
val messageCount: OptionalInt = OptionalInt.Missing,
@SerialName("member_count")
val memberCount: OptionalInt = OptionalInt.Missing,
val threadMetadata: Optional<DiscordThreadMetadata> = Optional.Missing(),
@SerialName("default_auto_archive_duration")
val defaultAutoArchiveDuration: Optional<ArchiveDuration> = Optional.Missing(),
val member: Optional<DiscordThreadMember> = Optional.Missing()
)

@Serializable(with = ChannelType.Serializer::class)
Expand Down Expand Up @@ -94,6 +102,12 @@ sealed class ChannelType(val value: Int) {
/** A channel in which game developers can sell their game on Discord. */
object GuildStore : ChannelType(6)

object PublicNewsThread : ChannelType(10)

object PrivateThread : ChannelType(11)

object PublicGuildThread : ChannelType(12)

object GuildStageVoice : ChannelType(13)

companion object;
Expand All @@ -110,6 +124,9 @@ sealed class ChannelType(val value: Int) {
4 -> GuildCategory
5 -> GuildNews
6 -> GuildStore
10 -> PublicNewsThread
11 -> PrivateThread
12 -> PublicGuildThread
13 -> GuildStageVoice
else -> Unknown(code)
}
Expand Down Expand Up @@ -154,3 +171,46 @@ sealed class OverwriteType(val value: Int) {
}
}
}

@Serializable
class DiscordThreadMetadata(
val archived: Boolean,
@SerialName("archive_timestamp")
val archiveTimestamp: String,
@SerialName("auto_archive_duration")
val autoArchiveDuration: ArchiveDuration,
val locked: OptionalBoolean = OptionalBoolean.Missing
)

@Serializable(with = ArchiveDuration.Serializer::class)
sealed class ArchiveDuration(val duration: Int) {
class Unknown(duration: Int) : ArchiveDuration(duration)
object Hour : ArchiveDuration(60)
object Day : ArchiveDuration(1440)
object ThreeDays : ArchiveDuration(4320)
object Week : ArchiveDuration(10080)
Comment on lines +193 to +194
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there's an official documentation out there, but these values are limited to a certain guild boost level, which should be documented.


object Serializer : KSerializer<ArchiveDuration> {
override fun deserialize(decoder: Decoder): ArchiveDuration {
val value = decoder.decodeInt()
return values.firstOrNull { it.duration == value } ?: Unknown(value)
}

override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("AutoArchieveDuration", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: ArchiveDuration) {
encoder.encodeInt(value.duration)
}
}

companion object {
val values: Set<ArchiveDuration>
get() = setOf(
Hour,
Day,
ThreeDays,
Week,
)
}
}
1 change: 1 addition & 0 deletions common/src/main/kotlin/entity/DiscordGuild.kt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ data class DiscordGuild(
val voiceStates: Optional<List<DiscordVoiceState>> = Optional.Missing(),
val members: Optional<List<DiscordGuildMember>> = Optional.Missing(),
val channels: Optional<List<DiscordChannel>> = Optional.Missing(),
val threads: Optional<List<DiscordChannel>> = Optional.Missing(),
val presences: Optional<List<DiscordPresenceUpdate>> = Optional.Missing(),
@SerialName("max_presences")
val maxPresences: OptionalInt? = OptionalInt.Missing,
Expand Down
9 changes: 7 additions & 2 deletions common/src/main/kotlin/entity/DiscordMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ data class DiscordMessage(
* This is a list even though the docs say it's a component
*/
val components: Optional<List<DiscordComponent>> = Optional.Missing(),
val interaction: Optional<DiscordMessageInteraction> = Optional.Missing()
val interaction: Optional<DiscordMessageInteraction> = Optional.Missing(),
val thread: Optional<DiscordChannel> = Optional.Missing()
)

/**
Expand Down Expand Up @@ -298,7 +299,11 @@ enum class MessageFlag(val code: Int) {
/* This message came from the urgent message system. */
Urgent(16),

Ephemeral(64);
HasThread(32),

Ephemeral(64),

Loading(128);
}

@Serializable(with = MessageFlags.Serializer::class)
Expand Down
11 changes: 11 additions & 0 deletions common/src/main/kotlin/entity/Member.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.kord.common.entity

import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalBoolean
import dev.kord.common.entity.optional.OptionalSnowflake
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

Expand Down Expand Up @@ -82,3 +83,13 @@ data class DiscordUpdatedGuildMember(
val premiumSince: Optional<String?> = Optional.Missing(),
val pending: OptionalBoolean = OptionalBoolean.Missing
)

@Serializable
data class DiscordThreadMember(
val id: OptionalSnowflake = OptionalSnowflake.Missing,
@SerialName("user_id")
val userId: OptionalSnowflake = OptionalSnowflake.Missing,
@SerialName("join_timestamp")
val joinTimestamp: String,
val flags: Int
)
8 changes: 7 additions & 1 deletion common/src/main/kotlin/entity/Permission.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ sealed class Permission(val code: DiscordBitSet) {
object ManageEmojis : Permission(0x40000000)
object UseSlashCommands : Permission(0x80000000)
object RequestToSpeak : Permission(0x100000000)
object ManageThreads : Permission(0x0400000000)
object UsePublicThreads : Permission(0x0800000000)
object UsePrivateThreads : Permission(0x1000000000)
object All : Permission(values.fold(EmptyBitSet()) { acc, value -> acc.add(value.code); acc })

companion object {
Expand Down Expand Up @@ -192,7 +195,10 @@ sealed class Permission(val code: DiscordBitSet) {
ManageWebhooks,
ManageEmojis,
UseSlashCommands,
RequestToSpeak
RequestToSpeak,
ManageThreads,
UsePublicThreads,
UsePrivateThreads,
)
}
}
5 changes: 5 additions & 0 deletions core/src/main/kotlin/behavior/MessageBehavior.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package dev.kord.core.behavior

import dev.kord.common.annotation.KordPreview
import dev.kord.common.entity.ArchiveDuration
import dev.kord.common.entity.Permission
import dev.kord.common.entity.Snowflake
import dev.kord.common.exception.RequestException
import dev.kord.core.Kord
import dev.kord.core.behavior.channel.MessageChannelBehavior
import dev.kord.core.behavior.channel.createMessage
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.cache.data.MessageData
import dev.kord.core.entity.*
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.MessageChannel
import dev.kord.core.entity.channel.thread.ThreadChannel
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
Expand All @@ -18,6 +22,7 @@ import dev.kord.core.supplier.getChannelOfOrNull
import dev.kord.rest.builder.message.MessageCreateBuilder
import dev.kord.rest.builder.message.MessageModifyBuilder
import dev.kord.rest.builder.webhook.EditWebhookMessageBuilder
import dev.kord.rest.json.request.StartThreadRequest
import dev.kord.rest.request.RestRequestException
import dev.kord.rest.service.RestClient
import kotlinx.coroutines.flow.Flow
Expand Down
24 changes: 24 additions & 0 deletions core/src/main/kotlin/behavior/ThreadUserBehavior.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.kord.core.behavior

import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.channel.threads.ChannelThreadBehavior
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.thread.ThreadChannel
import dev.kord.core.supplier.getChannelOf
import dev.kord.core.supplier.getChannelOfOrNull

interface ThreadUserBehavior : UserBehavior {

val threadId: Snowflake

val thread: ChannelThreadBehavior get() = ChannelThreadBehavior(threadId, kord)

suspend fun getThread(): ThreadChannel {
return supplier.getChannelOf(threadId)
}


suspend fun getThreadOrNull(): ThreadChannel? {
return supplier.getChannelOfOrNull(threadId)
}
}
BartArys marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dev.kord.core.Kord
import dev.kord.core.cache.data.MessageData
import dev.kord.core.entity.Message
import dev.kord.core.entity.Strategizable
import dev.kord.core.entity.channel.GuildChannel
import dev.kord.core.entity.channel.MessageChannel
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
Expand Down
22 changes: 21 additions & 1 deletion core/src/main/kotlin/behavior/channel/NewsChannelBehavior.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package dev.kord.core.behavior.channel

import dev.kord.common.annotation.KordPreview
import dev.kord.common.entity.ArchiveDuration
import dev.kord.common.entity.ChannelType
import dev.kord.common.entity.Permission
import dev.kord.common.entity.Snowflake
import dev.kord.common.exception.RequestException
import dev.kord.core.Kord
import dev.kord.core.behavior.channel.threads.ThreadParentChannelBehavior
import dev.kord.core.behavior.channel.threads.startThread
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.NewsChannel
import dev.kord.core.entity.channel.thread.NewsChannelThread
import dev.kord.core.entity.channel.thread.ThreadChannel
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
Expand All @@ -23,7 +29,7 @@ import kotlin.contracts.contract
/**
* The behavior of a Discord News Channel associated to a guild.
*/
interface NewsChannelBehavior : GuildMessageChannelBehavior {
interface NewsChannelBehavior : ThreadParentChannelBehavior {

/**
* Requests to get the this behavior as a [NewsChannel].
Expand Down Expand Up @@ -55,6 +61,20 @@ interface NewsChannelBehavior : GuildMessageChannelBehavior {
kord.rest.channel.followNewsChannel(id, ChannelFollowRequest(webhookChannelId = target.asString))
}


override suspend fun startPublicThread(name: String, archiveDuration: ArchiveDuration): NewsChannelThread {
return startThread(name, archiveDuration, ChannelType.PublicNewsThread) as NewsChannelThread
}

override suspend fun startPublicThreadWithMessage(
messageId: Snowflake,
name: String,
archiveDuration: ArchiveDuration
): NewsChannelThread {
return super.startPublicThreadWithMessage(messageId, name, archiveDuration) as NewsChannelThread
}


/**
* Returns a new [NewsChannelBehavior] with the given [strategy].
*/
Expand Down
23 changes: 22 additions & 1 deletion core/src/main/kotlin/behavior/channel/TextChannelBehavior.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package dev.kord.core.behavior.channel

import dev.kord.common.entity.ArchiveDuration
import dev.kord.common.entity.ChannelType
import dev.kord.common.entity.Snowflake
import dev.kord.common.exception.RequestException
import dev.kord.core.Kord
import dev.kord.core.behavior.channel.threads.PrivateThreadParentChannelBehavior
import dev.kord.core.behavior.channel.threads.startThread
import dev.kord.core.cache.data.ChannelData
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.TextChannel
import dev.kord.core.entity.channel.thread.TextChannelThread
import dev.kord.core.entity.channel.thread.ThreadChannel
import dev.kord.core.exception.EntityNotFoundException
import dev.kord.core.supplier.EntitySupplier
import dev.kord.core.supplier.EntitySupplyStrategy
Expand All @@ -17,7 +23,7 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

interface TextChannelBehavior : GuildMessageChannelBehavior {
interface TextChannelBehavior : PrivateThreadParentChannelBehavior {

/**
* Requests to get the this behavior as a [TextChannel].
Expand All @@ -36,6 +42,21 @@ interface TextChannelBehavior : GuildMessageChannelBehavior {
*/
override suspend fun asChannelOrNull(): TextChannel? = super.asChannelOrNull() as? TextChannel

override suspend fun startPublicThread(name: String, archiveDuration: ArchiveDuration): TextChannelThread {
return startThread(name, archiveDuration, ChannelType.PublicGuildThread) as TextChannelThread
}

override suspend fun startPrivateThread(name: String, archiveDuration: ArchiveDuration): TextChannelThread {
return startThread(name, archiveDuration, ChannelType.PrivateThread) as TextChannelThread
}

override suspend fun startPublicThreadWithMessage(
messageId: Snowflake,
name: String,
archiveDuration: ArchiveDuration
): TextChannelThread {
return super.startPublicThreadWithMessage(messageId, name, archiveDuration) as TextChannelThread
}

/**
* Returns a new [TextChannelBehavior] with the given [strategy].
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.kord.core.behavior.channel.threads

import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.behavior.channel.MessageChannelBehavior
import dev.kord.core.cache.data.toData
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.thread.ThreadChannel
import dev.kord.core.entity.channel.thread.ThreadUser
import dev.kord.core.supplier.EntitySupplier
import dev.kord.rest.builder.channel.thread.ThreadModifyBuilder
import kotlinx.coroutines.flow.Flow
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

interface ChannelThreadBehavior : MessageChannelBehavior {

val members: Flow<ThreadUser>
get() = supplier.getThreadMembers(id)


suspend fun removeUser(userId: Snowflake) {
kord.rest.channel.removeUserFromThread(id, userId)
}

suspend fun addUser(userId: Snowflake) {
kord.rest.channel.addUserToThread(id, userId)
}

suspend fun join() {
kord.rest.channel.joinThread(id)
}

suspend fun leave() {
kord.rest.channel.leaveThread(id)
}


}

@OptIn(ExperimentalContracts::class)
suspend inline fun ChannelThreadBehavior.edit(builder: ThreadModifyBuilder.() -> Unit): ThreadChannel {
contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) }
val appliedBuilder = ThreadModifyBuilder().apply(builder)
val patchedChannel = kord.rest.channel.patchThread(id, appliedBuilder.toRequest(), appliedBuilder.reason)
return Channel.from(patchedChannel.toData(), kord) as ThreadChannel
}

fun ChannelThreadBehavior(id: Snowflake, kord: Kord, supplier: EntitySupplier = kord.defaultSupplier): ChannelThreadBehavior {
return object: ChannelThreadBehavior {
override val kord: Kord
get() = kord
override val id: Snowflake
get() = id
override val supplier: EntitySupplier
get() = supplier

}
}
Loading