Skip to content

Commit

Permalink
Start localization, make tricks a slash command
Browse files Browse the repository at this point in the history
Signed-off-by: shedaniel <daniel@shedaniel.me>
  • Loading branch information
shedaniel committed Feb 14, 2022
1 parent 9a93218 commit 0c83872
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 105 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ repositories {
}

dependencies {
implementation("me.shedaniel:linkie-core:1.0.92") {
implementation("me.shedaniel:linkie-core:1.0.94") {
exclude module: "korio"
}
discord_apiImplementation("com.github.Discord4J.Discord4J:discord4j-core:30ac8f4") {
discord_apiImplementation("com.github.Discord4J.Discord4J:discord4j-core:85de29f") {
force true
}
// compile("com.github.shadorc.discord4j:discord4j-core:217336e") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ import discord4j.core.event.domain.interaction.ChatInputInteractionEvent
import discord4j.discordjson.json.ApplicationCommandData
import discord4j.discordjson.json.ApplicationCommandOptionChoiceData
import discord4j.discordjson.json.ApplicationCommandRequest
import discord4j.discordjson.possible.Possible
import me.shedaniel.linkie.discord.handler.ThrowableHandler
import me.shedaniel.linkie.discord.utils.CommandContext
import me.shedaniel.linkie.discord.utils.SlashCommandBasedContext
import me.shedaniel.linkie.discord.utils.dismissButton
import me.shedaniel.linkie.discord.utils.event
import me.shedaniel.linkie.discord.utils.extensions.getOrNull
import me.shedaniel.linkie.discord.utils.replyComplex
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.stream.Collectors

data class SlashCommandHandler(
val responder: (event: ChatInputInteractionEvent) -> Unit,
Expand All @@ -52,32 +53,29 @@ class SlashCommands(
val applicationId: Long by lazy { client.restClient.applicationId.block() }
val handlers = mutableMapOf<String, SlashCommandHandler>()
val guildHandlers = mutableMapOf<GuildCommandKey, SlashCommandHandler>()
val globalCommands: Flux<ApplicationCommandData> by lazy {
val registeredCommands = mutableMapOf<String, ApplicationCommandData>()
val registeredGuildCommands = mutableMapOf<GuildCommandKey, ApplicationCommandData>()
val globalCommands: MutableList<ApplicationCommandData> by lazy {
client.restClient.applicationService
.getGlobalApplicationCommands(applicationId)
.cache()
.toStream()
.collect(Collectors.toList())
}
val guildCommands = mutableMapOf<Snowflake, Flux<ApplicationCommandData>>()
val guildCommands = mutableMapOf<Snowflake, MutableList<ApplicationCommandData>>()

data class GuildCommandKey(val guildId: Snowflake, val commandName: String)

fun getGuildCommands(id: Snowflake): Flux<ApplicationCommandData> {
fun getGuildCommands(id: Snowflake): MutableList<ApplicationCommandData> {
return guildCommands.getOrPut(id) {
client.restClient.applicationService
.getGuildApplicationCommands(applicationId, id.asLong())
.cache()
.toStream()
.collect(Collectors.toList())
}
}

init {
/*gateway.restClient.applicationService.getGuildApplicationCommands(applicationId, testingGuild).parallel().flatMap { data ->
gateway.restClient.applicationService.deleteGuildApplicationCommand(applicationId, testingGuild, data.id().toLong())
.doOnSuccess {
println(data.id())
}
}.then().block()*/
}

init {
client.event<ChatInputInteractionEvent> { event ->
val handler = when {
Expand Down Expand Up @@ -117,26 +115,37 @@ class SlashCommands(

fun guildCommand(guild: Snowflake, command: SlashCommand) {
for (cmd in command.cmds) {
var registered = false
getGuildCommands(guild)
.flatMap { data ->
if (data.name() == cmd) {
registered = true
val commandData = buildData(command, cmd, data.id(), data.applicationId())
if (data.toString() != commandData.toString()) {
println("not same $cmd guild ${guild.asString()}")
return@flatMap modifyGuildCommand(guild, command, data.id().toLong(), cmd)
}
guildHandlers[GuildCommandKey(guild, cmd)] = buildHandler(command, cmd)

val existingCommand = registeredGuildCommands[GuildCommandKey(guild, cmd)]

if (existingCommand != null) {
if (!equals(existingCommand, existingCommand.derive(command))) {
println("Updating guild (${guild.asString()}) command /$cmd")
modifyGuildCommand(guild, command, existingCommand.id().toLong(), cmd).subscribe { newData ->
registeredGuildCommands[GuildCommandKey(guild, cmd)] = newData
}
return@flatMap Mono.empty()
}.doOnComplete {
if (!registered) {
println("yes $cmd")
registered = true
createGuildCommand(guild, command, cmd).subscribe()
}
return
}

for (data in getGuildCommands(guild)) {
if (data.name() == cmd) {
val commandData = data.derive(command)
if (!equals(data, commandData)) {
println("Updating guild (${guild.asString()}) command /$cmd")
modifyGuildCommand(guild, command, data.id().toLong(), cmd).subscribe { newData ->
registeredGuildCommands[GuildCommandKey(guild, cmd)] = newData
}
}
}.subscribe()
guildHandlers[GuildCommandKey(guild, cmd)] = buildHandler(command, cmd)
return
}
}

println("Registering guild (${guild.asString()}) command /$cmd")
createGuildCommand(guild, command, cmd).subscribe { newData ->
registeredGuildCommands[GuildCommandKey(guild, cmd)] = newData
}
}
}

Expand Down Expand Up @@ -164,28 +173,49 @@ class SlashCommands(
.doOnError { errorHandler("Unable to create guild command: " + it.message) }
.onErrorResume { Mono.empty() }

private fun equals(data1: ApplicationCommandData, data2: ApplicationCommandData): Boolean {
return data1.name() == data2.name() &&
data1.description() == data2.description() &&
data1.applicationId() == data2.applicationId() &&
data1.type() == data2.type() &&
data1.options() == data2.options() &&
data1.defaultPermission() == data2.defaultPermission() &&
data1.id() == data2.id()
}

fun globalCommand(command: SlashCommand) {
for (cmd in command.cmds) {
var registered = false
globalCommands
.flatMap { data ->
if (data.name() == cmd) {
registered = true
val commandData = buildData(command, cmd, data.id(), data.applicationId())
if (data.toString() != commandData.toString()) {
println("not same $cmd global")
return@flatMap modifyGlobalCommand(command, data.id().toLong(), cmd)
}
handlers[cmd] = buildHandler(command, cmd)

val existingCommand = registeredCommands[cmd]

if (existingCommand != null) {
if (!equals(existingCommand, existingCommand.derive(command))) {
println("Updating global command /$cmd")
modifyGlobalCommand(command, existingCommand.id().toLong(), cmd).subscribe { newData ->
registeredCommands[cmd] = newData
}
return@flatMap Mono.empty()
}.doOnComplete {
if (!registered) {
println("yes $cmd")
registered = true
createGlobalCommand(command, cmd).subscribe()
}
return
}

for (data in globalCommands) {
if (data.name() == cmd) {
val commandData = data.derive(command)
if (!equals(data, commandData)) {
println("Updating global command /$cmd")
modifyGlobalCommand(command, data.id().toLong(), cmd).subscribe { newData ->
registeredCommands[cmd] = newData
}
}
}.subscribe()
handlers[cmd] = buildHandler(command, cmd)
return
}
}

println("Registering global command /$cmd")
createGlobalCommand(command, cmd).subscribe { newData ->
registeredCommands[cmd] = newData
}
}
}

Expand Down Expand Up @@ -242,9 +272,13 @@ class SlashCommands(
.addAllOptions(command.options.map(SlashCommandOption<*>::toData))
.build()

private fun buildData(command: SlashCommand, cmd: String, id: String, applicationId: String): ApplicationCommandData =
private fun ApplicationCommandData.derive(command: SlashCommand): ApplicationCommandData =
buildData(command, name(), id(), type(), applicationId())

private fun buildData(command: SlashCommand, cmd: String, id: String, type: Possible<Int>, applicationId: String): ApplicationCommandData =
ApplicationCommandData.builder()
.id(id)
.type(type)
.applicationId(applicationId)
.name(cmd.also { require(it.toLowerCase() == it && "^[\\w-_]+\$".toRegex().matchEntire(it) != null) { "$it is not a valid name" } })
.description(command.description(cmd))
Expand Down Expand Up @@ -304,6 +338,36 @@ class SlashCommands(
}
return sink.suggested
}

fun removeGlobalCommand(name: String) {
val cmd = globalCommands.firstOrNull { it.name() == name.toLowerCase() }
?: registeredCommands[name.toLowerCase()]
?: return
println("Deleting global command /${cmd.name()}")
client.restClient.applicationService
.deleteGlobalApplicationCommand(applicationId, cmd.id().toLong())
.doOnError { errorHandler("Unable to remove global command: " + it.message) }
.onErrorResume { Mono.empty() }
.subscribe()
globalCommands.removeIf { name.toLowerCase() == it.name() }
registeredCommands.remove(name.toLowerCase())
handlers.remove(name.toLowerCase())
}

fun removeGuildCommand(guildId: Snowflake, name: String) {
val cmd = guildCommands[guildId]?.firstOrNull { it.name() == name.toLowerCase() }
?: registeredGuildCommands[GuildCommandKey(guildId, name.toLowerCase())]
?: return
println("Deleting guild (${guildId.asString()}) command /${cmd.name()}")
client.restClient.applicationService
.deleteGuildApplicationCommand(applicationId, guildId.asLong(), cmd.id().toLong())
.doOnError { errorHandler("Unable to remove guild command: " + it.message) }
.onErrorResume { Mono.empty() }
.subscribe()
guildCommands[guildId]?.removeIf { name.toLowerCase() == it.name() }
registeredGuildCommands.remove(GuildCommandKey(guildId, name.toLowerCase()))
guildHandlers.remove(GuildCommandKey(guildId, name.toLowerCase()))
}
}

interface SlashCommandOptionSuggestionSink {
Expand Down Expand Up @@ -589,6 +653,7 @@ interface OptionsGetter : SuggestionOptionsGetter, WeakOptionsGetter {

fun of(slashCommand: SlashCommand, ctx: CommandContext, option: ApplicationCommandInteractionOption): OptionsGetter =
WeakOptionsGetter.of(slashCommand, option).asStrong(ctx)

fun of(slashCommand: SlashCommand, ctx: CommandContext, value: Any?): OptionsGetter =
WeakOptionsGetter.of(slashCommand, value).asStrong(ctx)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import reactor.core.publisher.Mono

interface CommandContext {
val prefix: String
val locale: String?
val message: MessageCreator
val interactionId: Snowflake
val messageId: Snowflake?
Expand Down Expand Up @@ -68,6 +69,8 @@ val CommandContext.inGuild: InGuildCommandContext
get() = this@inGuild.user
override val prefix: String
get() = this@inGuild.prefix
override val locale: String?
get() = this@inGuild.locale
override val message: MessageCreator
get() = this@inGuild.message
override val guild: Guild
Expand All @@ -94,6 +97,8 @@ class SlashCommandBasedContext(

override val prefix: String
get() = "/"
override val locale: String?
get() = event.interaction.data.locale().getOrNull()
override val interactionId: Snowflake
get() = event.interaction.id
override val messageId: Snowflake?
Expand All @@ -120,6 +125,8 @@ class MessageBasedCommandContext(
) : CommandContext {
override val message: MessageCreator by lazy { channel.msgCreator(this, event.message) }

override val locale: String?
get() = null
override val interactionId: Snowflake
get() = messageId
override val messageId: Snowflake
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/me/shedaniel/linkie/discord/LinkieBot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import me.shedaniel.linkie.discord.tricks.TricksManager
import me.shedaniel.linkie.discord.utils.CommandContext
import me.shedaniel.linkie.discord.utils.discriminatedName
import me.shedaniel.linkie.discord.utils.event
import me.shedaniel.linkie.discord.utils.extensions.getOrNull
import me.shedaniel.linkie.discord.utils.reply
import me.shedaniel.linkie.discord.utils.sendMessage
import me.shedaniel.linkie.discord.utils.setTimestampToNow
Expand Down Expand Up @@ -133,6 +134,7 @@ fun main() {
)
) {
val slashCommands = SlashCommands(this, LinkieThrowableHandler, ::warn)
TricksManager.listen(slashCommands)
val commandManager = object : CommandManager(if (isDebug) "@" else "!") {
override fun getPrefix(event: MessageCreateEvent): String {
return event.guildId.orElse(null)?.let { ConfigManager[it.asLong()].prefix } ?: super.getPrefix(event)
Expand Down Expand Up @@ -175,7 +177,7 @@ fun main() {
val dispatch: ThreadMembersUpdate = ThreadMembersUpdateEvent::class.java.getDeclaredField("dispatch").also {
it.isAccessible = true
}.get(event) as ThreadMembersUpdate
if (dispatch.addedMembers().any { it.userId().asLong() == this.selfId.asLong() }) {
if (dispatch.addedMembers().any { it.userId().getOrNull()?.asLong() == this.selfId.asLong() }) {
gateway.getChannelById(event.threadId).subscribe { channel ->
if (channel is ThreadChannel) {
channel.sendMessage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package me.shedaniel.linkie.discord.commands
import discord4j.core.`object`.entity.User
import me.shedaniel.linkie.discord.OptionlessCommand
import me.shedaniel.linkie.discord.gateway
import me.shedaniel.linkie.discord.lang.i18n
import me.shedaniel.linkie.discord.utils.CommandContext
import me.shedaniel.linkie.discord.utils.addField
import me.shedaniel.linkie.discord.utils.basicEmbed
Expand All @@ -29,20 +30,20 @@ import me.shedaniel.linkie.discord.utils.replyComplex
object AboutCommand : OptionlessCommand {
override suspend fun execute(ctx: CommandContext) {
ctx.message.replyComplex {
layout {
row {
linkButton("Library Source", "https://github.com/linkie/linkie-core/")
linkButton("Bot Source", "https://github.com/linkie/linkie-discord/")
linkButton("Bot Invite", "https://discord.com/api/oauth2/authorize?client_id=472081983925780490&permissions=339008&scope=bot%20applications.commands")
}
}
embed {
title("About Linkie")
title("text.about.title".i18n(ctx))
gateway.self.map(User::getAvatarUrl).block()?.also { url -> thumbnail(url) }
description = "A mappings bot created by <@430615025066049538>."
addField("License", "Apache 2.0")
description = "text.about.description".i18n(ctx)
addField("text.about.license".i18n(ctx), "Apache 2.0")
basicEmbed(ctx.user)
}
layout {
row {
linkButton("text.about.links.core".i18n(ctx), "https://github.com/linkie/linkie-core/")
linkButton("text.about.links.bot".i18n(ctx), "https://github.com/linkie/linkie-discord/")
linkButton("text.about.links.invite".i18n(ctx), "https://discord.com/api/oauth2/authorize?client_id=472081983925780490&permissions=339008&scope=bot%20applications.commands")
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import me.shedaniel.linkie.discord.Command
import me.shedaniel.linkie.discord.scommands.SlashCommandBuilderInterface
import me.shedaniel.linkie.discord.scommands.args
import me.shedaniel.linkie.discord.scommands.opt
import me.shedaniel.linkie.discord.scommands.string
import me.shedaniel.linkie.discord.scripting.LinkieScripting
import me.shedaniel.linkie.discord.tricks.ContentType
import me.shedaniel.linkie.discord.tricks.Trick
Expand All @@ -36,18 +37,17 @@ import java.util.*

object AddTrickCommand : Command {
override suspend fun SlashCommandBuilderInterface.buildCommand(slash: Boolean) {
val name = string("name", "The name of the trick")
val args = args()
executeCommandWithGetter { ctx, options ->
execute(ctx, options.opt(args))
execute(ctx, options.opt(name), options.opt(args))
}
}

fun execute(ctx: CommandContext, args: MutableList<String>) {
fun execute(ctx: CommandContext, name: String, args: MutableList<String>) {
ctx.validateInGuild {
args.validateUsage(prefix, 2..Int.MAX_VALUE, "$cmd <name> [--script] <trick>")
val name = args.first()
args.validateUsage(prefix, 1..Int.MAX_VALUE, "$cmd <name> [--script] <trick>")
LinkieScripting.validateTrickName(name)
args.removeAt(0)
var type = ContentType.TEXT
val flags = mutableListOf<Char>()
val iterator = args.iterator()
Expand Down Expand Up @@ -104,7 +104,7 @@ object AddTrickCommand : Command {
message.reply {
basicEmbed(user)
title("Added Trick")
description = "Successfully added trick: $name"
description = "Successfully added trick: $name\nSlash commands of this trick may take a few minutes / hours to update (Discord Caching)"
}
}
}
Expand Down
Loading

0 comments on commit 0c83872

Please sign in to comment.