diff --git a/.gitignore b/.gitignore index 045cafd..e04844e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,4 @@ npm-debug.log .tmpaccesswidener/ logs/ -listeners/ \ No newline at end of file +/listeners/ \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/LinkieBot.kt b/src/main/kotlin/me/shedaniel/linkie/discord/LinkieBot.kt index efc97aa..ed9c887 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/LinkieBot.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/LinkieBot.kt @@ -31,9 +31,40 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import me.shedaniel.linkie.LinkieConfig import me.shedaniel.linkie.Namespaces -import me.shedaniel.linkie.discord.commands.* +import me.shedaniel.linkie.discord.commands.AWCommand +import me.shedaniel.linkie.discord.commands.AboutCommand +import me.shedaniel.linkie.discord.commands.AddTrickCommand +import me.shedaniel.linkie.discord.commands.EvaluateCommand +import me.shedaniel.linkie.discord.commands.FTBDramaCommand +import me.shedaniel.linkie.discord.commands.FabricCommand +import me.shedaniel.linkie.discord.commands.FabricDramaCommand +import me.shedaniel.linkie.discord.commands.ForgeCommand +import me.shedaniel.linkie.discord.commands.GetValueCommand +import me.shedaniel.linkie.discord.commands.GoogleCommand +import me.shedaniel.linkie.discord.commands.HelpCommand +import me.shedaniel.linkie.discord.commands.ListAllTricksCommand +import me.shedaniel.linkie.discord.commands.ListTricksCommand +import me.shedaniel.linkie.discord.commands.ListenerCommand +import me.shedaniel.linkie.discord.commands.NamespacesCommand +import me.shedaniel.linkie.discord.commands.QueryClassCommand +import me.shedaniel.linkie.discord.commands.QueryCompoundCommand +import me.shedaniel.linkie.discord.commands.QueryFieldCommand +import me.shedaniel.linkie.discord.commands.QueryMethodCommand +import me.shedaniel.linkie.discord.commands.QueryTranslateClassCommand +import me.shedaniel.linkie.discord.commands.QueryTranslateFieldCommand +import me.shedaniel.linkie.discord.commands.QueryTranslateMethodCommand +import me.shedaniel.linkie.discord.commands.RandomClassCommand +import me.shedaniel.linkie.discord.commands.RemoveTrickCommand +import me.shedaniel.linkie.discord.commands.RunTrickCommand +import me.shedaniel.linkie.discord.commands.SetValueCommand +import me.shedaniel.linkie.discord.commands.TrickInfoCommand +import me.shedaniel.linkie.discord.commands.TricksCommand +import me.shedaniel.linkie.discord.commands.ValueCommand +import me.shedaniel.linkie.discord.commands.ValueListCommand import me.shedaniel.linkie.discord.config.ConfigManager import me.shedaniel.linkie.discord.listener.ChannelListeners +import me.shedaniel.linkie.discord.listener.listeners.ArchitecturyVersionListener +import me.shedaniel.linkie.discord.listener.listeners.FabricMCVersionListener import me.shedaniel.linkie.discord.listener.listeners.MinecraftVersionListener import me.shedaniel.linkie.discord.tricks.TricksManager import me.shedaniel.linkie.discord.utils.event @@ -44,7 +75,6 @@ import me.shedaniel.linkie.namespaces.MojangSrgNamespace import me.shedaniel.linkie.namespaces.PlasmaNamespace import me.shedaniel.linkie.namespaces.YarnNamespace import me.shedaniel.linkie.namespaces.YarrnNamespace -import me.shedaniel.linkie.utils.similarity import java.io.File import java.util.* @@ -182,8 +212,11 @@ fun registerCommands(commands: CommandHandler) { commands.registerCommand(FabricCommand, "fabric") commands.registerCommand(ForgeCommand, "forge") commands.registerCommand(GoogleCommand, "google") + commands.registerCommand(ListenerCommand, "listener") } fun registerListeners(listeners: ChannelListeners) { listeners["minecraft"] = MinecraftVersionListener + listeners["fabricmc"] = FabricMCVersionListener + listeners["architectury"] = ArchitecturyVersionListener } diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/AboutCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/AboutCommand.kt index 1d851a0..4d8699b 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/AboutCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/AboutCommand.kt @@ -22,7 +22,10 @@ import discord4j.core.event.domain.message.MessageCreateEvent import me.shedaniel.linkie.discord.CommandBase import me.shedaniel.linkie.discord.MessageCreator import me.shedaniel.linkie.discord.gateway -import me.shedaniel.linkie.discord.utils.* +import me.shedaniel.linkie.discord.utils.addField +import me.shedaniel.linkie.discord.utils.description +import me.shedaniel.linkie.discord.utils.discriminatedName +import me.shedaniel.linkie.discord.utils.setTimestampToNow import me.shedaniel.linkie.discord.validateEmpty object AboutCommand : CommandBase { diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/GetValueCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/GetValueCommand.kt index d8f6223..b0a839a 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/GetValueCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/GetValueCommand.kt @@ -19,11 +19,15 @@ package me.shedaniel.linkie.discord.commands import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent -import me.shedaniel.linkie.discord.* +import me.shedaniel.linkie.discord.CommandBase +import me.shedaniel.linkie.discord.MessageCreator import me.shedaniel.linkie.discord.config.ConfigManager import me.shedaniel.linkie.discord.utils.description import me.shedaniel.linkie.discord.utils.discriminatedName import me.shedaniel.linkie.discord.utils.setTimestampToNow +import me.shedaniel.linkie.discord.validateAdmin +import me.shedaniel.linkie.discord.validateInGuild +import me.shedaniel.linkie.discord.validateUsage object GetValueCommand : CommandBase { override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList, channel: MessageChannel) { diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListAllTricksCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListAllTricksCommand.kt index c7a55e3..f113670 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListAllTricksCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListAllTricksCommand.kt @@ -20,13 +20,17 @@ import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.spec.EmbedCreateSpec -import me.shedaniel.linkie.discord.* +import me.shedaniel.linkie.discord.CommandBase +import me.shedaniel.linkie.discord.MessageCreator +import me.shedaniel.linkie.discord.ValueKeeper import me.shedaniel.linkie.discord.scripting.LinkieScripting +import me.shedaniel.linkie.discord.sendPages import me.shedaniel.linkie.discord.tricks.Trick import me.shedaniel.linkie.discord.tricks.TricksManager import me.shedaniel.linkie.discord.utils.addInlineField import me.shedaniel.linkie.discord.utils.discriminatedName import me.shedaniel.linkie.discord.utils.setTimestampToNow +import me.shedaniel.linkie.discord.validateEmpty import me.shedaniel.linkie.utils.dropAndTake import java.time.Duration import java.time.Instant diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListTricksCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListTricksCommand.kt index 0c841bb..87d152e 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListTricksCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListTricksCommand.kt @@ -22,13 +22,17 @@ import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.spec.EmbedCreateSpec -import me.shedaniel.linkie.discord.* +import me.shedaniel.linkie.discord.CommandBase +import me.shedaniel.linkie.discord.MessageCreator +import me.shedaniel.linkie.discord.ValueKeeper import me.shedaniel.linkie.discord.scripting.LinkieScripting +import me.shedaniel.linkie.discord.sendPages import me.shedaniel.linkie.discord.tricks.Trick import me.shedaniel.linkie.discord.tricks.TricksManager import me.shedaniel.linkie.discord.utils.addInlineField import me.shedaniel.linkie.discord.utils.discriminatedName import me.shedaniel.linkie.discord.utils.setTimestampToNow +import me.shedaniel.linkie.discord.validateUsage import me.shedaniel.linkie.utils.dropAndTake import java.time.Duration import java.time.Instant diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListenerCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListenerCommand.kt index 829f70c..b03c2c2 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListenerCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ListenerCommand.kt @@ -17,8 +17,10 @@ package me.shedaniel.linkie.discord.commands import discord4j.core.`object`.entity.User +import discord4j.core.`object`.entity.channel.GuildMessageChannel import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent +import discord4j.rest.util.Permission import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -26,33 +28,118 @@ import me.shedaniel.linkie.discord.CommandBase import me.shedaniel.linkie.discord.MessageCreator import me.shedaniel.linkie.discord.SubCommandHolder import me.shedaniel.linkie.discord.config.ConfigManager +import me.shedaniel.linkie.discord.gateway import me.shedaniel.linkie.discord.listener.ChannelListeners +import me.shedaniel.linkie.discord.sendPages +import me.shedaniel.linkie.discord.utils.buildReactions +import me.shedaniel.linkie.discord.utils.buildSafeDescription +import me.shedaniel.linkie.discord.utils.discriminatedName +import me.shedaniel.linkie.discord.utils.setTimestampToNow +import me.shedaniel.linkie.discord.validateAdmin import me.shedaniel.linkie.discord.validateInGuild import me.shedaniel.linkie.discord.validateUsage +import me.shedaniel.linkie.utils.dropAndTake +import java.time.Duration +import kotlin.math.ceil object ListenerCommand : SubCommandHolder() { - val listen = Listen + val list = subCmd(List) + val listen = subCmd(Listen) + val unlisten = subCmd(UnListen) + + object List : CommandBase { + override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList, channel: MessageChannel) { + event.validateInGuild() + event.member.get().validateAdmin() + val config = ConfigManager[event.guildId.get().asLong()] + val channelId = event.message.channelId.asLong() + val listened = config.listenerChannels.filterValues { channelIds -> channelId in channelIds }.keys.toList().sorted() + val maxPage = ceil(listened.size / 10.0).toInt() + message.sendPages(0, listened.size / 10, user) { page -> + setFooter("Requested by ${user.discriminatedName}", user.avatarUrl) + setTimestampToNow() + if (maxPage > 1) setTitle("List of Listeners (Page ${page + 1}/$maxPage)") + else setTitle("List of Listeners") + buildSafeDescription { + listened.dropAndTake(10 * page, 10).forEach { id -> + if (isNotEmpty()) + append('\n') + append("- $id") + } + } + } + } + } object Listen : CommandBase { override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList, channel: MessageChannel) { event.validateInGuild() + event.member.get().validateAdmin() + require((channel as GuildMessageChannel).getEffectivePermissions(gateway.selfId).block()?.contains(Permission.MANAGE_MESSAGES) == true) { "Linkie currently lacks the `MANAGE_MESSAGES` permission!" } args.validateUsage(prefix, 1, "$cmd ") ChannelListeners[args[0].toLowerCase()] val config = ConfigManager[event.guildId.get().asLong()] - val channels = config.listenerChannels.getOrPut(args[0].toLowerCase(), ::mutableListOf) + val channels = config.listenerChannels.getOrPut(args[0].toLowerCase(), ::mutableSetOf) val channelId = event.message.channelId.asLong() if (channels.contains(channelId)) { - throw IllegalStateException("You have already this listener to this channel!") + throw IllegalStateException("You have already added this listener to this channel!") } else { channels.add(channelId) ConfigManager.save() message.sendEmbed { setTitle("Listener added") - setDescription("You have successfully added listener id `${args[0].toLowerCase()}`.\nThis message will self-destruct in 20 seconds to keep this channel clean.") + setDescription("You have successfully added listener id `${args[0].toLowerCase()}`.\nThis message will self-destruct in 20 seconds to keep this channel clean.\n" + + "Or alternatively you can just click that ❌ emote.") + }.subscribe { + var deleted = false + buildReactions(Duration.ofMinutes(2)) { + registerB("❌") { + deleted = true + it.delete().subscribe() + false + } + }.build(it) { it == user.id } + GlobalScope.launch { + delay(20000) + if (!deleted) + it.delete().subscribe() + } + } + } + } + } + + object UnListen : CommandBase { + override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList, channel: MessageChannel) { + event.validateInGuild() + event.member.get().validateAdmin() + args.validateUsage(prefix, 1, "$cmd ") + ChannelListeners[args[0].toLowerCase()] + val config = ConfigManager[event.guildId.get().asLong()] + val channels = config.listenerChannels.getOrPut(args[0].toLowerCase(), ::mutableSetOf) + val channelId = event.message.channelId.asLong() + if (!channels.contains(channelId)) { + throw IllegalStateException("You have not added this listener to this channel!") + } else { + channels.remove(channelId) + ConfigManager.save() + message.sendEmbed { + setTitle("Listener removed") + setDescription("You have successfully removed listener id `${args[0].toLowerCase()}`.\nThis message will self-destruct in 20 seconds to keep this channel clean.\n" + + "Or alternatively you can just click that ❌ emote.") }.subscribe { + var deleted = false + buildReactions(Duration.ofMinutes(2)) { + registerB("❌") { + deleted = true + it.delete().subscribe() + false + } + }.build(it) { it == user.id } GlobalScope.launch { delay(20000) - it.delete() + if (!deleted) + it.delete().subscribe() } } } diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/NamespacesCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/NamespacesCommand.kt index de2700f..7ebf955 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/NamespacesCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/NamespacesCommand.kt @@ -23,7 +23,6 @@ import me.shedaniel.linkie.Namespaces import me.shedaniel.linkie.discord.CommandBase import me.shedaniel.linkie.discord.MessageCreator import me.shedaniel.linkie.discord.utils.description -import me.shedaniel.linkie.discord.utils.sendEmbedMessage import me.shedaniel.linkie.discord.utils.discriminatedName import me.shedaniel.linkie.discord.utils.setTimestampToNow import me.shedaniel.linkie.discord.validateEmpty diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryCompoundCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryCompoundCommand.kt index b945e05..1c07798 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryCompoundCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryCompoundCommand.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.runBlocking import me.shedaniel.linkie.* import me.shedaniel.linkie.discord.* import me.shedaniel.linkie.discord.utils.* -import me.shedaniel.linkie.discord.utils.isValidIdentifier import me.shedaniel.linkie.namespaces.YarnNamespace import me.shedaniel.linkie.utils.* import java.time.Duration diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateFieldCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateFieldCommand.kt index 00f78f4..e6dc341 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateFieldCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateFieldCommand.kt @@ -25,7 +25,6 @@ import me.shedaniel.linkie.discord.* import me.shedaniel.linkie.discord.utils.* import me.shedaniel.linkie.utils.dropAndTake import me.shedaniel.linkie.utils.onlyClass -import me.shedaniel.linkie.utils.remapDescriptor import java.time.Duration import java.util.concurrent.atomic.AtomicInteger import kotlin.math.ceil diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateMethodCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateMethodCommand.kt index 36a76d5..f36628a 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateMethodCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/QueryTranslateMethodCommand.kt @@ -25,7 +25,6 @@ import me.shedaniel.linkie.discord.* import me.shedaniel.linkie.discord.utils.* import me.shedaniel.linkie.utils.dropAndTake import me.shedaniel.linkie.utils.onlyClass -import me.shedaniel.linkie.utils.remapDescriptor import java.time.Duration import java.util.concurrent.atomic.AtomicInteger import kotlin.math.ceil diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/RandomClassCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/RandomClassCommand.kt index fc777c9..375fa69 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/RandomClassCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/RandomClassCommand.kt @@ -16,7 +16,6 @@ package me.shedaniel.linkie.discord.commands -import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent @@ -25,11 +24,19 @@ import me.shedaniel.linkie.Class import me.shedaniel.linkie.MappingsContainer import me.shedaniel.linkie.MappingsProvider import me.shedaniel.linkie.Namespaces -import me.shedaniel.linkie.discord.* -import me.shedaniel.linkie.discord.utils.* +import me.shedaniel.linkie.discord.CommandBase +import me.shedaniel.linkie.discord.MessageCreator +import me.shedaniel.linkie.discord.ValueKeeper +import me.shedaniel.linkie.discord.utils.buildReactions +import me.shedaniel.linkie.discord.utils.description +import me.shedaniel.linkie.discord.utils.discriminatedName +import me.shedaniel.linkie.discord.utils.setTimestampToNow +import me.shedaniel.linkie.discord.utils.tryRemoveAllReactions +import me.shedaniel.linkie.discord.validateGuild +import me.shedaniel.linkie.discord.validateNamespace +import me.shedaniel.linkie.discord.validateUsage import java.time.Duration import java.util.* -import java.util.concurrent.atomic.AtomicReference object RandomClassCommand : CommandBase { override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList, channel: MessageChannel) { diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/SetValueCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/SetValueCommand.kt index 9dd9cf6..42ad84f 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/SetValueCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/SetValueCommand.kt @@ -19,11 +19,15 @@ package me.shedaniel.linkie.discord.commands import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent -import me.shedaniel.linkie.discord.* +import me.shedaniel.linkie.discord.CommandBase +import me.shedaniel.linkie.discord.MessageCreator import me.shedaniel.linkie.discord.config.ConfigManager import me.shedaniel.linkie.discord.utils.description import me.shedaniel.linkie.discord.utils.discriminatedName import me.shedaniel.linkie.discord.utils.setTimestampToNow +import me.shedaniel.linkie.discord.validateAdmin +import me.shedaniel.linkie.discord.validateInGuild +import me.shedaniel.linkie.discord.validateUsage object SetValueCommand : CommandBase { override suspend fun execute(event: MessageCreateEvent, message: MessageCreator, prefix: String, user: User, cmd: String, args: MutableList, channel: MessageChannel) { diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/TrickInfoCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/TrickInfoCommand.kt index 93455ee..6e6b5e2 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/TrickInfoCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/TrickInfoCommand.kt @@ -23,7 +23,11 @@ import me.shedaniel.linkie.discord.CommandBase import me.shedaniel.linkie.discord.MessageCreator import me.shedaniel.linkie.discord.scripting.LinkieScripting import me.shedaniel.linkie.discord.tricks.TricksManager -import me.shedaniel.linkie.discord.utils.* +import me.shedaniel.linkie.discord.utils.addField +import me.shedaniel.linkie.discord.utils.addInlineField +import me.shedaniel.linkie.discord.utils.description +import me.shedaniel.linkie.discord.utils.discriminatedName +import me.shedaniel.linkie.discord.utils.setTimestampToNow import me.shedaniel.linkie.discord.validateUsage import java.time.Instant diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ValueListCommand.kt b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ValueListCommand.kt index fbd7ee9..ba9ca8a 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/commands/ValueListCommand.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/commands/ValueListCommand.kt @@ -20,10 +20,18 @@ import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.spec.EmbedCreateSpec -import me.shedaniel.linkie.discord.* +import me.shedaniel.linkie.discord.CommandBase +import me.shedaniel.linkie.discord.MessageCreator import me.shedaniel.linkie.discord.config.ConfigManager import me.shedaniel.linkie.discord.config.GuildConfig -import me.shedaniel.linkie.discord.utils.* +import me.shedaniel.linkie.discord.sendPages +import me.shedaniel.linkie.discord.utils.addInlineField +import me.shedaniel.linkie.discord.utils.description +import me.shedaniel.linkie.discord.utils.discriminatedName +import me.shedaniel.linkie.discord.utils.setTimestampToNow +import me.shedaniel.linkie.discord.validateAdmin +import me.shedaniel.linkie.discord.validateEmpty +import me.shedaniel.linkie.discord.validateInGuild import me.shedaniel.linkie.utils.dropAndTake import kotlin.math.ceil diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/config/GuildConfig.kt b/src/main/kotlin/me/shedaniel/linkie/discord/config/GuildConfig.kt index 141964b..ab9ac36 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/config/GuildConfig.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/config/GuildConfig.kt @@ -26,7 +26,7 @@ data class GuildConfig( var evalEnabled: Boolean = true, var whitelistedMappings: List = listOf(), var blacklistedMappings: List = listOf(), - var listenerChannels: MutableMap> = mutableMapOf(), + var listenerChannels: MutableMap> = mutableMapOf(), ) { fun isMappingsEnabled(namespace: String): Boolean { if (whitelistedMappings.isNotEmpty()) diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/listener/ChannelListeners.kt b/src/main/kotlin/me/shedaniel/linkie/discord/listener/ChannelListeners.kt index 2d3e4f5..efc3f9d 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/listener/ChannelListeners.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/listener/ChannelListeners.kt @@ -17,26 +17,21 @@ package me.shedaniel.linkie.discord.listener import com.soywiz.klock.minutes -import com.soywiz.klock.seconds import com.soywiz.korio.async.runBlockingNoJs import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.User -import discord4j.core.`object`.entity.channel.GuildChannel -import discord4j.core.`object`.entity.channel.MessageChannel -import discord4j.core.`object`.entity.channel.TextChannel +import discord4j.core.`object`.entity.channel.GuildMessageChannel +import discord4j.rest.util.Permission import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import me.shedaniel.linkie.discord.EmbedCreator import me.shedaniel.linkie.discord.MessageCreator -import me.shedaniel.linkie.discord.api import me.shedaniel.linkie.discord.config.ConfigManager -import me.shedaniel.linkie.discord.config.GuildConfig import me.shedaniel.linkie.discord.gateway import me.shedaniel.linkie.discord.utils.content import me.shedaniel.linkie.discord.utils.sendEmbedMessage @@ -46,8 +41,6 @@ import me.shedaniel.linkie.utils.info import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.io.File -import java.lang.NullPointerException -import java.util.stream.Collectors object ChannelListeners { private val listeners = mutableMapOf>() @@ -61,7 +54,7 @@ object ChannelListeners { operator fun get(id: String): ChannelListener<*> = listeners[id] ?: throw NullPointerException("Unknown listener: $id\nKnown Listeners: " + listeners.keys.joinToString(", ")) fun init() { - val cycleMs = 30.seconds.millisecondsLong + val cycleMs = 1.minutes.millisecondsLong var nextDelay = getMillis() - cycleMs CoroutineScope(Dispatchers.Default).launch { while (true) { @@ -78,7 +71,11 @@ object ChannelListeners { coroutineScope { listeners.map { (id, listener) -> launch { - reloadListener(id, listener) + try { + reloadListener(id, listener) + } catch (t: Throwable) { + t.printStackTrace() + } } }.forEach { it.join() } } @@ -87,17 +84,17 @@ object ChannelListeners { private suspend fun reloadListener(id: String, listener: ChannelListener) { coroutineScope { val dataFile = File(dir, "$id.json") - val channels = mutableListOf() - ConfigManager.configs.forEach { (guildId, config) -> - config.listenerChannels[id]?.takeIf { it.isNotEmpty() }?.also { channelIds -> + val flux: Flux> = ConfigManager.configs.mapNotNull { (guildId, config) -> + config.listenerChannels[id]?.takeIf { it.isNotEmpty() }?.let { channelIds -> val guild = gateway.getGuildById(Snowflake.of(guildId)).blockOptional().get() - guild.members Flux.fromIterable(channelIds) .flatMap { guild.getChannelById(Snowflake.of(it)) } - .filter { it is TextChannel } - .collect(Collectors.toCollection { channels }) + .filter { it is GuildMessageChannel } + .map { it as GuildMessageChannel } + .collectList() } - } + }.let { Flux.fromIterable(it) }.flatMap { it } + val channels: List = flux.collectList().block()?.flatten() ?: emptyList() val message = object : MessageCreator { override val executor: User? = null @@ -106,7 +103,11 @@ object ChannelListeners { channels.forEach { channel -> val message = channel.sendMessage { it.content = content - }.flatMap { it.publish() } + }.flatMap { + if (channel.getEffectivePermissions(gateway.selfId).block()?.contains(Permission.MANAGE_MESSAGES) == true) + it.publish() + else Mono.just(it) + } mono = if (mono == null) message else mono!!.then(message) } @@ -116,7 +117,11 @@ object ChannelListeners { override fun sendEmbed(content: EmbedCreator): Mono { var mono: Mono? = null channels.forEach { channel -> - val message = channel.sendEmbedMessage { runBlockingNoJs { content() } }.flatMap { it.publish() } + val message = channel.sendEmbedMessage { runBlockingNoJs { content() } }.flatMap { + if (channel.getEffectivePermissions(gateway.selfId).block()?.contains(Permission.MANAGE_MESSAGES) == true) + it.publish() + else Mono.just(it) + } mono = if (mono == null) message else mono!!.then(message) } diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/ArchitecturyVersionListener.kt b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/ArchitecturyVersionListener.kt new file mode 100644 index 0000000..38df3ea --- /dev/null +++ b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/ArchitecturyVersionListener.kt @@ -0,0 +1,55 @@ +package me.shedaniel.linkie.discord.listener.listeners + +import discord4j.rest.util.Color + +object ArchitecturyVersionListener : MavenPomVersionListener() { + init { + listen("plugin", "https://maven.shedaniel.me/me/shedaniel/architectury-plugin/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Architectury Plugin Update") + setDescription("New Architectury Plugin version has been added: $version") + setColor(Color.ORANGE) + }.subscribe() + } + + listen("transformer", "https://maven.shedaniel.me/me/shedaniel/architectury-transformer/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Architectury Transformer Update") + setDescription("New Architectury Transformer version has been added: $version") + setColor(Color.ORANGE) + }.subscribe() + } + + listen("api", "https://maven.shedaniel.me/me/shedaniel/architectury/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Architectury API Update") + setDescription("New Architectury API version has been added: $version") + setColor(Color.ORANGE) + }.subscribe() + } + + listen("api-snapshot", "https://maven.shedaniel.me/me/shedaniel/architectury-snapshot/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Architectury API Update") + setDescription("New Architectury API snapshot has been added: $version") + setColor(Color.ORANGE) + }.subscribe() + } + + listen("plugin", "https://maven.shedaniel.me/architectury-plugin/architectury-plugin.gradle.plugin/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Architectury Plugin Update") + setDescription("New Architectury Plugin version has been added: $version") + setColor(Color.ORANGE) + }.subscribe() + } + + listen("loom", "https://maven.shedaniel.me/forgified-fabric-loom/forgified-fabric-loom.gradle.plugin/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Architectury Loom Update") + setDescription("New Architectury Loom version has been added: $version") + setColor(Color.ORANGE) + }.subscribe() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/FabricMCVersionListener.kt b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/FabricMCVersionListener.kt new file mode 100644 index 0000000..fa8a7a5 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/FabricMCVersionListener.kt @@ -0,0 +1,59 @@ +package me.shedaniel.linkie.discord.listener.listeners + +import discord4j.rest.util.Color +import me.shedaniel.linkie.utils.toVersion +import me.shedaniel.linkie.utils.tryToVersion + +object FabricMCVersionListener : MavenPomVersionListener() { + init { + listen("intermediary", "https://maven.fabricmc.net/net/fabricmc/intermediary/maven-metadata.xml") { version, message -> + message.sendEmbed { + val isUnstable = version.tryToVersion() == null || version.toVersion().snapshot == null + setTitle("Fabric Intermediary Update") + setDescription("New Intermediary ${if (isUnstable) "snapshot" else "release"} has been added: $version") + setColor(Color.GRAY) + }.subscribe() + } + + listen("installer", "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Fabric Installer Update") + setDescription("New Fabric Installer version has been added: $version") + setColor(Color.GRAY) + }.subscribe() + } + + listen("loom", "https://maven.fabricmc.net/net/fabricmc/fabric-loom/maven-metadata.xml") { version, message -> + if (version.endsWith("SNAPSHOT")) return@listen + message.sendEmbed { + setTitle("Fabric Loom Update") + setDescription("New Fabric Loom version has been added: $version") + setColor(Color.GRAY) + }.subscribe() + } + + listen("loader", "https://maven.fabricmc.net/net/fabricmc/fabric-loader/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Fabric Loader Update") + setDescription("New Fabric Loader version has been added: $version") + setColor(Color.GRAY) + }.subscribe() + } + + listen("yarn", "https://maven.fabricmc.net/net/fabricmc/yarn/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Yarn Update") + setDescription("New Yarn version has been added: $version") + setColor(Color.GRAY) + }.subscribe() + } + + listen("api", "https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml") { version, message -> + message.sendEmbed { + setTitle("Fabric API Update") + setDescription("New Fabric API version has been added: $version") + setColor(Color.GRAY) + }.subscribe() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/MavenPomVersionListener.kt b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/MavenPomVersionListener.kt new file mode 100644 index 0000000..1320a35 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/MavenPomVersionListener.kt @@ -0,0 +1,89 @@ +package me.shedaniel.linkie.discord.listener.listeners + +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import me.shedaniel.linkie.discord.MessageCreator +import me.shedaniel.linkie.discord.listener.ChannelListener +import me.shedaniel.linkie.utils.Version +import me.shedaniel.linkie.utils.toVersion +import me.shedaniel.linkie.utils.tryToVersion +import org.dom4j.io.SAXReader +import java.io.InputStream +import java.net.URL +import java.net.URLConnection + +open class MavenPomVersionListener : ChannelListener { + override val serializer: KSerializer = PomVersionData.serializer() + private val mavenListeners = mutableMapOf() + + protected fun listen( + id: String, + mavenPomURL: String, + messageSender: (version: String, message: MessageCreator) -> Unit, + ) = listen(id, URL(mavenPomURL), messageSender) + + protected fun listen( + id: String, + mavenPomURL: URL, + messageSender: (version: String, message: MessageCreator) -> Unit, + ) { + mavenListeners[id] = MavenListener(mavenPomURL, messageSender) + } + + override suspend fun updateData(data: PomVersionData?, message: MessageCreator): PomVersionData { + val newData = data ?: PomVersionData() + + runBlocking { + mavenListeners.forEach { (id, listener) -> + launch { + val newVersions = mutableSetOf() + val containsKey = newData.mavens.containsKey(id) + val mavenData = newData.mavens.getOrPut(id, ::mutableSetOf) + val rootElement = SAXReader().read(listener.mavenPomURL.readText().byteInputStream()).rootElement + rootElement + .element("versioning") + .element("versions") + .elementIterator("version") + .asSequence() + .map { it.text } + .forEach { version -> + if (mavenData.add(version) && data != null && containsKey) { + if (version.tryToVersion() == null) { + listener.messageSender(version, message) + } else { + newVersions.add(version.toVersion()) + } + } + } + + if (newVersions.isNotEmpty()) { + val latest = newVersions.maxOrNull()!!.toString() + listener.messageSender(latest, message) + } + } + } + } + + return newData + } + + @Serializable + data class PomVersionData( + val mavens: MutableMap> = mutableMapOf(), + ) + + private fun URL.readText(): String { + val connection: URLConnection = openConnection() + connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.11 Safari/537.36") + connection.connect() + val stream: InputStream = connection.getInputStream() + return stream.use { it.readBytes().decodeToString() } + } +} + +data class MavenListener( + val mavenPomURL: URL, + val messageSender: (version: String, message: MessageCreator) -> Unit, +) \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/MinecraftVersionListener.kt b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/MinecraftVersionListener.kt new file mode 100644 index 0000000..06f3a31 --- /dev/null +++ b/src/main/kotlin/me/shedaniel/linkie/discord/listener/listeners/MinecraftVersionListener.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, 2020, 2021 shedaniel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.shedaniel.linkie.discord.listener.listeners + +import discord4j.rest.util.Color +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import me.shedaniel.linkie.discord.MessageCreator +import me.shedaniel.linkie.discord.listener.ChannelListener +import me.shedaniel.linkie.utils.toVersion +import me.shedaniel.linkie.utils.tryToVersion +import java.net.URL + +object MinecraftVersionListener : ChannelListener { + private val json = Json { } + override val serializer: KSerializer = MinecraftVersionInfo.serializer() + + override suspend fun updateData(data: MinecraftVersionInfo?, message: MessageCreator): MinecraftVersionInfo { + val newData = data ?: MinecraftVersionInfo() + + runBlocking { + launch { + val versions = json.parseToJsonElement(URL("https://launchermeta.mojang.com/mc/game/version_manifest.json").readText()).jsonObject["versions"]!!.jsonArray + versions.forEach { versionElement -> + val id = versionElement.jsonObject["id"]!!.jsonPrimitive.content + val type = versionElement.jsonObject["type"]!!.jsonPrimitive.content + val url = versionElement.jsonObject["url"]!!.jsonPrimitive.content + val isUnstable = type != "release" || id.tryToVersion() == null || id.toVersion().snapshot != null + + if (newData.versions.put(id, url) != url && data != null) { + message.sendEmbed { + setTitle("Minecraft Update") + setDescription("New Minecraft ${if (isUnstable) "snapshot" else "release"} has been released: $id") + + setColor(Color.GREEN) + }.subscribe() + } + } + } + + launch { + val versions = json.parseToJsonElement(URL("https://bugs.mojang.com/rest/api/latest/project/MC/versions").readText()).jsonArray + versions.forEach { versionElement -> + if (!versionElement.jsonObject["released"]!!.jsonPrimitive.boolean) return@forEach + val name = versionElement.jsonObject["name"]!!.jsonPrimitive.content.removePrefix("Minecraft ") + if (name.startsWith("Future Version")) return@forEach + val id = versionElement.jsonObject["id"]!!.jsonPrimitive.content + val isUnstable = name.tryToVersion() == null || name.toVersion().snapshot != null + + if (newData.issueTrackerVersions.put(name, id) != id && data != null) { + message.sendEmbed { + setTitle("Minecraft Update") + setDescription("New Minecraft ${if (isUnstable) "snapshot" else "release"} has been added to the issue tracker: $id") + + setColor(Color.GREEN) + }.subscribe() + } + } + } + } + + return newData + } + + @Serializable + data class MinecraftVersionInfo( + val versions: MutableMap = mutableMapOf(), + val issueTrackerVersions: MutableMap = mutableMapOf(), + ) +} \ No newline at end of file diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/scripting/ContextExtensions.kt b/src/main/kotlin/me/shedaniel/linkie/discord/scripting/ContextExtensions.kt index da91a65..63756a0 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/scripting/ContextExtensions.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/scripting/ContextExtensions.kt @@ -27,7 +27,14 @@ import discord4j.core.`object`.presence.Presence import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.rest.util.Permission import me.shedaniel.linkie.discord.gateway -import me.shedaniel.linkie.discord.utils.* +import me.shedaniel.linkie.discord.utils.description +import me.shedaniel.linkie.discord.utils.discriminatedName +import me.shedaniel.linkie.discord.utils.getOrNull +import me.shedaniel.linkie.discord.utils.sendEdit +import me.shedaniel.linkie.discord.utils.sendEditEmbed +import me.shedaniel.linkie.discord.utils.sendEmbedMessage +import me.shedaniel.linkie.discord.utils.sendMessage +import me.shedaniel.linkie.discord.utils.setTimestampToNow import me.shedaniel.linkie.discord.validatePermissions import org.graalvm.polyglot.Value import org.graalvm.polyglot.proxy.ProxyArray @@ -35,7 +42,15 @@ import org.graalvm.polyglot.proxy.ProxyExecutable import org.graalvm.polyglot.proxy.ProxyInstant import java.time.Instant import java.util.* -import kotlin.math.* +import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.cos +import kotlin.math.floor +import kotlin.math.min +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.sqrt +import kotlin.math.tan import kotlin.random.Random object ContextExtensions { diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/scripting/LinkieScripting.kt b/src/main/kotlin/me/shedaniel/linkie/discord/scripting/LinkieScripting.kt index 92b4e22..036abbc 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/scripting/LinkieScripting.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/scripting/LinkieScripting.kt @@ -18,7 +18,11 @@ package me.shedaniel.linkie.discord.scripting import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.message.MessageCreateEvent -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout import me.shedaniel.linkie.discord.config.ConfigManager import me.shedaniel.linkie.discord.tricks.ContentType import me.shedaniel.linkie.discord.tricks.Trick diff --git a/src/main/kotlin/me/shedaniel/linkie/discord/tricks/Trick.kt b/src/main/kotlin/me/shedaniel/linkie/discord/tricks/Trick.kt index 8622973..4b08b50 100644 --- a/src/main/kotlin/me/shedaniel/linkie/discord/tricks/Trick.kt +++ b/src/main/kotlin/me/shedaniel/linkie/discord/tricks/Trick.kt @@ -27,7 +27,12 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import me.shedaniel.linkie.discord.scripting.* +import me.shedaniel.linkie.discord.scripting.ContextExtensions +import me.shedaniel.linkie.discord.scripting.EvalContext +import me.shedaniel.linkie.discord.scripting.ScriptingContext +import me.shedaniel.linkie.discord.scripting.funObj +import me.shedaniel.linkie.discord.scripting.getAsString +import me.shedaniel.linkie.discord.scripting.validateArgs import java.util.* @Serializable