Add minimal implementation of timeout feature
This commit is contained in:
parent
74c2f643f9
commit
93a6e92191
|
@ -1,7 +1,7 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.3.40"
|
||||
kotlin("jvm") version "1.3.41"
|
||||
id("com.github.johnrengelman.shadow") version "5.1.0" apply true
|
||||
application
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ application {
|
|||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes(
|
||||
mapOf(
|
||||
"Main-Class" to botMainClass
|
||||
)
|
||||
mapOf(
|
||||
"Main-Class" to botMainClass
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ dependencies {
|
|||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation("org.javacord:javacord:3.0.4")
|
||||
implementation("org.mapdb:mapdb:3.0.7")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-RC")
|
||||
|
||||
testImplementation("io.kotlintest:kotlintest-runner-junit5:3.3.2")
|
||||
testImplementation("io.mockk:mockk:1.9.3")
|
||||
|
|
|
@ -4,6 +4,7 @@ import moe.kageru.kagebot.Util.checked
|
|||
import moe.kageru.kagebot.config.Config
|
||||
import moe.kageru.kagebot.config.ConfigParser
|
||||
import moe.kageru.kagebot.config.RawConfig
|
||||
import moe.kageru.kagebot.cron.CronD
|
||||
import org.javacord.api.DiscordApiBuilder
|
||||
import org.javacord.api.event.message.MessageCreateEvent
|
||||
import java.io.File
|
||||
|
@ -48,5 +49,6 @@ object Kagebot {
|
|||
Log.info("kagebot Mk II running")
|
||||
Globals.api.addMessageCreateListener { checked { it.process() } }
|
||||
Config.features.eventFeatures().forEach { it.register(Globals.api) }
|
||||
CronD.startAll()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ object Util {
|
|||
* Mimics the behavior of [Optional.ifPresent], but returns null if the optional is empty,
|
||||
* allowing easier fallback behavior via Kotlin’s ?: operator.
|
||||
*/
|
||||
private inline fun <T, R> Optional<T>.ifNotEmpty(op: (T) -> R): R? {
|
||||
internal inline fun <T, R> Optional<T>.ifNotEmpty(op: (T) -> R): R? {
|
||||
if (this.isPresent) {
|
||||
return op(this.get())
|
||||
}
|
||||
|
@ -53,6 +53,22 @@ object Util {
|
|||
}
|
||||
}
|
||||
|
||||
private fun <T> Optional<T>.toNullable(): T? {
|
||||
return ifNotEmpty { it }
|
||||
}
|
||||
|
||||
fun findUser(idOrName: String): User? {
|
||||
return when {
|
||||
idOrName.isEntityId() -> server.getMemberById(idOrName).toNullable()
|
||||
else -> {
|
||||
when {
|
||||
idOrName.contains('#') -> server.getMemberByDiscriminatedNameIgnoreCase(idOrName).toNullable()
|
||||
else -> server.getMembersByName(idOrName).firstOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> CompletableFuture<T>.failed(): Boolean {
|
||||
try {
|
||||
join()
|
||||
|
@ -94,6 +110,7 @@ object Util {
|
|||
op()
|
||||
} catch (e: Exception) {
|
||||
Log.warn("An uncaught exception occurred.\n$e")
|
||||
Log.warn(e.stackTrace.joinToString("\n"))
|
||||
MessageUtil.sendEmbed(
|
||||
Globals.api.owner.get(),
|
||||
EmbedBuilder()
|
||||
|
@ -102,7 +119,7 @@ object Util {
|
|||
.addField(
|
||||
"$e", """```
|
||||
${e.stackTrace.joinToString("\n")}
|
||||
```""".trimIndent()
|
||||
```""".trimIndent().run { applyIf(length > 1800) { substring(1..1800) } }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ object ConfigParser {
|
|||
|
||||
fun reloadFeatures(rawConfig: RawConfig) {
|
||||
Config.features = rawConfig.features?.let(::Features)
|
||||
?: Features(RawFeatures(null))
|
||||
?: Features(RawFeatures(null, null))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
package moe.kageru.kagebot.config
|
||||
|
||||
class RawFeatures(val welcome: RawWelcomeFeature?)
|
||||
class RawFeatures(val welcome: RawWelcomeFeature?, val timeout: RawTimeoutFeature?)
|
||||
class RawWelcomeFeature(val content: List<String>?, val fallbackChannel: String?, val fallbackMessage: String?)
|
||||
class RawTimeoutFeature(val role: String?)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package moe.kageru.kagebot.cron
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.kageru.kagebot.config.Config
|
||||
|
||||
object CronD {
|
||||
fun startAll() {
|
||||
GlobalScope.launch {
|
||||
minutely()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun minutely() {
|
||||
while (true) {
|
||||
Config.features.timeout?.checkAndRelease()
|
||||
delay(60_000)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,23 +7,26 @@ class Features(
|
|||
debug: DebugFeature,
|
||||
help: HelpFeature,
|
||||
getConfig: GetConfigFeature,
|
||||
setConfig: SetConfigFeature
|
||||
setConfig: SetConfigFeature,
|
||||
val timeout: TimeoutFeature?
|
||||
) {
|
||||
constructor(rawFeatures: RawFeatures) : this(
|
||||
rawFeatures.welcome?.let(::WelcomeFeature),
|
||||
DebugFeature(),
|
||||
HelpFeature(),
|
||||
GetConfigFeature(),
|
||||
SetConfigFeature()
|
||||
SetConfigFeature(),
|
||||
rawFeatures.timeout?.let(::TimeoutFeature)
|
||||
)
|
||||
|
||||
private val all = listOf(welcome, debug, help, getConfig, setConfig)
|
||||
private val all = listOf(welcome, debug, help, getConfig, setConfig, timeout)
|
||||
private val featureMap = mapOf(
|
||||
"help" to help,
|
||||
"debug" to debug,
|
||||
"welcome" to welcome,
|
||||
"getConfig" to getConfig,
|
||||
"setConfig" to setConfig
|
||||
"setConfig" to setConfig,
|
||||
"timeout" to timeout
|
||||
)
|
||||
|
||||
fun findByString(feature: String) = featureMap[feature]
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package moe.kageru.kagebot.features
|
||||
|
||||
import moe.kageru.kagebot.Log
|
||||
import moe.kageru.kagebot.Util.findRole
|
||||
import moe.kageru.kagebot.Util.findUser
|
||||
import moe.kageru.kagebot.Util.ifNotEmpty
|
||||
import moe.kageru.kagebot.config.Config
|
||||
import moe.kageru.kagebot.config.RawTimeoutFeature
|
||||
import moe.kageru.kagebot.persistence.Dao
|
||||
import org.javacord.api.entity.permission.Role
|
||||
import org.javacord.api.event.message.MessageCreateEvent
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class TimeoutFeature(raw: RawTimeoutFeature) : MessageFeature {
|
||||
private val timeoutRole: Role = raw.role?.let(::findRole)
|
||||
?: throw IllegalArgumentException("No timeout role defined")
|
||||
|
||||
override fun handle(message: MessageCreateEvent) {
|
||||
val (_, target, time) = message.readableMessageContent.split(' ', limit = 3)
|
||||
findUser(target)?.let { user ->
|
||||
val oldRoles = user.getRoles(Config.server).map { role ->
|
||||
user.removeRole(role)
|
||||
role.id
|
||||
}
|
||||
user.addRole(timeoutRole)
|
||||
val releaseTime = Instant.now().plus(Duration.ofMinutes(time.toLong())).epochSecond
|
||||
Dao.saveTimeout(releaseTime, listOf(user.id) + oldRoles)
|
||||
} ?: message.channel.sendMessage("Could not find user $target. Consider using the user ID.")
|
||||
}
|
||||
|
||||
fun checkAndRelease() {
|
||||
val now = Instant.now().epochSecond
|
||||
Dao.getAllTimeouts()
|
||||
.filter { releaseTime -> now > releaseTime }
|
||||
.map {
|
||||
Dao.deleteTimeout(it).let { rawIds ->
|
||||
UserInTimeout.ofLongs(rawIds).toPair()
|
||||
}
|
||||
}.forEach { (userId, roleIds) ->
|
||||
Config.server.getMemberById(userId).ifNotEmpty { user ->
|
||||
roleIds.forEach { roleId ->
|
||||
user.addRole(findRole("$roleId"))
|
||||
}
|
||||
user.removeRole(timeoutRole)
|
||||
} ?: Log.warn("Tried to free user $userId, but couldn’t find them on the server anymore")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UserInTimeout(private val id: Long, private val roles: List<Long>) {
|
||||
fun toPair() = Pair(id, roles)
|
||||
|
||||
companion object {
|
||||
fun ofLongs(longs: LongArray): UserInTimeout = longs.run {
|
||||
val userId = first()
|
||||
val roles = if (size > 1) slice(1 until size) else emptyList()
|
||||
return UserInTimeout(userId, roles)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,16 +36,16 @@ class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeatu
|
|||
}
|
||||
}
|
||||
|
||||
fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
|
||||
private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
|
||||
|
||||
val embed: EmbedBuilder? by lazy {
|
||||
rawWelcome.content?.let(MessageUtil::listToEmbed)
|
||||
}
|
||||
val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let {
|
||||
private val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let {
|
||||
if (rawWelcome.fallbackMessage == null) {
|
||||
throw IllegalArgumentException("[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined")
|
||||
}
|
||||
Util.findChannel(it)
|
||||
}
|
||||
val fallbackMessage: String? = rawWelcome.fallbackMessage
|
||||
private val fallbackMessage: String? = rawWelcome.fallbackMessage
|
||||
}
|
||||
|
|
|
@ -5,13 +5,17 @@ import org.mapdb.Serializer
|
|||
|
||||
object Dao {
|
||||
private val db = DBMaker.fileDB("kagebot.db").fileMmapEnable().closeOnJvmShutdown().make()
|
||||
private val strings = db.hashMap("main", Serializer.STRING, Serializer.STRING).createOrOpen()
|
||||
private val prisoners = db.hashMap("timeout", Serializer.LONG, Serializer.LONG_ARRAY).createOrOpen()
|
||||
|
||||
fun store(key: String, value: String) {
|
||||
strings[key] = value
|
||||
fun saveTimeout(releaseTime: Long, roles: List<Long>) {
|
||||
prisoners[releaseTime] = roles.toLongArray()
|
||||
}
|
||||
|
||||
fun get(key: String): String? {
|
||||
return strings[key]
|
||||
fun getAllTimeouts() = prisoners.keys
|
||||
|
||||
fun deleteTimeout(releaseTime: Long): LongArray {
|
||||
val timeout = prisoners[releaseTime]!!
|
||||
prisoners.remove(releaseTime)
|
||||
return timeout
|
||||
}
|
||||
}
|
|
@ -25,6 +25,9 @@ content = [
|
|||
"5th", "asdasd"
|
||||
]
|
||||
|
||||
[feature.timeout]
|
||||
role = "timeout"
|
||||
|
||||
[[command]]
|
||||
trigger = "!ping"
|
||||
response = "pong"
|
||||
|
@ -114,3 +117,7 @@ feature = "getConfig"
|
|||
[[command]]
|
||||
trigger = "!setConfig"
|
||||
feature = "setConfig"
|
||||
|
||||
[[command]]
|
||||
trigger = "!prison"
|
||||
feature = "timeout"
|
Loading…
Reference in New Issue
Block a user