Skip to content

Commit

Permalink
Bot works
Browse files Browse the repository at this point in the history
  • Loading branch information
Laxystem committed Dec 6, 2023
1 parent 4f0fec0 commit de0dcf1
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 13 deletions.
1 change: 0 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ val klogging: String by project

dependencies {
implementation(kotlin("stdlib"))
implementation(kotlin("reflect"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization")

implementation("com.kotlindiscord.kord.extensions:kord-extensions:$kordex")

implementation("io.github.oshai:kotlin-logging:$klogging")
Expand Down
15 changes: 8 additions & 7 deletions src/main/kotlin/quest/laxla/supertrouper/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ package quest.laxla.supertrouper

import com.kotlindiscord.kord.extensions.ExtensibleBot
import com.kotlindiscord.kord.extensions.utils.env
import dev.kord.common.entity.Snowflake
import com.kotlindiscord.kord.extensions.utils.envOrNull
import kotlinx.coroutines.runBlocking

private val token = env("token")
val officialServer = env("official_server")
private val token = env("TOKEN")
private val testingServer = envOrNull("TESTING_SERVER")

fun main() = runBlocking {
ExtensibleBot(token) {
extensions {
add(::MaintenanceExtension)
applicationCommands {
testingServer?.let { defaultGuild(it) }
}

applicationCommands {
defaultGuild(Snowflake(officialServer))
extensions {
add(::MaintenanceExtension)
add(::PrivateMassagingExtension)
}
}.start()
}
10 changes: 6 additions & 4 deletions src/main/kotlin/quest/laxla/supertrouper/MaintenanceExtension.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package quest.laxla.supertrouper

import com.kotlindiscord.kord.extensions.checks.isBotAdmin
import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand
import dev.kord.common.entity.Permission
import dev.kord.common.entity.Snowflake
import com.kotlindiscord.kord.extensions.extensions.slashCommandCheck

class MaintenanceExtension : TrouperExtension() {
override suspend fun setup() {
slashCommandCheck {
isBotAdmin()
}

publicSlashCommand {
name = "stop"
description = "WARNING: Stops the bot completely."
guildId = Snowflake(officialServer)
requirePermission(Permission.Administrator)

action {
//language=Markdown
Expand Down
53 changes: 53 additions & 0 deletions src/main/kotlin/quest/laxla/supertrouper/Overwrites.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package quest.laxla.supertrouper

import dev.kord.common.entity.Overwrite
import dev.kord.common.entity.OverwriteType
import dev.kord.common.entity.Permissions
import dev.kord.common.entity.Snowflake
import dev.kord.core.entity.PermissionOverwriteEntity
import dev.kord.rest.builder.channel.PermissionOverwritesBuilder

fun overwrite(
target: Snowflake,
type: OverwriteType,
allowed: Permissions = Permissions(),
denied: Permissions = Permissions()
) = Overwrite(target, type, allowed, denied)

fun PermissionOverwritesBuilder.addOverwrite(
target: Snowflake,
type: OverwriteType,
allowed: Permissions = Permissions(),
denied: Permissions = Permissions()
) = addOverwrite(overwrite(target, type, allowed, denied))

fun PermissionOverwritesBuilder.sync(
vararg overrides: Overwrite,
defaults: Iterable<PermissionOverwriteEntity>
) = sync(overrides.asIterable(), defaults)

fun PermissionOverwritesBuilder.sync(
overrides: Iterable<Overwrite>,
defaults: Iterable<PermissionOverwriteEntity>
) {
val permissions = mutableMapOf<Overwrite, PermissionOverwriteEntity>()

defaults.forEach { default ->
val override = overrides.find { it.id == default.target && it.type == default.type }

if (override == null) addOverwrite(default.target, default.type, default.allowed, default.denied)
else permissions[override] = default
}

overrides.forEach { override ->
val default = permissions[override]

if (default == null) addOverwrite(override)
else addOverwrite(
default.target,
default.type,
default.allowed - default.denied - override.deny + override.allow,
default.denied - default.allowed - override.allow + override.deny
)
}
}
154 changes: 154 additions & 0 deletions src/main/kotlin/quest/laxla/supertrouper/PrivateMassagingExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package quest.laxla.supertrouper

import com.kotlindiscord.kord.extensions.checks.anyGuild
import com.kotlindiscord.kord.extensions.checks.isNotBot
import com.kotlindiscord.kord.extensions.extensions.*
import com.kotlindiscord.kord.extensions.utils.any
import com.kotlindiscord.kord.extensions.utils.envOrNull
import dev.kord.common.entity.OverwriteType
import dev.kord.common.entity.Permission
import dev.kord.core.behavior.GuildBehavior
import dev.kord.core.behavior.channel.createTextChannel
import dev.kord.core.behavior.channel.edit
import dev.kord.core.behavior.createCategory
import dev.kord.core.entity.User
import dev.kord.core.entity.channel.Category
import dev.kord.core.entity.channel.TextChannel
import dev.kord.core.event.guild.MemberJoinEvent
import dev.kord.gateway.Intent
import dev.kord.gateway.PrivilegedIntent
import dev.kord.rest.builder.channel.addMemberOverwrite
import dev.kord.rest.builder.channel.addRoleOverwrite
import kotlinx.coroutines.flow.*

private const val PrivateMessagesCategoryName = "Private Messages"
private val memberLimit = envOrNull("AUTOMATIC_CHANNEL_CREATION_MEMBER_LIMIT")?.toInt() ?: 30
private val privateMessageOwnerPermissions = Permission.ViewChannel + Permission.ReadMessageHistory
private val privateMessageBotPermissions =
privateMessageOwnerPermissions + Permission.ManageChannels + Permission.SendMessages + Permission.ManageMessages

class PrivateMassagingExtension : TrouperExtension() {
@OptIn(PrivilegedIntent::class)
override suspend fun setup() {
intents += Intent.GuildMembers

slashCommandCheck {
anyGuild()
isNotBot()
}

userCommandCheck {
anyGuild()
isNotBot()
}

event<MemberJoinEvent> {
action {
if (event.guild.members.count() < memberLimit) getOrCreateChannel(event.guild, event.member)
}
}

ephemeralSlashCommand(::TargetedArguments) {
name = "pm"
description = "Get a link to a user's private messages channel"

action {
respond {
content = getOrCreateChannelMention(guild!!, target.asUser())
}
}
}

ephemeralUserCommand {
name = "Private Message"

action {
respond {
content = getOrCreateChannelMention(guild!!, targetUsers.single())
}
}
}

ephemeralSlashCommand(::TargetedArguments) slash@{
name = "sync"
description = "Syncs a private message channel's permissions with the category."

requirePermission(Permission.ManageRoles)

action {
val category = getOrCreateCategory(guild!!)
val targetUser = target.asUser()
val targetChannel = getChannel(category, targetUser)
if (targetChannel == null) {
respond { content = "${target.mention} does not have a private message channel in this server." }
return@action
}

targetChannel.edit {
sync(
overwrite(this@slash.kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions),
overwrite(targetUser.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions),
defaults = category.permissionOverwrites
)
}

respond {
content = "Synced ${targetChannel.mention} for ${target.mention} successfully."
}
}
}
}

private suspend fun getOrCreateChannelMention(guild: GuildBehavior, user: User): String =
user.mention + ": " + (getOrCreateChannel(guild, user)?.mention ?: "Ineligible")

private suspend fun getOrCreateChannel(guild: GuildBehavior, user: User) =
getOrCreateChannel(getOrCreateCategory(guild), user)

private suspend fun getOrCreateCategory(guild: GuildBehavior) = getCategory(guild) ?: createCategory(guild)

private suspend fun getCategory(guild: GuildBehavior) = guild.channels.filterIsInstance<Category>().filter {
it.name.equals(PrivateMessagesCategoryName, ignoreCase = true)
}.singleOrNull()

private suspend fun createCategory(guild: GuildBehavior) = guild.createCategory(PrivateMessagesCategoryName) {
reason = "Private messaging category was missing."
nsfw = false

addMemberOverwrite(kord.selfId) {
allowed += privateMessageBotPermissions
}

addRoleOverwrite(guild.id) {
denied += Permission.ViewChannel
}
}

private suspend fun getOrCreateChannel(category: Category, user: User) =
if (user.isBot) null else getChannel(category, user) ?: createChannel(category, user)

private suspend fun getChannel(category: Category, user: User) =
category.channels.filterIsInstance<TextChannel>().firstOrNull { channel ->
channel.categoryId == category.id && (channel.topic?.contains(user.mention) == true || channel.pinnedMessages.any {
it.author?.id == kord.selfId && it.mentionedUserIds.singleOrNull() == user.id
})
}

private suspend fun createChannel(category: Category, user: User): TextChannel {
val mention = user.mention

val channel = category.createTextChannel(user.username) {
reason = "Created a PM with $mention."
nsfw = category.data.nsfw.discordBoolean
topic = mention

sync(
overwrite(kord.selfId, OverwriteType.Member, allowed = privateMessageBotPermissions),
overwrite(user.id, OverwriteType.Member, allowed = privateMessageOwnerPermissions),
defaults = category.permissionOverwrites
)
}

return channel
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/quest/laxla/supertrouper/TargetedArguments.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package quest.laxla.supertrouper

import com.kotlindiscord.kord.extensions.commands.Arguments
import com.kotlindiscord.kord.extensions.commands.application.slash.SlashCommandContext
import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalUser
import com.kotlindiscord.kord.extensions.components.forms.ModalForm

private const val TargetArgumentName = "target"
private const val TargetArgumentDescription = "Target of this command. Defaults to you."

open class TargetedArguments : Arguments() {
val targetOrNull by optionalUser {
name = TargetArgumentName
description = TargetArgumentDescription
}
}

val <C, A, M> C.target where C : SlashCommandContext<*, A, M>, A : TargetedArguments, M : ModalForm
get() = arguments.targetOrNull ?: user
4 changes: 3 additions & 1 deletion src/main/kotlin/quest/laxla/supertrouper/TrouperExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package quest.laxla.supertrouper

import com.kotlindiscord.kord.extensions.extensions.Extension

private const val NameRegexGroup = "name"

abstract class TrouperExtension : Extension() {
final override val name: String = this::class.simpleName!!.substringBeforeLast("Extension")
.replace("([A-Z])".toRegex()) { '-' + it.groups.single()!!.value.lowercase() }.removePrefix("-")
.replace("(?<$NameRegexGroup>[A-Z])".toRegex()) { '-' + it.groups[NameRegexGroup]!!.value.lowercase() }.removePrefix("-")
}

0 comments on commit de0dcf1

Please sign in to comment.