Skip to content
This repository has been archived by the owner on Mar 7, 2024. It is now read-only.

Replace JDA with Discord4J #4

Draft
wants to merge 17 commits into
base: develop
Choose a base branch
from
22 changes: 11 additions & 11 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,24 @@ dependencies {

implementation(group = "ch.qos.logback", name = "logback-classic", version = "1.2.3")

implementation(group = "com.sparkjava", name = "spark-core", version = "2.9.2")
implementation(group = "com.sparkjava", name = "spark-core", version = "2.9.3")
implementation(group = "org.apache.velocity", name = "velocity-engine-core", version = "2.2")

implementation(group = "com.github.ben-manes.caffeine", name = "caffeine", version = "2.8.5")

implementation(group = "com.fasterxml.jackson.core", name = "jackson-databind", version = "2.10.1")

implementation(group = "net.sf.trove4j", name = "trove4j", version = "3.0.3")

implementation(group = "com.discord4j", name = "discord4j-core", version = "3.1.6")

// TODO: remove after switch to d4j
implementation(group = "net.dv8tion", name = "JDA", version = "4.2.1_264") {
exclude(module = "opus-java")
}

// TODO: remove after switch to d4j
// TODO: custom oauth client?
// implementation(group = "com.jagrosh", name = "jda-utilities-oauth2", version = "3.0.5")
implementation(group = "com.github.JDA-Applications", name = "JDA-Utilities", version = "804d58a") {
// This is fine
Expand All @@ -43,16 +53,6 @@ dependencies {
exclude(module = "jda-utilities-command")
exclude(module = "jda-utilities-menu")
}
implementation(group = "net.dv8tion", name = "JDA", version = "4.3.0_298") {
exclude(module = "opus-java")
}

// Yes, this is JDA
// We're running this PR https://github.com/DV8FromTheWorld/JDA/pull/1178
// but it is broken atm
/*implementation(group = "com.github.dv8fromtheworld", name = "JDA", version = "68f4c4b") {
exclude(module = "opus-java")
}*/
}

configure<JavaPluginConvention> {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/dunctebot/dashboard/Container.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package com.dunctebot.dashboard

import com.dunctebot.dashboard.websocket.WebsocketClient
import com.dunctebot.duncteapi.DuncteApi
import com.dunctebot.jda.JDARestClient
import com.dunctebot.discord.DiscordRestClient
import com.fasterxml.jackson.databind.json.JsonMapper
import okhttp3.OkHttpClient

val restJDA = JDARestClient(System.getenv("BOT_TOKEN"))
val discordClient = DiscordRestClient(System.getenv("BOT_TOKEN"))
val duncteApis = DuncteApi("Bot ${System.getenv("BOT_TOKEN")}")

val httpClient = OkHttpClient()
Expand Down
24 changes: 16 additions & 8 deletions src/main/kotlin/com/dunctebot/dashboard/WebHelpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import com.dunctebot.dashboard.rendering.WebVariables
import com.fasterxml.jackson.databind.JsonNode
import com.jagrosh.jdautilities.oauth2.OAuth2Client
import com.jagrosh.jdautilities.oauth2.session.Session
import net.dv8tion.jda.api.entities.Guild
import net.dv8tion.jda.internal.utils.IOUtil
import discord4j.discordjson.json.GuildUpdateData
import discord4j.rest.entity.RestGuild
import okhttp3.FormBody
import spark.*
import java.net.URLDecoder
Expand Down Expand Up @@ -43,11 +43,18 @@ val Request.userId: String
val Request.guildId: String?
get() = this.params(GUILD_ID)

fun Request.fetchGuild(): Guild? {
val guildId: String = this.guildId ?: return null
val Request.guild: RestGuild?
get() {
val guildId = this.guildId ?: return null

return discordClient.getGuild(guildId.toLong())
}

fun Request.fetchGuild(): GuildUpdateData? {
val guild: RestGuild = this.guild ?: return null

return try {
restJDA.retrieveGuildById(guildId).complete()
guild.data.block()
} catch (e: Exception) {
e.printStackTrace()
null
Expand Down Expand Up @@ -127,8 +134,9 @@ fun verifyCaptcha(response: String): JsonNode {
.post(body)
.build()
).execute().use {
val readFully = IOUtil.readFully(IOUtil.getBody(it))

return jsonMapper.readTree(readFully)
it.body().use { body ->
// reads the entire body into memory
return jsonMapper.readTree(body!!.bytes())
}
}
}
47 changes: 32 additions & 15 deletions src/main/kotlin/com/dunctebot/dashboard/WebServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import com.dunctebot.dashboard.controllers.api.DataController
import com.dunctebot.dashboard.controllers.api.GuildApiController
import com.dunctebot.dashboard.controllers.api.OtherAPi
import com.dunctebot.dashboard.controllers.errors.HttpErrorHandlers
import com.dunctebot.dashboard.rendering.VelocityRenderer
import com.dunctebot.dashboard.rendering.WebVariables
import com.dunctebot.dashboard.utils.fetchGuildPatronStatus
import com.dunctebot.dashboard.utils.getEffectivePermissions
import com.dunctebot.models.settings.GuildSetting
import com.dunctebot.models.settings.ProfanityFilterType
import com.dunctebot.models.settings.WarnAction
import com.dunctebot.models.utils.Utils
import com.fasterxml.jackson.databind.JsonNode
import com.jagrosh.jdautilities.oauth2.OAuth2Client
import net.dv8tion.jda.api.entities.TextChannel
import spark.ModelAndView
import discord4j.common.util.Snowflake
import discord4j.rest.util.Permission
import discord4j.rest.util.PermissionSet
import spark.Spark.*

// The socket server will be used to communicate with DuncteBot himself
Expand Down Expand Up @@ -202,11 +202,6 @@ class WebServer {
return@get OtherAPi.uptimeRobot()
}

// keep?
get("/commands.json") { _, _ ->
"TODO: setup websocket to bot"
}

post("/update-data") { request, _ ->
return@post DataController.updateData(request)
}
Expand Down Expand Up @@ -255,19 +250,41 @@ class WebServer {

private fun getWithGuildData(path: String, map: WebVariables, view: String) {
get(path) { request, _ ->
val guild = request.fetchGuild()
val guild = request.guild

if (guild != null) {
val guildId = guild.idLong
val guildId = guild.id.asLong()
val self = guild.selfMember.block()!!
val selfId = Snowflake.of(self.user().id())

val tcs = guild.channels
.filter {
it.getEffectivePermissions(guild, self).map { p ->
println("Permissions $p")
p.containsAll(PermissionSet.of(
Permission.SEND_MESSAGES, Permission.VIEW_CHANNEL /* read messages */
))
}.block()!!
}
.collectList()
.block()!!

println("channels $tcs")

val goodRoles = guild.roles
.filter { !it.managed() }
.filter { it.name() != "@everyone" && it.name() != "@here" }
// TODO: check if can interact
.collectList()
.block()!!

val tcs = guild.textChannelCache.filter(TextChannel::canTalk).toList()
val goodRoles = guild.roleCache.filter {
/*val goodRoles_old = guild.roleCache.filter {
guild.selfMember.canInteract(it) && it.name != "@everyone" && it.name != "@here"
}.filter { !it.isManaged }.toList()
}.filter { !it.isManaged }.toList()*/

map.put("goodChannels", tcs)
map.put("goodRoles", goodRoles)
map.put("guild", guild)
map.put("guild", discordClient.retrieveGuildData(guildId))

val settings = duncteApis.getGuildSetting(guildId)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import com.dunctebot.dashboard.*
import com.dunctebot.dashboard.WebServer.Companion.OLD_PAGE
import com.dunctebot.dashboard.WebServer.Companion.SESSION_ID
import com.dunctebot.dashboard.WebServer.Companion.USER_ID
import net.dv8tion.jda.api.Permission
import com.dunctebot.discord.extensions.hasPermission
import discord4j.rest.util.Permission
import spark.Request
import spark.Response
import spark.Spark
Expand All @@ -21,15 +22,16 @@ object DashboardController {
}

val guild = request.fetchGuild() ?: throw haltDiscordError(DiscordError.NO_GUILD, request.guildId!!)
val guildId = guild.id().asLong()

val member = try {
restJDA.retrieveMemberById(guild, request.userId).complete()
discordClient.retrieveMemberById(guildId, request.userId).block()!!
} catch (e: Exception) {
e.printStackTrace()
throw haltDiscordError(DiscordError.WAT)
}

if (!member.hasPermission(Permission.MANAGE_SERVER)) {
if (!member.hasPermission(guildId, Permission.MANAGE_GUILD)) {
throw haltDiscordError(DiscordError.NO_PERMS)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ import com.dunctebot.dashboard.*
import com.dunctebot.dashboard.rendering.DbModelAndView
import com.dunctebot.dashboard.rendering.WebVariables
import com.github.benmanes.caffeine.cache.Caffeine
import net.dv8tion.jda.api.entities.Member
import net.dv8tion.jda.api.entities.Role
import net.dv8tion.jda.api.exceptions.ErrorResponseException
import discord4j.discordjson.json.MemberData
import discord4j.discordjson.json.RoleData
import spark.Request
import spark.Response
import java.util.concurrent.TimeUnit
import kotlin.streams.toList

object GuildController {
private const val DEFAULT_ROLE_COLOUR = 0x1FFFFFFF

// some hash -> "$userId-$guildId"
// TODO: convert to expiring map
val securityKeys = mutableMapOf<String, String>()
val guildHashes = Caffeine.newBuilder()
.expireAfterWrite(2, TimeUnit.HOURS)
.build<String, Long>()
val guildRoleCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.build<Long, List<CustomRole>>()
.build<Long, Pair<String, List<CustomRole>>>()

fun handleOneGuildRegister(request: Request): Any {
val params = request.paramsMap
Expand Down Expand Up @@ -87,33 +88,35 @@ object GuildController {

fun showGuildRoles(request: Request, response: Response): Any {
val hash = request.params("hash")
val guildId = guildHashes.getIfPresent(hash) ?: return haltNotFound(request, response)
val guild = try {
// TODO: do we want to do this?
// Maybe only cache for a short time as it will get outdated data
restJDA.fakeJDA.getGuildById(guildId) ?: restJDA.retrieveGuildById(guildId.toString()).complete()
} catch (e: ErrorResponseException) {
e.printStackTrace()
return haltNotFound(request, response)
}
val guildId = guildHashes.getIfPresent(hash) ?: throw haltNotFound(request, response)
// val guildId = hash.toLong() // cheat :D

val roles = guildRoleCache.get(guild.idLong) {
val members = restJDA.retrieveAllMembers(guild).stream().toList()
val (guildName, roles) = guildRoleCache.get(guildId) {
val internalRoles = discordClient.retrieveGuildRoles(guildId)
val members = discordClient.retrieveGuildMembers(guildId).collectList().block()!!
val guild = discordClient.retrieveGuildData(guildId)

guild.roles.map { CustomRole(it, members) }
guild.name() to internalRoles.map { CustomRole(it, members) }
.sort { o1, o2 -> o2.position() - o1.position() }
.collectList().block()!!
}!!

return WebVariables()
.put("hide_menu", true)
.put("title", "Roles for ${guild.name}")
.put("guild_name", guild.name)
.put("title", "Roles for $guildName")
.put("guild_name", guildName)
.put("roles", roles)
.toModelAndView("guildRoles.vm")
}

class CustomRole(private val realRole: Role, allMembers: List<Member>) : Role by realRole {
// Accessed by our templating engine
@Suppress("unused")
val memberCount = allMembers.filter { it.roles.contains(realRole) }.size
// Accessed by our templating engine
@Suppress("unused")
class CustomRole(private val realRole: RoleData, allMembers: List<MemberData>) : RoleData by realRole {
val memberCount = allMembers.filter { it.roles().contains(realRole.id()) }.size

// overloads mimicking JDA names for easy access in the template
val idLong = realRole.id().asLong()
val name: String = realRole.name()
val colorRaw = if (realRole.color() == 0) DEFAULT_ROLE_COLOUR else realRole.color()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.jagrosh.jdautilities.oauth2.OAuth2Client
import com.jagrosh.jdautilities.oauth2.OAuth2Client.DISCORD_REST_VERSION
import com.jagrosh.jdautilities.oauth2.Scope
import com.jagrosh.jdautilities.oauth2.exceptions.InvalidStateException
import net.dv8tion.jda.api.exceptions.HttpException
import org.slf4j.LoggerFactory
import spark.Request
import spark.Response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package com.dunctebot.dashboard.controllers.api
import com.dunctebot.dashboard.*
import com.dunctebot.dashboard.controllers.GuildController
import com.dunctebot.dashboard.utils.HashUtils
import com.dunctebot.discord.extensions.asTag
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ObjectNode
import net.dv8tion.jda.api.entities.User
import discord4j.discordjson.json.UserData
import spark.Request
import spark.Response
import java.util.concurrent.CompletableFuture
Expand Down Expand Up @@ -45,8 +46,8 @@ object GuildApiController {
.put("code", response.status())
}

val user: User? = try {
restJDA.retrieveUserById(data["user_id"].asText()).complete()
val user: UserData? = try {
discordClient.getUser(data["user_id"].asText()).data.block()
} catch (e: Exception) {
e.printStackTrace()
null
Expand Down Expand Up @@ -101,12 +102,13 @@ object GuildApiController {
.put("id", guildId)
.put("name", guild["name"].asText())

val userId = user.id().asString()
val userJson = jsonMapper.createObjectNode()
.put("id", user.id)
.put("name", user.name)
.put("id", userId)
.put("name", user.username())
.put("formatted", user.asTag)

val theKey = "${user.idLong}-${guildId}"
val theKey = "$userId-$guildId"
val theHash = HashUtils.sha1(theKey + System.currentTimeMillis())

GuildController.securityKeys[theHash] = theKey
Expand Down
Loading