diff --git a/build.gradle b/build.gradle index 193df7c..179a317 100644 --- a/build.gradle +++ b/build.gradle @@ -23,8 +23,7 @@ dependencies { compile 'com.jagrosh:jda-utilities:2.1.4' compile 'com.sedmelluq:lavaplayer:1.3.7' compile 'com.sedmelluq:jda-nas:1.0.6' - - + compile 'khttp:khttp:0.1.0' } compileKotlin { diff --git a/src/main/kotlin/nl/voidcorp/dbot/Events.kt b/src/main/kotlin/nl/voidcorp/dbot/Events.kt index 60ffd4c..20f9531 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/Events.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/Events.kt @@ -13,7 +13,7 @@ object Events : ListenerAdapter() { event.message.addReaction(e).queue() } if (event.message.channel.idLong == 499628388659625995) { - if (event.message.mentionedMembers.contains(event.guild.getMember(event.jda.selfUser))) { + if (event.message.mentionedMembers.contains(event.guild.getMember(event.jda.selfUser)) and (event.message.author != event.jda.selfUser)) { if (event.message.contentStripped.toLowerCase().contains("hello")) { val i = random.nextInt(10) diff --git a/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt b/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt index 9a07a52..7033254 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/UnityBot.kt @@ -1,9 +1,11 @@ package nl.voidcorp.dbot +import com.jagrosh.jdautilities.command.CommandClient import com.jagrosh.jdautilities.command.CommandClientBuilder import com.jagrosh.jdautilities.examples.command.PingCommand import com.sedmelluq.discord.lavaplayer.jdaudp.NativeAudioSendFactory import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager +import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager import io.javalin.Javalin import io.javalin.json.FromJsonMapper @@ -12,6 +14,7 @@ import io.javalin.json.ToJsonMapper import net.dv8tion.jda.core.EmbedBuilder import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.JDABuilder +import net.dv8tion.jda.core.MessageBuilder import net.dv8tion.jda.core.entities.Game import net.dv8tion.jda.webhook.WebhookClient import net.dv8tion.jda.webhook.WebhookClientBuilder @@ -29,9 +32,11 @@ val cb = CommandClientBuilder() val log = LoggerFactory.getLogger("UnityBot") +@Suppress("JoinDeclarationAndAssignment") fun main(args: Array) { playerManager.registerSourceManager(YoutubeAudioSourceManager(true)) + playerManager.registerSourceManager(SoundCloudAudioSourceManager(true)) JavalinJson.apply { toJsonMapper = object : ToJsonMapper { @@ -83,18 +88,30 @@ fun main(args: Array) { ctx.status(200).result("OK") } + lateinit var client: CommandClient cb.setOwnerId("168743656738521088") cb.setPrefix("!") cb.setAlternativePrefix("+") cb.addCommand(PingCommand()) cb.addCommand(helloCommand) - cb.setGame(Game.watching("fraud (and a broken music bot)")) + cb.setGame(Game.watching("fraud and \uD83C\uDFB5")) + cb.setHelpConsumer { event -> + event.reply(MessageBuilder("Help for ${event.selfMember.asMention}\n").append { + var st = "" + for (c in client.commands) { + st += "`${c.name}`: ${c.help}${if (c.aliases.isNotEmpty()) " (alias: `${c.aliases.first()}`)" else ""}\n" + } + st + }.build() + ) + } initMusic() - val client = cb.build() + client = cb.build() + bot = JDABuilder(args[0]).addEventListener(client).addEventListener(nl.voidcorp.dbot.Events).setAudioSendFactory(NativeAudioSendFactory()).build() } diff --git a/src/main/kotlin/nl/voidcorp/dbot/commands/Music.kt b/src/main/kotlin/nl/voidcorp/dbot/commands/Music.kt index 49d3609..6802bf4 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/commands/Music.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/commands/Music.kt @@ -5,17 +5,19 @@ import com.jagrosh.jdautilities.command.CommandEvent import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler import com.sedmelluq.discord.lavaplayer.player.AudioPlayer import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers +import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager +import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioTrack import com.sedmelluq.discord.lavaplayer.tools.FriendlyException import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame import net.dv8tion.jda.core.EmbedBuilder import net.dv8tion.jda.core.audio.AudioSendHandler -import net.dv8tion.jda.core.entities.Member import nl.voidcorp.dbot.cb import nl.voidcorp.dbot.log import nl.voidcorp.dbot.music.TrackScheduler import nl.voidcorp.dbot.playerManager +import java.awt.Color import java.time.LocalDateTime @@ -51,7 +53,49 @@ fun initMusic() { } override fun noMatches() { - getLinkFromSearch(event, scheduler) + getLinkFromSearch(event, scheduler, false) + } + + override fun playlistLoaded(playlist: AudioPlaylist) { + for (t in playlist.tracks) { + scheduler.queue(t, event.member) + } + + + } + + }) + } + + val ytCommand = CommandBuilder().setName("youtube").addAlias("yt").setHelp("Play a song from YouTube!").build { event -> + queueCommand.run(event) + } + + val soundcloudCommand = CommandBuilder().setName("soundcloud").addAlias("sc").setHelp("Play a song from SoundCloud!").build { event -> + val scheduler = if (guildMusicMap.containsKey(event.guild.idLong)) guildMusicMap[event.guild.idLong]!! else { + val channel = event.guild.voiceChannels.firstOrNull { it.members.contains(event.member) } + if (channel == null) { + event.reply("Join a voice Channel please!") + return@build + } + val s = TrackScheduler(playerManager.createPlayer(), event.guild, channel) + guildMusicMap[event.guild.idLong] = s + s + } + + + + playerManager.loadItem(event.args, object : AudioLoadResultHandler { + override fun loadFailed(exception: FriendlyException) { + event.reply("Shit's fucked!") + } + + override fun trackLoaded(track: AudioTrack) { + scheduler.queue(track, event.member) + } + + override fun noMatches() { + getLinkFromSearch(event, scheduler, false, "scsearch") } override fun playlistLoaded(playlist: AudioPlaylist) { @@ -87,7 +131,7 @@ fun initMusic() { } override fun noMatches() { - getLinkFromSearch(event, scheduler) + getLinkFromSearch(event, scheduler, true) } override fun playlistLoaded(playlist: AudioPlaylist) { @@ -101,6 +145,7 @@ fun initMusic() { }) } + val skipCommand = CommandBuilder().setName("skip").addAlias("s").setHelp("Skips the currently playing track").build { event -> val scheduler = guildMusicMap[event.guild.idLong] if (scheduler == null) { @@ -112,42 +157,22 @@ fun initMusic() { } - cb.addCommands(playCommand, skipCommand, queueCommand) + cb.addCommands(playCommand, skipCommand, queueCommand, ytCommand, soundcloudCommand) } -fun getLinkFromSearch(event: CommandEvent, scheduler: TrackScheduler) { - log.info("Searching for '${event.args}'") - /*val search = yt.search().list("id,snippet") - search.key = "AIzaSyDiEYrQNT-eDRZKy6JvQHBDymttwaLd7Mg" - search.q = event.args - search.type = "video" - search.fields = "items(id/kind,id/videoId,snippet/title,snippet/thumbnails/high/url,snippet/description,snippet/channelTitle)" - search.maxResults = 10 - val res = search.execute() - if (res.isEmpty()) { - event.reply("Really nothing was found...") - } else { - var found = false - for (r in res.items) { - if (r.id.kind == "youtube#video" && !found) { - found = true - val id = r.id.videoId +fun getLinkFromSearch(event: CommandEvent, scheduler: TrackScheduler, shouldInsertFront: Boolean, searchPrefix: String = "ytsearch") { + log.info("Searching youtube for '${event.args}'") - } - } - if (!found) { - event.replyError("I could really not find anything for '${event.args}'") - - } - }*/ - - playerManager.loadItem("ytsearch:${event.args}", object : AudioLoadResultHandler { + playerManager.loadItem("$searchPrefix:${event.args}", object : AudioLoadResultHandler { override fun loadFailed(exception: FriendlyException) { event.reply("Shit's fucked!") } override fun trackLoaded(track: AudioTrack) { - scheduler.queue(track, event.member) + if (shouldInsertFront) + scheduler.insertFront(track, event.member) + else + scheduler.queue(track, event.member) /*event.reply( EmbedBuilder().setImage(r.snippet.thumbnails.high.url).setTitle(r.snippet.title, "https://youtu.be/$id") .setAuthor(r.snippet.channelTitle).setFooter("Requested by ${event.member.effectiveName}", event.author.effectiveAvatarUrl) @@ -163,19 +188,41 @@ fun getLinkFromSearch(event: CommandEvent, scheduler: TrackScheduler) { override fun playlistLoaded(playlist: AudioPlaylist) { if (!playlist.isSearchResult) for (t in playlist.tracks) { - scheduler.queue(t, event.member) + if (shouldInsertFront) + scheduler.insertFront(t, event.member) + else + scheduler.queue(t, event.member) } else { val track = playlist.tracks.first() - event.reply(EmbedBuilder() - .setFooter("Requested by ${event.member.effectiveName}", event.member.user.effectiveAvatarUrl) - .setAuthor(track.info.author).setTitle(track.info.title, track.info.uri) - .setThumbnail("https://img.youtube.com/vi/${track.info.identifier}/hqdefault.jpg\n") - .setTimestamp(LocalDateTime.now()).build()) + if (!scheduler.isQueueEmpty() or scheduler.isSongPlaying()) { - scheduler.queue(track, event.member) + if (track is SoundCloudAudioTrack) { + val scsm = track.sourceManager as SoundCloudAudioSourceManager + scsm.updateClientId() + val art = khttp.get("http://api.soundcloud.com/tracks/${track.info.identifier}?client_id=${scsm.clientId}", + headers = mapOf("Content-Type" to "application/json")).jsonObject["artwork_url"].toString().replace("large", "t300x300") + event.reply(EmbedBuilder() + .setFooter("Requested by ${event.member.effectiveName}", event.member.user.effectiveAvatarUrl) + .setAuthor(track.info.author).setTitle(track.info.title, track.info.uri) + .setThumbnail(art) + .setTimestamp(LocalDateTime.now()).setColor(Color.decode("#ff8800")).build()) + } else { + event.reply(EmbedBuilder() + .setFooter("Requested by ${event.member.effectiveName}", event.member.user.effectiveAvatarUrl) + .setAuthor(track.info.author).setTitle(track.info.title, track.info.uri) + .setThumbnail("https://img.youtube.com/vi/${track.info.identifier}/hqdefault.jpg") + .setTimestamp(LocalDateTime.now()).setColor(Color.decode("#ff0000")).build()) + } + + } + + if (shouldInsertFront) + scheduler.insertFront(track, event.member) + else + scheduler.queue(track, event.member) } diff --git a/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt b/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt index 4a66b48..3f6893a 100644 --- a/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt +++ b/src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt @@ -2,10 +2,12 @@ package nl.voidcorp.dbot.music import com.sedmelluq.discord.lavaplayer.player.AudioPlayer import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter +import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager +import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioTrack +import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason import net.dv8tion.jda.core.EmbedBuilder -import net.dv8tion.jda.core.MessageBuilder import net.dv8tion.jda.core.entities.Guild import net.dv8tion.jda.core.entities.Member import net.dv8tion.jda.core.entities.TextChannel @@ -13,6 +15,7 @@ import net.dv8tion.jda.core.entities.VoiceChannel import nl.voidcorp.dbot.commands.AudioPlayerSendHandler import nl.voidcorp.dbot.commands.guildMusicMap import nl.voidcorp.dbot.log +import java.awt.Color import java.time.LocalDateTime import java.util.* import kotlin.concurrent.thread @@ -20,6 +23,7 @@ import kotlin.concurrent.thread class TrackScheduler(val player: AudioPlayer, val guild: Guild, channel: VoiceChannel) : AudioEventAdapter() { val musicChannel: TextChannel + init { player.addListener(this) @@ -42,7 +46,7 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, channel: VoiceCh override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) { if (q.isNotEmpty()) { - player.playTrack(q.remove()) + player.playTrack(q.poll()) } else { stopPlay(endReason) player.destroy() @@ -50,12 +54,27 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, channel: VoiceCh } override fun onTrackStart(player: AudioPlayer, track: AudioTrack) { - if (track.userData is Member) - musicChannel.sendMessage(EmbedBuilder() - .setFooter("Requested by ${(track.userData as Member).effectiveName}", (track.userData as Member) - .user.effectiveAvatarUrl).setAuthor(track.info.author).setTitle(track.info.title, track.info.uri) - .setThumbnail("https://img.youtube.com/vi/${track.info.identifier}/hqdefault.jpg\n") - .setTimestamp(LocalDateTime.now()).build()).append("Now playing").queue() + if (track is YoutubeAudioTrack) { + if (track.userData is Member) + musicChannel.sendMessage(EmbedBuilder() + .setFooter("Requested by ${(track.userData as Member).effectiveName}", (track.userData as Member) + .user.effectiveAvatarUrl).setAuthor(track.info.author).setTitle(track.info.title, track.info.uri) + .setThumbnail("https://img.youtube.com/vi/${track.info.identifier}/hqdefault.jpg") + .setTimestamp(LocalDateTime.now()).setColor(Color.decode("#ff0000")).build()).append("Now playing").queue() + } else if (track is SoundCloudAudioTrack) { + val scsm = track.sourceManager as SoundCloudAudioSourceManager + scsm.updateClientId() + val art = khttp.get("http://api.soundcloud.com/tracks/${track.info.identifier}?client_id=${scsm.clientId}", headers = mapOf("Content-Type" to "application/json")).jsonObject["artwork_url"].toString().replace("large", "t300x300") + + if (track.userData is Member) + musicChannel.sendMessage(EmbedBuilder() + .setFooter("Requested by ${(track.userData as Member).effectiveName}", (track.userData as Member) + .user.effectiveAvatarUrl).setAuthor(track.info.author).setTitle(track.info.title, track.info.uri) + .setThumbnail(art) + .setTimestamp(LocalDateTime.now()).setColor(Color.decode("#ff8800")).build()).append("Now playing").queue() + + + } } @@ -67,7 +86,7 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, channel: VoiceCh player.playTrack(track) } else { log.info("Track Queue'd") - q.addLast(track) + q.offer(track) } } @@ -78,29 +97,31 @@ class TrackScheduler(val player: AudioPlayer, val guild: Guild, channel: VoiceCh player.playTrack(track) } else { log.info("Track inserted") - q.addFirst(track) + q.push(track) } } fun skip() { - if (q.isNotEmpty()) { + /*if (q.isNotEmpty()) { log.info("skipped Track!") player.playTrack(q.remove()) } else { stopPlay(AudioTrackEndReason.REPLACED) player.destroy() - } + }*/ + player.stopTrack() } fun isQueueEmpty() = q.isEmpty() + fun isSongPlaying() = player.playingTrack != null fun stopPlay(endReason: AudioTrackEndReason) { if (endReason != AudioTrackEndReason.REPLACED) { guildMusicMap.remove(guild.idLong) - musicChannel.sendMessage("Playlist has ended, leaving the voice channel!").queue() + musicChannel.sendMessage(EmbedBuilder().setColor(musicChannel.guild.selfMember.color).setTitle("I'm done here").setDescription("There are no more songs left in the queue.").setTimestamp(LocalDateTime.now()).build()).queue() thread { guild.audioManager.closeAudioConnection() } } }