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
13 changes: 10 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ dependencies {
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,9 +53,6 @@ dependencies {
exclude(module = "jda-utilities-command")
exclude(module = "jda-utilities-menu")
}
implementation(group = "net.dv8tion", name = "JDA", version = "4.2.1_264") {
exclude(module = "opus-java")
}

// Yes, this is JDA
// We're running this PR https://github.com/DV8FromTheWorld/JDA/pull/1178
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
18 changes: 14 additions & 4 deletions src/main/kotlin/com/dunctebot/dashboard/WebHelpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.dunctebot.dashboard.WebServer.Companion.USER_ID
import com.fasterxml.jackson.databind.JsonNode
import com.jagrosh.jdautilities.oauth2.OAuth2Client
import com.jagrosh.jdautilities.oauth2.session.Session
import discord4j.discordjson.json.GuildUpdateData
import discord4j.rest.entity.RestGuild
import net.dv8tion.jda.api.entities.Guild
import net.dv8tion.jda.internal.utils.IOUtil
import okhttp3.FormBody
Expand Down Expand Up @@ -39,11 +41,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 @@ -80,8 +89,9 @@ fun String?.toSafeLong(): Long {
}
}

@Throws(HaltException::class)
fun haltNotFound(request: Request, response: Response) {
Spark.halt(404, CustomErrorPages.getFor(404, request, response) as String)
throw Spark.halt(404, CustomErrorPages.getFor(404, request, response) as String)
}

fun verifyCaptcha(response: String): JsonNode {
Expand Down
31 changes: 25 additions & 6 deletions src/main/kotlin/com/dunctebot/dashboard/WebServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ 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 discord4j.common.util.Snowflake
import discord4j.core.`object`.entity.channel.TextChannel
import discord4j.rest.util.Permission
import discord4j.rest.util.PermissionSet
import spark.ModelAndView
import spark.Spark.*

Expand Down Expand Up @@ -270,15 +273,31 @@ 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
.ofType(TextChannel::class.java)
.filter {
it.getEffectivePermissions(selfId).map { p ->
p.containsAll(PermissionSet.of(
Permission.SEND_MESSAGES, Permission.VIEW_CHANNEL /* read messages */
))
}.block()!!
}

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

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import com.dunctebot.dashboard.WebServer.Companion.SESSION_ID
import com.dunctebot.dashboard.WebServer.Companion.USER_ID
import com.dunctebot.dashboard.fetchGuild
import com.dunctebot.dashboard.guildId
import com.dunctebot.dashboard.restJDA
import com.dunctebot.dashboard.discordClient
import com.dunctebot.dashboard.userId
import net.dv8tion.jda.api.Permission
import discord4j.rest.util.Permission
import spark.Request
import spark.Response
import spark.Spark
Expand All @@ -27,13 +27,18 @@ object DashboardController {
"&scope=bot&permissions=1609952470\" target=\"_blank\">invite it</a>?")

val member = try {
restJDA.retrieveMemberById(guild, request.userId).complete()
discordClient.retrieveMemberById(guild.id().asLong(), request.userId).block()!!
} catch (e: Exception) {
e.printStackTrace()
throw Spark.halt(200, "<h1>Either discord did a fucky wucky or you are not in the server that you are trying to edit</h1>")
duncte123 marked this conversation as resolved.
Show resolved Hide resolved
}

if (!member.hasPermission(Permission.MANAGE_SERVER)) {
println("---------------------------------------")
println("Permissions")
println(member.permissions().get())
println("---------------------------------------")

if (!member.permissions().get().contains(Permission.MANAGE_GUILD.name)) {
Spark.halt(200, "<h1>You do not have permission to edit this server</h1>")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ 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 {
// some hash -> "$userId-$guildId"
Expand Down Expand Up @@ -88,32 +86,28 @@ 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 guild = discordClient.retrieveGuildData(guildId)

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

guild.roles.map { CustomRole(it, members) }
internalRoles.map { CustomRole(it, members) }.collectList().block()
}!!

val guildName = guild.name()

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 {
class CustomRole(private val realRole: RoleData, allMembers: List<MemberData>) : RoleData by realRole {
// Accessed by our templating engine
@Suppress("unused")
val memberCount = allMembers.filter { it.roles.contains(realRole) }.size
val memberCount = allMembers.filter { it.roles().contains(realRole.id()) }
}
}
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.retrieveD4JUserById(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
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.dunctebot.dashboard.websocket.handlers

import com.dunctebot.dashboard.restJDA
import com.dunctebot.dashboard.discordClient
import com.dunctebot.dashboard.websocket.handlers.base.SocketHandler
import com.fasterxml.jackson.databind.JsonNode

class DataUpdateHandler : SocketHandler() {
override fun handleInternally(data: JsonNode?) {
if (data!!.has("guilds")) {
data["guilds"]["invalidate"].forEach {
restJDA.invalidateGuild(it.asLong())
discordClient.invalidateGuild(it.asLong())
}
}
}
Expand Down
64 changes: 64 additions & 0 deletions src/main/kotlin/com/dunctebot/discord/DiscordRestClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.dunctebot.discord

import com.github.benmanes.caffeine.cache.Caffeine
import discord4j.common.util.Snowflake
import discord4j.core.DiscordClient
import discord4j.discordjson.json.*
import discord4j.rest.entity.RestGuild
import discord4j.rest.entity.RestUser
import net.dv8tion.jda.api.entities.Guild
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.concurrent.TimeUnit


/**
* Custom jda rest client that allows for rest-only usage of JDA
*
* This class has been inspired by GivawayBot and all credit goes to them https://github.com/jagrosh/GiveawayBot
*/
class DiscordRestClient(token: String) {
// create a guild cache that keeps the guilds in cache for 30 minutes
// When we stop accessing the guild it will be removed from the cache
// TODO: use expiring map instead
private val guildCache = Caffeine.newBuilder()
.expireAfterAccess(30, TimeUnit.MINUTES)
.build<String, Guild>()

private val client = DiscordClient.create(token)

// TODO: still needed?
fun invalidateGuild(guildId: Long) {
this.guildCache.invalidate(guildId.toString())
}

// Note: instantly starts the request
fun retrieveD4JUserById(id: String): RestUser {
return this.client.getUserById(Snowflake.of(id))
}

// TODO: cache this
fun retrieveD4JSelfUser(): Mono<UserData> {
return this.client.self
}

fun retrieveGuildData(guildId: Long): GuildUpdateData {
return this.getGuild(guildId).data.block()!!
}

fun getGuild(guildId: Long): RestGuild {
return this.client.getGuildById(Snowflake.of(guildId))
}

fun retrieveGuildRoles(guildId: Long): Flux<RoleData> {
return this.getGuild(guildId).roles
}

fun retrieveGuildMembers(guildId: Long): Flux<MemberData> {
return this.getGuild(guildId).members
}

fun retrieveMemberById(guildId: Long, memberId: String): Mono<MemberData> {
return this.getGuild(guildId).getMember(Snowflake.of(memberId))
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/com/dunctebot/discord/extensions/UserData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.dunctebot.discord.extensions

import discord4j.discordjson.json.UserData

val UserData.asTag: String
get() = "${this.username()}#${this.discriminator()}"
12 changes: 0 additions & 12 deletions src/main/kotlin/com/dunctebot/jda/FakeJDA.kt

This file was deleted.

31 changes: 0 additions & 31 deletions src/main/kotlin/com/dunctebot/jda/FakeSessionController.kt

This file was deleted.

Loading