From 948b32f3879152b0836bac508cd29897a2be47fd Mon Sep 17 00:00:00 2001 From: Julius de Jeu Date: Sat, 1 Dec 2018 00:46:46 +0100 Subject: [PATCH] Add logger for the server, it can be used to log edits, deletions and nickname alters Add admin role thing, this can be used to set admin roles Add @random to @ a random user --- src/main/kotlin/nl/voidcorp/dbot/Events.kt | 13 ++ src/main/kotlin/nl/voidcorp/dbot/Logging.kt | 120 ++++++++++++++++++ src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt | 2 +- .../voidcorp/dbot/commands/AdminCommands.kt | 20 +++ .../nl/voidcorp/dbot/commands/Categories.kt | 28 ++-- .../nl/voidcorp/dbot/commands/Commands.kt | 18 ++- .../kotlin/nl/voidcorp/dbot/music/Music.kt | 47 ++++++- .../voidcorp/dbot/music/SecondaryCommands.kt | 9 +- .../nl/voidcorp/dbot/music/TrackScheduler.kt | 64 ++++++++-- .../nl/voidcorp/dbot/storage/GuildSettings.kt | 4 +- 10 files changed, 292 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/nl/voidcorp/dbot/Logging.kt diff --git a/src/main/kotlin/nl/voidcorp/dbot/Events.kt b/src/main/kotlin/nl/voidcorp/dbot/Events.kt index ce35e2a..0b690dc 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/Events.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/Events.kt @@ -1,10 +1,13 @@ package nl.voidcorp.dbot +import net.dv8tion.jda.core.OnlineStatus +import net.dv8tion.jda.core.Permission import net.dv8tion.jda.core.events.guild.voice.GuildVoiceLeaveEvent import net.dv8tion.jda.core.events.message.MessageReceivedEvent import net.dv8tion.jda.core.events.message.react.MessageReactionAddEvent import net.dv8tion.jda.core.events.message.react.MessageReactionRemoveEvent import net.dv8tion.jda.core.hooks.ListenerAdapter +import nl.voidcorp.dbot.commands.GSM import nl.voidcorp.dbot.music.guildMusicMap object Events : ListenerAdapter() { @@ -32,6 +35,16 @@ object Events : ListenerAdapter() { } } }*/ + if (event.message.contentRaw.contains("@random") and + (event.author != event.jda.selfUser) and + (GSM.getSettings(event.guild).adminRoles.any { it in event.member.roles.map { role -> role.idLong } } + or event.member.hasPermission(Permission.ADMINISTRATOR)) + ) { + val mem = event.textChannel.members.filter { !it.user.isBot } + .filter { it.onlineStatus in mutableListOf(OnlineStatus.ONLINE, OnlineStatus.IDLE) } + .filter { it.user.idLong != event.author.idLong }.random() + event.channel.sendMessage("Hey, ${mem.asMention} look here ^").queue() + } if (event.author.idLong == 168743656738521088 && event.message.mentionedMembers.contains( event.guild.getMember( event.jda.selfUser diff --git a/src/main/kotlin/nl/voidcorp/dbot/Logging.kt b/src/main/kotlin/nl/voidcorp/dbot/Logging.kt new file mode 100644 index 0000000..4f04ad8 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/dbot/Logging.kt @@ -0,0 +1,120 @@ +package nl.voidcorp.dbot + +import net.dv8tion.jda.core.EmbedBuilder +import net.dv8tion.jda.core.entities.* +import net.dv8tion.jda.core.events.guild.member.GuildMemberNickChangeEvent +import net.dv8tion.jda.core.events.message.guild.GuildMessageDeleteEvent +import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent +import net.dv8tion.jda.core.events.message.guild.GuildMessageUpdateEvent +import net.dv8tion.jda.core.hooks.ListenerAdapter +import nl.voidcorp.dbot.commands.GSM +import java.awt.Color +import java.time.LocalDateTime +import java.time.temporal.TemporalAccessor +import java.util.* + +object Logging : ListenerAdapter() { + val map = mutableMapOf>() + + enum class Colors(val colorString: String) { + NAMECHANGE("#00cc66"), + MESSAGEDELETE("#ff3300"), + MESSAGEEDIT("#ffcc66"), + UNKNOWN("#000000") + } + + override fun onGuildMessageReceived(event: GuildMessageReceivedEvent) { + if (map[event.guild.idLong] == null) map[event.guild.idLong] = ArrayDeque() + map[event.guild.idLong]?.apply { + while (size > 100) { + poll() + } + offer(event.message) + } + } + + fun logChannel(guild: Guild): TextChannel? { + val log = GSM.getSettings(guild).logChannel + return if (log != null) { + guild.getTextChannelById(log) + } else { + null + } + } + + fun buildEmbed( + affectedMember: Member?, + message: String = "", + color: Colors = Colors.UNKNOWN, + title: String = color.name.toLowerCase().capitalize(), + fields: Map = mutableMapOf(), + timestamp: TemporalAccessor = LocalDateTime.now() + ): MessageEmbed { + val em = EmbedBuilder() + .setTimestamp(timestamp) + .setColor(Color.decode(color.colorString)) + if (title.isNotBlank()) em.setTitle(title) + if (message.isNotBlank()) em.setDescription(message) + fields.forEach { name, content -> em.addField(name, content, true) } + if (affectedMember != null) em.setFooter(affectedMember.user.name, affectedMember.user.effectiveAvatarUrl) + return em.build() + } + + override fun onGuildMemberNickChange(event: GuildMemberNickChangeEvent) { + val lc = logChannel(event.guild) + if ((event.user.idLong == 144116077129891840) or (event.user.idLong == 131399667442384896)) return + if (lc != null) { + val em = buildEmbed( + event.member, + fields = mutableMapOf( + "Old name" to (event.prevNick ?: event.user.name), + "New Name" to (event.newNick ?: event.user.name) + ), + color = Colors.NAMECHANGE + ) + lc.sendMessage(em).queue() + } + } + + override fun onGuildMessageDelete(event: GuildMessageDeleteEvent) { + val lc = logChannel(event.guild) + if (lc != null) { + map[event.guild.idLong]?.firstOrNull { it.idLong == event.messageIdLong }.let { + + val em = buildEmbed( + it?.member, + color = Colors.MESSAGEDELETE, + fields = mutableMapOf( + "Message" to (it?.contentDisplay ?: "Something else (message too old...)"), + "Channel" to event.channel.asMention + ), + timestamp = (it?.editedTime ?: it?.creationTime) ?: LocalDateTime.now() + ) + lc.sendMessage(em).queue() + } + } + } + + override fun onGuildMessageUpdate(event: GuildMessageUpdateEvent) { + val lc = logChannel(event.guild) + if (lc != null) { + (map[event.guild.idLong]?.firstOrNull { it.idLong == event.messageIdLong }).let { + + val em = buildEmbed( + event.member, color = Colors.MESSAGEEDIT, + fields = mutableMapOf( + "Old message" to (it?.contentStripped ?: "Something else (message too old...)"), + "New message" to event.message.contentStripped, + "Channel" to event.channel.asMention + ) + ) + + map[event.guild.idLong]?.removeAll { old -> old.idLong == event.messageIdLong } + map[event.guild.idLong]?.offer(event.message) + lc.sendMessage(em).queue() + } + } + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt b/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt index 6558515..9b25c05 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt @@ -39,7 +39,7 @@ fun main(args: Array) { custom.version = "1.5" - bot = JDABuilder(args[0]).addEventListener(custom).addEventListener(nl.voidcorp.dbot.Events) + bot = JDABuilder(args[0]).addEventListener(custom).addEventListener(Events).addEventListener(Logging) .setAudioSendFactory(NativeAudioSendFactory()).build() /*Runtime.getRuntime().addShutdownHook(thread(start = false) { diff --git a/src/main/kotlin/nl/voidcorp/dbot/commands/AdminCommands.kt b/src/main/kotlin/nl/voidcorp/dbot/commands/AdminCommands.kt index b32992c..7df7692 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/commands/AdminCommands.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/commands/AdminCommands.kt @@ -12,6 +12,8 @@ fun initAdmin() { commands += ListPrefixes commands += RemovePrefix commands += AddRoleCommand + commands += addAdminRole + commands += logSetCommand /*commands += MuteCommand commands += LuaExec*/ } @@ -100,6 +102,24 @@ object AddRoleCommand : UnityCommand( } }) +val addAdminRole = UnityCommand("addadminrole", "Add a role that is considered and admin role", AdminCategory) { ce -> + val g = GSM.getSettings(ce.guild) + + g.adminRoles.addAll(ce.message.mentionedRoles.map { it.idLong }) + ce.guild.getRolesByName("admin", true).firstOrNull()?.idLong?.let { g.adminRoles.add(it) } + ce.reply("The admin roles are ${g.adminRoles.joinToString { "`${ce.guild.getRoleById(it).name}`" }}") +} + +val logSetCommand = UnityCommand("loghere", "Set the logging channel", AdminCategory) { ce -> + val g = GSM.getSettings(ce.guild) + if (ce.textChannel != null) { + ce.reply("Set logging channel to ${ce.textChannel!!.asMention}!") + g.logChannel = ce.textChannel!!.idLong + } + + +} + object LuaExec : UnityCommand("luaexec", "Executes a bit of lua code", AdminCategory, exec = { ce -> ce.message.delete().queue() diff --git a/src/main/kotlin/nl/voidcorp/dbot/commands/Categories.kt b/src/main/kotlin/nl/voidcorp/dbot/commands/Categories.kt index 86702ab..ebc378a 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/commands/Categories.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/commands/Categories.kt @@ -79,20 +79,32 @@ object GeneralCategory : UnityCategory("general") }*/ -object MusicCategory : UnityCategory("Music Commands", listOf("bot", "music", "music-bot"), errorMessage = { "Music commands can only be used in ${it.asMention}!" }) -object MusicCategoryPrivate : UnityCategory("Music Commands", listOf("bot", "music", "music-bot"), listOf("admin"), { "Music commands can only be used in ${it.asMention}!" }) +object MusicCategory : UnityCategory( + "Music Commands", + listOf("bot", "music", "music-bot"), + errorMessage = { "Music commands can only be used in ${it.asMention}!" }) -object AdminCategory : UnityCategory("Admin Stuff") { +object MusicCategoryPrivate : UnityCategory( + "Music Commands", + listOf("bot", "music", "music-bot"), errorMessage = + { "Music commands can only be used in ${it.asMention}!" }) { override fun test(ce: UnityCommandEvent): Boolean { - val res = when { + + return super.test(ce) and AdminCategory.test(ce) + + } +} + +object AdminCategory : UnityCategory("Admin Stuff", errorMessage = { "You need to be and admin to use this!" }) { + override fun test(ce: UnityCommandEvent): Boolean { + return when { ce.member.hasPermission(Permission.ADMINISTRATOR) -> true ce.member.roles.firstOrNull { it.name.equals("admin", true) } != null -> true + ce.member.roles.map { it.idLong }.any { + GSM.getSettings(ce.guild).adminRoles.contains(it) + } -> true else -> false } - if (!res) { - ce.reply("You are not an admin!") - } - return res } } diff --git a/src/main/kotlin/nl/voidcorp/dbot/commands/Commands.kt b/src/main/kotlin/nl/voidcorp/dbot/commands/Commands.kt index b10a438..b681f9b 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/commands/Commands.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/commands/Commands.kt @@ -14,13 +14,19 @@ import java.time.temporal.ChronoUnit import kotlin.random.Random -val helloCommand = UnityCommand("hello", "Say hello to Andy!") { - val i = Random.nextInt(10) - if (i > 8) { - it.reply("Can you speak up or I'll throw you a microphone") - } else { - it.reply(MessageBuilder("Hello, ").append(it.author).append("!").build()) +val helloCommand = UnityCommand("hello", "Say hello to Andy!") { uce -> + val nick: String? = uce.selfMember.nickname + uce.guild.controller.setNickname(uce.selfMember, "Andy Zaidman").queue { + val i = Random.nextInt(10) + if (i > 8) { + uce.reply("Can you speak up or I'll throw you a microphone") + } else { + uce.reply(MessageBuilder("Hello, ").append(uce.author).append("!").build()) + } + uce.guild.controller.setNickname(uce.selfMember, nick).queue() + } + } diff --git a/src/main/kotlin/nl/voidcorp/dbot/music/Music.kt b/src/main/kotlin/nl/voidcorp/dbot/music/Music.kt index 07a8067..6144c36 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/music/Music.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/music/Music.kt @@ -269,6 +269,47 @@ fun initMusic() { event.reply("Stopped all playback!") } + val pauseCommand = UnityMusicCommand( + "pause", + "Pauses the song, or resumes it!", + MusicCategory, + aliases = mutableListOf("resume") + ) { event, scheduler -> + if (scheduler.isPaused()) { + scheduler.resume() + event.reply("Resuming playback!") + } else { + scheduler.pause() + event.reply("Paused playback!") + } + + } + + val seekCommand = UnityMusicCommand( + "seek", + "Seeks to a specified point in the song!", + aliases = mutableListOf(), + arguments = "dd:hh:mm:ss" + ) { event, scheduler -> + var a = event.args + + if ((a.toLongOrNull() == null) and (a.indexOf(":") == -1) and !a.split(':').map { it.toLongOrNull() }.any { it == null }) { + event.reply("Please supply your time in the format `dd:hh:mm:ss` (values that are `00:` can be omitted)") + return@UnityMusicCommand + } else { + while (a.count { it == ':' } < 3) { + a = "00:$a" + } + + val millis = scheduler.timeToMillis(a) + scheduler.seek(millis) + event.reply(scheduler.getCurrentTrackInfo()) + + } + + + } + commands.addAll( playCommand, skipCommand, @@ -279,7 +320,9 @@ fun initMusic() { attachmentPlay, voteSkipCommand, loopCommand, - stopCommand + stopCommand, + pauseCommand, + seekCommand ) } @@ -312,7 +355,7 @@ fun getLinkFromSearch( } override fun noMatches() { - + event.reply("Sorry, I ") } override fun playlistLoaded(playlist: AudioPlaylist) { diff --git a/src/main/kotlin/nl/voidcorp/dbot/music/SecondaryCommands.kt b/src/main/kotlin/nl/voidcorp/dbot/music/SecondaryCommands.kt index 829f1b6..d2a252a 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/music/SecondaryCommands.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/music/SecondaryCommands.kt @@ -5,6 +5,7 @@ import net.dv8tion.jda.core.EmbedBuilder import nl.voidcorp.dbot.commands import nl.voidcorp.dbot.commands.MusicCategory import nl.voidcorp.dbot.commands.UnityCommand +import java.net.URLEncoder import java.time.LocalDateTime val lyricsCommand = UnityCommand("lyrics", "Search for lyrics!", MusicCategory) { ce -> @@ -53,7 +54,7 @@ data class Song(val artist: String, val title: String) fun findInfo(search: String): Song? { val res = khttp.get( - "https://api.genius.com/search?q=${search.replace(" ", "%20")}", + "https://api.genius.com/search?q=${URLEncoder.encode(search, "UTF8")}", headers = mapOf("Authorization" to "Bearer eqn-1xrvrAKtoIFC-pIgNiW7cRzSvaF49wjFzasEu7coSLpufVVnv_IGVnxUIT43") ) val hits = res.jsonObject.getJSONObject("response").getJSONArray("hits")!! @@ -69,9 +70,9 @@ fun findInfo(search: String): Song? { fun findText(song: Song): String { val res = get( - "https://orion.apiseeds.com/api/music/lyric/${song.artist}/${song.title.replace( - " ", - "%20" + "https://orion.apiseeds.com/api/music/lyric/${URLEncoder.encode(song.artist, "UTF8")}/${URLEncoder.encode( + song.title, + "UTF8" )}?apikey=8pLAxkCnWJNGWRaoPcbCFpUAKdKD77zmlcjs2FKYjdH00MDyNr6lXLHb3PQZsKJI" ) return if (res.jsonObject.isNull("error")) { diff --git a/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt b/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt index be2e4b1..8968a76 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt @@ -51,7 +51,7 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, val voiceChannel pressedSkip = 0 if (q.isNotEmpty()) { val t = q.poll()!! - if (loop) q.addLast(t.makeClone().apply { userData = t.userData }) + if (loop) q.addLast(track.makeClone().apply { userData = t.userData }) player.playTrack(t) } else { if (loop) { @@ -180,7 +180,7 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, val voiceChannel } - eb.setDescription("${millisToTime(track.position)}/${millisToTime(track.duration)}") + eb.setDescription(genTrackTime(track)) return eb.build() } @@ -195,20 +195,43 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, val voiceChannel val minutes = TimeUnit.MILLISECONDS.toMinutes(ms) ms -= TimeUnit.MINUTES.toMillis(minutes) val seconds = TimeUnit.MILLISECONDS.toSeconds(ms) - val current = String.format( + return String.format( "%02d:%02d:%02d:%02d", days, hours, minutes, seconds - ).removePrefix("00:").removePrefix("00:") - return current + ) + } + + fun timeToMillis(time: String): Long { + val times = time.split(':').map { it.toLong() } + return TimeUnit.DAYS.toMillis(times[0]) + TimeUnit.HOURS.toMillis(times[1]) + + TimeUnit.MINUTES.toMillis(times[2]) + TimeUnit.SECONDS.toMillis(times[3]) + + } + + fun genTrackTime(audioTrack: AudioTrack): String { + val total = millisToTime(audioTrack.duration) + val current = millisToTime(audioTrack.position) + return if (current == "00:00:00:00") { + "" + } else { + val t = total.substring(total.lastIndexOf("00:") + 3) + val c = current.substring(current.length - t.length) + "$c/$t" + } + } fun getTrackList(member: Member): MessageEmbed { - return EmbedBuilder().setTitle("Hey ${member.effectiveName}, here is the playlist${if (loop) ", it loops!" else ""}") - .setTimestamp(LocalDateTime.now()).setColor(musicChannel.guild.selfMember.color) - .setFooter("Requested by ${member.effectiveName}", member.user.effectiveAvatarUrl).setDescription( - "**${player.playingTrack.info.title}**, requested by ${if (player.playingTrack.userData is Member) (player.playingTrack.userData as Member).effectiveName else "someone unknown..."} (*now playing*)\n" + - q.joinToString(separator = "\n") { "**${it.info.title}**, requested by ${if (it.userData is Member) (it.userData as Member).effectiveName else "someone unknown..."}" } - ).build() + val eb = + EmbedBuilder().setTitle("Hey ${member.effectiveName}, here is the playlist${if (loop) ", it loops!" else ""}") + .setTimestamp(LocalDateTime.now()).setColor(musicChannel.guild.selfMember.color) + .setFooter("Requested by ${member.effectiveName}", member.user.effectiveAvatarUrl) + .setDescription( + "**${player.playingTrack.info.title}**, requested by ${if (player.playingTrack.userData is Member) (player.playingTrack.userData as Member).effectiveName else "someone unknown..."} (*now playing*)\n" + + q.joinToString(separator = "\n") { "**${it.info.title}**, requested by ${if (it.userData is Member) (it.userData as Member).effectiveName else "someone unknown..."}" } + ) + + return eb.build() } var pressedSkip = 0 @@ -225,5 +248,24 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, val voiceChannel player.stopTrack() } + fun pause() { + player.isPaused = true + } + + fun resume() { + player.isPaused = false + } + + fun isPaused() = player.isPaused + + fun seek(ms: Long): Boolean { + return if (!player.playingTrack.isSeekable) false + else { + player.playingTrack.position = ms + + true + } + } + } diff --git a/src/main/kotlin/nl/voidcorp/dbot/storage/GuildSettings.kt b/src/main/kotlin/nl/voidcorp/dbot/storage/GuildSettings.kt index ced1a46..f3dfcce 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/storage/GuildSettings.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/storage/GuildSettings.kt @@ -8,7 +8,9 @@ import java.time.LocalDateTime data class GuildSettings( val prefixes: MutableList = mutableListOf(), val muted: MutableMap> = mutableMapOf(), - val roleMap: MutableMap = mutableMapOf() + val roleMap: MutableMap = mutableMapOf(), + val adminRoles: MutableSet = mutableSetOf(), + var logChannel: Long? = null ) { fun getPrefixes(): MutableCollection { return prefixes