Skip to content

Commit

Permalink
Add Auto Moderation (#647)
Browse files Browse the repository at this point in the history
* Automod models [broken]

* Fix Auto Moderation models

* Add "Discord" prefix to Auto Moderation models

* Add Auto Moderation routes

* Add Auto Moderation requests

* Add AutoModerationService

* Add Auto Moderation to Audit Log

* Add `GuildFeature.AutoModeration`

* Add `MessageType.AutoModerationAction`

* Add Auto Moderation Gateway Intents

* Add Auto Moderation Gateway Events

* Fix test

* Rename `AutoModerationKeywordPresetType` to `AutoModerationRuleKeywordPresetType`

* Remove TODOs, fields are indeed optional

* Add missing `Optional` default

* Add `DiscordAutoModerationRuleTriggerMetadata.allowList`

* Add builders

* Add docs for builders

* Add builder-taking functions to AutoModerationService

* Add cache models

* Rename package

* Add `TypedAutoModerationRuleBuilder` and `UntypedAutoModerationRuleModifyBuilder`

* Start adding core models/functions

* Add missing properties to `AutoModerationRule`

* Add `AutoModerationAction`

* Add docs for `AutoModerationRule`

* Add more documentation

* Add `AutoModerationRuleBehavior.delete`

* Add @Suppress to `assignX()` implementations

* Retrieve rules via suppliers

* Add `AutoModerationRuleBehavior.asAutoModerationRuleOrNull()` and `AutoModerationRuleBehavior.asAutoModerationRule()`

* Add info to `@suppress` tags

* Add `AutoModerationRuleBehavior.getGuildOrNull()` and `AutoModerationRuleBehavior.getGuild()`

* Let `AutoModerationAction` implement `KordObject`

* Document `ManageGuild` requirement for `asAutoModerationRule()`

* Add convenience functions for keyword matching strategies

* Change some builder methods to extensions

* Add core events and Unsafe behavior creating functions

* Register `AutoModerationRuleData` for caching

* Add `MessageContent` intent to `AutoModerationActionExecutionEvent` in `enableEvent`

* Add `autoModerationRules` to `AuditLog`

* Rename `allowList` to `allowedKeywords`

* Docs for builders

* Bound for type parameter of RequestBuilder

* Add `MentionSpam` trigger type

* Fix after merge

* Forgot api dump for gateway

* Remove `AutoModerationRuleTriggerType.HarmfulLink`

* `@KordExperimental` for `Spam` and `MentionSpam` trigger types

* Allow `Timeout` actions for `MentionSpam` rules

* KDoc for `AutoModerationRuleKeywordPresetType`

* KDoc for `AutoModerationActionType`

* Add missing removes in `removeKordData()`

* Add `KordCacheBuilder.autoModerationRules`

* Add KDoc for automod methods in `EntitySupplier`

* Adjust init blocks

* Rename user to member in `AutoModerationActionExecutionEvent`

Co-authored-by: HopeBaron <[email protected]>
  • Loading branch information
lukellmann and HopeBaron authored Aug 31, 2022
1 parent dc62be3 commit a2bfc1f
Show file tree
Hide file tree
Showing 49 changed files with 4,259 additions and 67 deletions.
305 changes: 298 additions & 7 deletions common/api/common.api

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions common/src/main/kotlin/entity/AuditLog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public data class DiscordAuditLog(
val users: List<DiscordUser>,
@SerialName("audit_log_entries")
val auditLogEntries: List<DiscordAuditLogEntry>,
@SerialName("auto_moderation_rules")
val autoModerationRules: List<DiscordAutoModerationRule>,
val integrations: List<DiscordPartialIntegration>,
val threads: List<DiscordChannel>
)
Expand Down Expand Up @@ -486,6 +488,10 @@ public sealed class AuditLogEvent(public val value: Int) {
public object ThreadUpdate : AuditLogEvent(111)
public object ThreadDelete : AuditLogEvent(112)
public object ApplicationCommandPermissionUpdate : AuditLogEvent(121)
public object AutoModerationRuleCreate : AuditLogEvent(140)
public object AutoModerationRuleUpdate : AuditLogEvent(141)
public object AutoModerationRuleDelete : AuditLogEvent(142)
public object AutoModerationBlockMessage : AuditLogEvent(143)


internal object Serializer : KSerializer<AuditLogEvent> {
Expand Down Expand Up @@ -545,6 +551,10 @@ public sealed class AuditLogEvent(public val value: Int) {
111 -> ThreadUpdate
112 -> ThreadDelete
121 -> ApplicationCommandPermissionUpdate
140 -> AutoModerationRuleCreate
141 -> AutoModerationRuleUpdate
142 -> AutoModerationRuleDelete
143 -> AutoModerationBlockMessage
else -> Unknown(value)
}
}
Expand Down
232 changes: 232 additions & 0 deletions common/src/main/kotlin/entity/AutoModeration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package dev.kord.common.entity

import dev.kord.common.annotation.KordExperimental
import dev.kord.common.entity.AutoModerationRuleTriggerType.Keyword
import dev.kord.common.entity.AutoModerationRuleTriggerType.MentionSpam
import dev.kord.common.entity.Permission.ModerateMembers
import dev.kord.common.entity.optional.Optional
import dev.kord.common.entity.optional.OptionalInt
import dev.kord.common.entity.optional.OptionalSnowflake
import dev.kord.common.serialization.DurationInSeconds
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializable
public data class DiscordAutoModerationRule(
val id: Snowflake,
@SerialName("guild_id")
val guildId: Snowflake,
val name: String,
@SerialName("creator_id")
val creatorId: Snowflake,
@SerialName("event_type")
val eventType: AutoModerationRuleEventType,
@SerialName("trigger_type")
val triggerType: AutoModerationRuleTriggerType,
@SerialName("trigger_metadata")
val triggerMetadata: DiscordAutoModerationRuleTriggerMetadata,
val actions: List<DiscordAutoModerationAction>,
val enabled: Boolean,
@SerialName("exempt_roles")
val exemptRoles: List<Snowflake>,
@SerialName("exempt_channels")
val exemptChannels: List<Snowflake>,
)

/** Characterizes the type of content which can trigger the rule. */
@Serializable(with = AutoModerationRuleTriggerType.Serializer::class)
public sealed class AutoModerationRuleTriggerType(public val value: Int) {

final override fun equals(other: Any?): Boolean =
this === other || (other is AutoModerationRuleTriggerType && this.value == other.value)

final override fun hashCode(): Int = value


/** An unknown [AutoModerationRuleTriggerType]. */
public class Unknown(value: Int) : AutoModerationRuleTriggerType(value)

/** Check if content contains words from a user defined list of keywords. */
public object Keyword : AutoModerationRuleTriggerType(1)

/**
* Check if content represents generic spam.
*
* This [trigger type][AutoModerationRuleTriggerType] is not yet released, so it cannot be used in most servers.
*/
@KordExperimental
public object Spam : AutoModerationRuleTriggerType(3)

/** Check if content contains words from internal pre-defined wordsets. */
public object KeywordPreset : AutoModerationRuleTriggerType(4)

/**
* Check if content contains more mentions than allowed.
*
* This [trigger type][AutoModerationRuleTriggerType] is not yet released, so it cannot be used in most servers.
*/
@KordExperimental
public object MentionSpam : AutoModerationRuleTriggerType(5)


internal object Serializer : KSerializer<AutoModerationRuleTriggerType> {

override val descriptor =
PrimitiveSerialDescriptor("dev.kord.common.entity.AutoModerationRuleTriggerType", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: AutoModerationRuleTriggerType) = encoder.encodeInt(value.value)

override fun deserialize(decoder: Decoder) = when (val value = decoder.decodeInt()) {
1 -> Keyword
3 -> Spam
4 -> KeywordPreset
5 -> MentionSpam
else -> Unknown(value)
}
}
}

@Serializable
public data class DiscordAutoModerationRuleTriggerMetadata(
@SerialName("keyword_filter")
val keywordFilter: Optional<List<String>> = Optional.Missing(),
val presets: Optional<List<AutoModerationRuleKeywordPresetType>> = Optional.Missing(),
@SerialName("allow_list")
val allowList: Optional<List<String>> = Optional.Missing(),
@SerialName("mention_total_limit")
val mentionTotalLimit: OptionalInt = OptionalInt.Missing,
)

/** An internally pre-defined wordset which will be searched for in content. */
@Serializable(with = AutoModerationRuleKeywordPresetType.Serializer::class)
public sealed class AutoModerationRuleKeywordPresetType(public val value: Int) {

final override fun equals(other: Any?): Boolean =
this === other || (other is AutoModerationRuleKeywordPresetType && this.value == other.value)

final override fun hashCode(): Int = value


/** An unknown [AutoModerationRuleKeywordPresetType]. */
public class Unknown(value: Int) : AutoModerationRuleKeywordPresetType(value)

/** Words that may be considered forms of swearing or cursing. */
public object Profanity : AutoModerationRuleKeywordPresetType(1)

/** Words that refer to sexually explicit behavior or activity. */
public object SexualContent : AutoModerationRuleKeywordPresetType(2)

/** Personal insults or words that may be considered hate speech. */
public object Slurs : AutoModerationRuleKeywordPresetType(3)


internal object Serializer : KSerializer<AutoModerationRuleKeywordPresetType> {

override val descriptor =
PrimitiveSerialDescriptor("dev.kord.common.entity.AutoModerationRuleKeywordPresetType", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: AutoModerationRuleKeywordPresetType) =
encoder.encodeInt(value.value)

override fun deserialize(decoder: Decoder) = when (val value = decoder.decodeInt()) {
1 -> Profanity
2 -> SexualContent
3 -> Slurs
else -> Unknown(value)
}
}
}

/** Indicates in what event context a rule should be checked. */
@Serializable(with = AutoModerationRuleEventType.Serializer::class)
public sealed class AutoModerationRuleEventType(public val value: Int) {

final override fun equals(other: Any?): Boolean =
this === other || (other is AutoModerationRuleEventType && this.value == other.value)

final override fun hashCode(): Int = value


/** An unknown [AutoModerationRuleEventType]. */
public class Unknown(value: Int) : AutoModerationRuleEventType(value)

/** When a member sends or edits a message in the guild. */
public object MessageSend : AutoModerationRuleEventType(1)


internal object Serializer : KSerializer<AutoModerationRuleEventType> {

override val descriptor =
PrimitiveSerialDescriptor("dev.kord.common.entity.AutoModerationRuleEventType", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: AutoModerationRuleEventType) = encoder.encodeInt(value.value)

override fun deserialize(decoder: Decoder) = when (val value = decoder.decodeInt()) {
1 -> MessageSend
else -> Unknown(value)
}
}
}

@Serializable
public data class DiscordAutoModerationAction(
val type: AutoModerationActionType,
val metadata: Optional<DiscordAutoModerationActionMetadata> = Optional.Missing(),
)

/** The type of action. */
@Serializable(with = AutoModerationActionType.Serializer::class)
public sealed class AutoModerationActionType(public val value: Int) {

final override fun equals(other: Any?): Boolean =
this === other || (other is AutoModerationActionType && this.value == other.value)

final override fun hashCode(): Int = value


/** An unknown [AutoModerationActionType]. */
public class Unknown(value: Int) : AutoModerationActionType(value)

/** Blocks the content of a message according to the rule. */
public object BlockMessage : AutoModerationActionType(1)

/** Logs user content to a specified channel. */
public object SendAlertMessage : AutoModerationActionType(2)

/**
* Timeout user for a specified duration.
*
* A [Timeout] action can only be set up for [Keyword] and [MentionSpam] rules. The [ModerateMembers] permission is
* required to use the [Timeout] action type.
*/
public object Timeout : AutoModerationActionType(3)


internal object Serializer : KSerializer<AutoModerationActionType> {

override val descriptor =
PrimitiveSerialDescriptor("dev.kord.common.entity.AutoModerationActionType", PrimitiveKind.INT)

override fun serialize(encoder: Encoder, value: AutoModerationActionType) = encoder.encodeInt(value.value)

override fun deserialize(decoder: Decoder) = when (val value = decoder.decodeInt()) {
1 -> BlockMessage
2 -> SendAlertMessage
3 -> Timeout
else -> Unknown(value)
}
}
}

@Serializable
public data class DiscordAutoModerationActionMetadata(
@SerialName("channel_id")
public val channelId: OptionalSnowflake = OptionalSnowflake.Missing,
@SerialName("duration_seconds")
public val durationSeconds: Optional<DurationInSeconds> = Optional.Missing(),
)
13 changes: 12 additions & 1 deletion common/src/main/kotlin/entity/DiscordGuild.kt
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,22 @@ public data class DiscordPartialGuild(
@Serializable(with = GuildFeature.Serializer::class)
public sealed class GuildFeature(public val value: String) {

override fun toString(): String = "GuildFeature(value=$value)"
final override fun equals(other: Any?): Boolean =
this === other || (other is GuildFeature && this.value == other.value)

final override fun hashCode(): Int = value.hashCode()
final override fun toString(): String = "GuildFeature(value=$value)"


/** An unknown [GuildFeature]. */
public class Unknown(value: String) : GuildFeature(value)

/** Guild has access to set an animated guild banner image. */
public object AnimatedBanner : GuildFeature("ANIMATED_BANNER")

/** Guild has set up auto moderation rules. */
public object AutoModeration : GuildFeature("AUTO_MODERATION")

/** Guild has access to set an invite splash background */
public object InviteSplash : GuildFeature("INVITE_SPLASH")

Expand Down Expand Up @@ -258,12 +267,14 @@ public sealed class GuildFeature(public val value: String) {
/** Guild is able to set role icons */
public object RoleIcons : GuildFeature("ROLE_ICONS")


internal object Serializer : KSerializer<GuildFeature> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("feature", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): GuildFeature = when (val value = decoder.decodeString()) {
"ANIMATED_BANNER" -> AnimatedBanner
"AUTO_MODERATION" -> AutoModeration
"INVITE_SPLASH" -> InviteSplash
"VIP_REGIONS" -> VIPRegions
"VANITY_URL" -> VanityUrl
Expand Down
7 changes: 5 additions & 2 deletions common/src/main/kotlin/entity/DiscordMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -775,10 +775,10 @@ public data class AllRemovedMessageReactions(
@Serializable(with = MessageType.MessageTypeSerializer::class)
public sealed class MessageType(public val code: Int) {

override fun equals(other: Any?): Boolean =
final override fun equals(other: Any?): Boolean =
this === other || (other is MessageType && this.code == other.code)

override fun hashCode(): Int = code
final override fun hashCode(): Int = code


/** The default code for unknown values. */
Expand Down Expand Up @@ -828,6 +828,8 @@ public sealed class MessageType(public val code: Int) {
public object ThreadStarterMessage : MessageType(21)
public object GuildInviteReminder : MessageType(22)
public object ContextMenuCommand : MessageType(23)
public object AutoModerationAction : MessageType(24)


internal object MessageTypeSerializer : KSerializer<MessageType> {

Expand Down Expand Up @@ -870,6 +872,7 @@ public sealed class MessageType(public val code: Int) {
ThreadStarterMessage,
GuildInviteReminder,
ContextMenuCommand,
AutoModerationAction,
)
}
}
Expand Down
Loading

0 comments on commit a2bfc1f

Please sign in to comment.