Skip to content

Commit

Permalink
Merge branch 'develop' into version/1.20.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Erdragh committed Aug 30, 2024
2 parents 424bc8f + d1b6368 commit d5b974c
Show file tree
Hide file tree
Showing 17 changed files with 117 additions and 43 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

### IntelliJ IDEA ###
.idea
.kotlin

### Eclipse ###
.apt_generated
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 1.5.3
- Unlink users on leave
- Update Dependencies
- Show better error message if FAQ is too long
- Limit the autocomplete suggestion return value length to fix no options showing up
- Added the `/listfaq` command

# 1.5.2
Fix tps command using wrong numbers for calculation

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@ on the [Discord Developer Portal](https://discord.com/developers/applications) a
to have the three privileged gateway intents: `PRESENCE`, `SERVER MEMBERS` and `MESSAGE CONTENT`.

Copy the bot token and store it somewhere safe (like a Password Manager) and never show it to
anybody else. To make sure the token gets read by the bot, it has to be in either an [Environment Variable](https://en.wikipedia.org/wiki/Environment_variable)
`DISCORD_TOKEN` where the running Minecraft server can access it or in the config file under the key `token`.
anybody else.

The bot can read the token from multiple sources, the easiest to configure being the `token` field in the
main server config file (`astralbot-server.toml`). Alternatively, you can also provide it via an [Environment Variable](https://en.wikipedia.org/wiki/Environment_variable)
`DISCORD_TOKEN` where the running Minecraft server can access it.
You could for example modify a `start.sh` script on a Unix-like system to `export` it or start the shell script with it set directly:

`startmc.sh`:
Expand Down
10 changes: 5 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import net.fabricmc.loom.task.RemapJarTask
plugins {
// This is an Architectury repository, as such the relevant plugins are needed
id("architectury-plugin") version "3.4-SNAPSHOT"
id("dev.architectury.loom") version "1.5-SNAPSHOT" apply false
id("dev.architectury.loom") version "1.7-SNAPSHOT" apply false
// The shadow plugin is used by the fabric subproject to include dependencies
// I'm temporarily using a fork of the original plugin to resolve "Unsupported java classfile major version 65"
// see: https://github.com/johnrengelman/shadow/issues/911
id("io.github.goooler.shadow") version "8.1.7" apply false
id("io.github.goooler.shadow") version "8.1.8" apply false
// Since this mod/bot is written in Kotlin and expected to run on Minecraft and as such
// the JVM, the Kotlin plugin is needed
kotlin("jvm") version "2.0.0"
kotlin("jvm") version "2.0.10"
// For generating documentation based on comments in the code
id("org.jetbrains.dokka") version "1.9.10"
java
Expand Down Expand Up @@ -267,7 +267,7 @@ subprojects {
relocate("com.iwebpp.crypto", "dev.erdragh.shadowed.com.iwebpp.crypto")
relocate("com.neovisionaries.ws", "dev.erdragh.shadowed.com.neovisionaries.ws")
relocate("org.json", "dev.erdragh.shadowed.org.json")
relocate("net.bytebuddy", "dev.erdragh.net.bytebuddy")
relocate("net.bytebuddy", "dev.erdragh.shadowed.net.bytebuddy")

exclude("**/org/slf4j/**")

Expand Down Expand Up @@ -330,7 +330,7 @@ subprojects {
// file. This allows me to keep all previous changes in the file
// without having to worry about them being included on new file
// uploads.
File("CHANGELOG.md")
File(rootDir, "CHANGELOG.md")
.readText(StandardCharsets.UTF_8)
.replace(Regex("[^^](#(#|\\n|.)+)|(^#.+)"), "")
.trim()
Expand Down
16 changes: 11 additions & 5 deletions common/src/main/kotlin/dev/erdragh/astralbot/Bot.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.erdragh.astralbot

import dev.erdragh.astralbot.commands.discord.CommandHandlingListener
import dev.erdragh.astralbot.listeners.CommandHandlingListener
import dev.erdragh.astralbot.listeners.UserEventListener
import dev.erdragh.astralbot.config.AstralBotConfig
import dev.erdragh.astralbot.handlers.FAQHandler
import dev.erdragh.astralbot.handlers.MinecraftHandler
Expand All @@ -18,6 +19,7 @@ import java.io.File
import java.time.Duration
import java.time.LocalDateTime
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.io.path.absolute
import kotlin.properties.Delegates

const val MODID = "astralbot"
Expand Down Expand Up @@ -76,11 +78,11 @@ private fun setupFromJDA(api: JDA) {
LOGGER.info("Fetching required data from Discord")
updatePresence(0)
applicationId = api.retrieveApplicationInfo().submit().get().idLong
if (AstralBotConfig.DISCORD_GUILD.get() < 0) {
if (AstralBotConfig.DISCORD_GUILD.get() <= 0) {
LOGGER.warn("No text channel for chat synchronization configured. Chat sync will not be enabled.")
return
}
if (AstralBotConfig.DISCORD_CHANNEL.get() < 0) {
if (AstralBotConfig.DISCORD_CHANNEL.get() <= 0) {
LOGGER.warn("No text channel for chat synchronization configured. Chat sync will not be enabled.")
return
}
Expand Down Expand Up @@ -108,7 +110,7 @@ fun startAstralbot(server: MinecraftServer) {
startTimestamp = LocalDateTime.now()
val env = System.getenv()

baseDirectory = File(server.serverDirectory, MODID)
baseDirectory = File(server.serverDirectory.absoluteFile, MODID)
if (baseDirectory!!.mkdir()) {
LOGGER.debug("Created $MODID directory")
}
Expand All @@ -127,7 +129,11 @@ fun startAstralbot(server: MinecraftServer) {
GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.GUILD_MEMBERS
).addEventListeners(CommandHandlingListener, minecraftHandler).build()
).addEventListeners(
CommandHandlingListener,
UserEventListener,
minecraftHandler
).build()

setupJob = GlobalScope.async {
launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ val allCommands = arrayOf(
// Bot management commands
ReloadCommand,
// Utility Commands
FAQCommand,
FAQCommands,
ListFaqsCommand,
// Player related commands
LinkCommand,
UnlinkCommand,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package dev.erdragh.astralbot.commands.discord

import dev.erdragh.astralbot.config.AstralBotTextConfig
import dev.erdragh.astralbot.handlers.FAQHandler
import net.dv8tion.jda.api.entities.Message
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import net.dv8tion.jda.api.interactions.commands.OptionType
import net.dv8tion.jda.api.interactions.commands.build.Commands
import net.dv8tion.jda.api.interactions.commands.build.OptionData
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData
import kotlin.math.min

/**
* This command prints FAQ messages, fetching them from
Expand All @@ -16,11 +20,11 @@ import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData
*
* @author Erdragh
*/
object FAQCommand : HandledSlashCommand, AutocompleteCommand {
object FAQCommands : HandledSlashCommand, AutocompleteCommand {
private const val OPTION_ID = "id"

override val command: SlashCommandData = Commands.slash("faq", "prints a specified FAQ answer")
.addOption(OptionType.STRING, OPTION_ID, "id of the faq", true, true)
override val command: SlashCommandData = Commands.slash("faq", "Prints a specified FAQ answer")
.addOption(OptionType.STRING, OPTION_ID, "ID of the FAQ", true, true)

override fun handle(event: SlashCommandInteractionEvent) {
// since interacting with the disk may take a while, the reply gets deferred
Expand All @@ -29,12 +33,46 @@ object FAQCommand : HandledSlashCommand, AutocompleteCommand {
val faqId = event.getOption(OPTION_ID)!!.asString
val faqResponse = FAQHandler.getFAQForId(faqId)

event.hook.sendMessage(faqResponse).queue()
if (faqResponse.length >= Message.MAX_CONTENT_LENGTH) {
event.hook.sendMessage(AstralBotTextConfig.FAQ_TOO_LONG.get().replace("{{id}}", faqId)).queue()
} else {
event.hook.sendMessage(faqResponse).queue()
}

}

override fun autocomplete(event: CommandAutoCompleteInteractionEvent) {
if (event.focusedOption.name == OPTION_ID) {
event.replyChoiceStrings(FAQHandler.suggestFAQIds(event.focusedOption.value)).queue()
val faqs = FAQHandler.suggestFAQIds(event.focusedOption.value)
event.replyChoiceStrings(faqs.slice(0..<min(OptionData.MAX_CHOICES, faqs.size))).queue()
}
}
}

object ListFaqsCommand : HandledSlashCommand, AutocompleteCommand {
private const val OPTION_SLUG = "slug"
override val command: SlashCommandData = Commands.slash("listfaq", "Lists available FAQs")
.addOption(OptionType.STRING, OPTION_SLUG, "Optional search key, narrows the results to only those that include the given input", false, true)

override fun handle(event: SlashCommandInteractionEvent) {
event.deferReply(false).queue()

val faqId = event.getOption(OPTION_SLUG)?.asString

val faqs = FAQHandler.suggestFAQIds(faqId)

val msg = if (faqs.isEmpty()) AstralBotTextConfig.FAQ_NONE_AVAILABLE.get() else faqs.joinToString(
"\n- ",
"- "
) { "`$it`" }

event.hook.sendMessage(msg).queue()
}

override fun autocomplete(event: CommandAutoCompleteInteractionEvent) {
if (event.focusedOption.name == OPTION_SLUG) {
val faqs = FAQHandler.suggestFAQIds(event.focusedOption.value)
event.replyChoiceStrings(faqs.slice(0..<min(OptionData.MAX_CHOICES, faqs.size))).queue()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dev.erdragh.astralbot.*
import dev.erdragh.astralbot.config.AstralBotConfig
import dev.erdragh.astralbot.config.AstralBotTextConfig
import dev.erdragh.astralbot.handlers.WhitelistHandler
import dev.erdragh.astralbot.listeners.CommandHandlingListener
import kotlinx.coroutines.runBlocking
import net.dv8tion.jda.api.Permission
import net.dv8tion.jda.api.entities.Role
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import dev.erdragh.astralbot.LOGGER
import dev.erdragh.astralbot.commands.discord.allCommands
import net.minecraftforge.common.ForgeConfigSpec
import java.net.URI
import java.net.URL
import java.net.URLDecoder
import java.nio.charset.StandardCharsets

/**
* Config for the AstralBot mod. This uses Forge's config system
Expand Down Expand Up @@ -64,18 +61,18 @@ object AstralBotConfig {
/**
* The ID of the discord channel where the messages are synchronized
*/
val DISCORD_CHANNEL: ForgeConfigSpec.ConfigValue<Long>
val DISCORD_CHANNEL: ForgeConfigSpec.LongValue

/**
* The ID of the Discord Guild (server) where this bot will be active.
* This is used to get the chat sync channel etc.
*/
val DISCORD_GUILD: ForgeConfigSpec.ConfigValue<Long>
val DISCORD_GUILD: ForgeConfigSpec.LongValue

/**
* The ID of the Discord role given to linked members
*/
val DISCORD_ROLE: ForgeConfigSpec.ConfigValue<Long>
val DISCORD_ROLE: ForgeConfigSpec.LongValue

/**
* If this is set to true the message sent into the Minecraft chat
Expand Down Expand Up @@ -142,11 +139,11 @@ object AstralBotConfig {
DISCORD_LINK = builder.comment("Link to the discord where your users can run the /link command")
.define("discordLink", "")
DISCORD_CHANNEL = builder.comment("Channel ID where the chat messages are synced")
.define("discordChannel", (-1).toLong())
.defineInRange("discordChannel", 0L, 0L, Long.MAX_VALUE)
DISCORD_GUILD = builder.comment("Guild (server) ID where the chat messages etc. are synced")
.define("discordGuild", (-1).toLong())
.defineInRange("discordChannel", 0L, 0L, Long.MAX_VALUE)
DISCORD_ROLE = builder.comment("ID of the role given to linked users")
.define("discordRole", (-1).toLong())
.defineInRange("discordChannel", 0L, 0L, Long.MAX_VALUE)

CLICKABLE_MESSAGES =
builder.comment("Whether to make messages sent into the Minecraft chat open the Discord chat when clicked")
Expand Down Expand Up @@ -187,7 +184,8 @@ object AstralBotConfig {

ENABLED_COMMANDS = builder.comment("Enabled Slash Commands")
.defineList("enabledCommands",
allCommands.map { it.command.name }) {
allCommands.map { it.command.name },
) {
if (it !is String) {
LOGGER.warn("$it in enabledCommands is not a String")
return@defineList false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ object AstralBotTextConfig {
val GENERIC_SUCCESS: ForgeConfigSpec.ConfigValue<String>
val GENERIC_BLOCKED: ForgeConfigSpec.ConfigValue<String>

val FAQ_ERROR: ForgeConfigSpec.ConfigValue<String>
val FAQ_TOO_LONG: ForgeConfigSpec.ConfigValue<String>
val FAQ_NONE_AVAILABLE: ForgeConfigSpec.ConfigValue<String>
val FAQ_NO_REGISTERED: ForgeConfigSpec.ConfigValue<String>

val TICK_REPORT: ForgeConfigSpec.ConfigValue<String>
Expand Down Expand Up @@ -50,8 +51,10 @@ object AstralBotTextConfig {
GENERIC_BLOCKED = builder.comment("Generic string that replaces blocked URLs/Links")
.define("genericBlocked", "[BLOCKED]")

FAQ_ERROR = builder.comment("Message sent to Discord if an error ocurrs during FAQ loading")
.define(mutableListOf("faq", "error"), "Bot Error (Contact Bot Operator)")
FAQ_TOO_LONG = builder.comment("Message sent to Discord if an FAQ entry is too long. The id is accessible via {{id}}")
.define(mutableListOf("faq", "tooLong"), "FAQ for id `{{id}}` exceeds Discord's message length limit.")
FAQ_NONE_AVAILABLE = builder.comment("Message sent to Discord if there are no FAQs available")
.define(mutableListOf("faq", "noneAvailable"), "No FAQs available")
FAQ_NO_REGISTERED =
builder.comment(
"""Message sent to Discord when there is no FAQ for the given id.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ object FAQHandler {
fun getFAQForId(id: String): String {
if (!faqDirectory.exists() || !faqDirectory.isDirectory) {
LOGGER.error("FAQ directory not specified as directory: ${faqDirectory.absolutePath}")
return AstralBotTextConfig.FAQ_ERROR.get()
return AstralBotTextConfig.GENERIC_ERROR.get()
}
val faqFiles = faqDirectory.listFiles { file -> file.name == "$id.md" }
val faqFile = if (faqFiles?.isNotEmpty() == true) faqFiles[0] else null
Expand All @@ -81,8 +81,8 @@ object FAQHandler {
* Used in the autocomplete implementation of the faq command.
* @return a List of all available FAQ ids that start with the given [slug]
*/
fun suggestFAQIds(slug: String): List<String> {
return availableFAQIDs.filter { it.startsWith(slug, true) }
fun suggestFAQIds(slug: String?): List<String> {
return availableFAQIDs.filter { it.contains(slug ?: "", true) }
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dev.erdragh.astralbot.commands.discord
package dev.erdragh.astralbot.listeners

import dev.erdragh.astralbot.LOGGER
import dev.erdragh.astralbot.applicationId
import dev.erdragh.astralbot.commands.discord.AutocompleteCommand
import dev.erdragh.astralbot.commands.discord.getEnabledCommands
import dev.erdragh.astralbot.config.AstralBotTextConfig
import dev.erdragh.astralbot.guild
import dev.erdragh.astralbot.waitForSetup
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.erdragh.astralbot.listeners

import dev.erdragh.astralbot.handlers.WhitelistHandler
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter

object UserEventListener : ListenerAdapter() {
override fun onGuildMemberRemove(event: GuildMemberRemoveEvent) {
WhitelistHandler.unWhitelist(event.user)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import net.minecraft.network.chat.HoverEvent
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import org.commonmark.parser.Parser
Expand Down Expand Up @@ -82,6 +83,7 @@ fun formatHoverText(text: Component): MessageEmbed {
fun formatHoverItems(stack: ItemStack, knownItems: MutableList<ItemStack>, player: Player?): MessageEmbed? {
if (knownItems.contains(stack)) return null
knownItems.add(stack)
// TODO check if context needs fixing
val tooltip = stack.getTooltipLines(player, TooltipFlag.NORMAL).map(::formatComponentToMarkdown)
return EmbedBuilder()
.setTitle("${tooltip[0]} ${if (stack.count > 1) "(${stack.count})" else ""}")
Expand Down
1 change: 1 addition & 0 deletions fabric/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fabric.loom.multiProjectOptimisation=true
4 changes: 2 additions & 2 deletions forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import net.minecraftforge.fml.event.config.ModConfigEvent
@Mod("astralbot")
object BotMod {
init {
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, AstralBotConfig.SPEC)
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, AstralBotTextConfig.SPEC, "astralbot-text.toml")
ModLoadingContext.get().activeContainer.registerConfig(ModConfig.Type.SERVER, AstralBotConfig.SPEC)
ModLoadingContext.get().activeContainer.registerConfig(ModConfig.Type.SERVER, AstralBotTextConfig.SPEC, "astralbot-text.toml")
MOD_BUS.addListener(::onConfigReloaded)

FORGE_BUS.addListener(::onServerStart)
Expand Down
Loading

0 comments on commit d5b974c

Please sign in to comment.