Add begin of music bot features
This commit is contained in:
parent
a3291e02b5
commit
2f9e50fd3e
|
@ -20,6 +20,10 @@ dependencies {
|
||||||
compile "ch.qos.logback:logback-classic:1.2.1"
|
compile "ch.qos.logback:logback-classic:1.2.1"
|
||||||
compile 'io.javalin:javalin:2.3.0'
|
compile 'io.javalin:javalin:2.3.0'
|
||||||
compile 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
compile 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||||
|
compile 'com.jagrosh:jda-utilities:2.1.4'
|
||||||
|
compile 'com.sedmelluq:lavaplayer:1.3.7'
|
||||||
|
compile 'com.sedmelluq:jda-nas:1.0.6'
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,37 @@
|
||||||
package nl.voidcorp.dbot
|
package nl.voidcorp.dbot
|
||||||
|
|
||||||
|
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.youtube.YoutubeAudioSourceManager
|
||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import io.javalin.json.FromJsonMapper
|
import io.javalin.json.FromJsonMapper
|
||||||
import io.javalin.json.JavalinJson
|
import io.javalin.json.JavalinJson
|
||||||
import io.javalin.json.ToJsonMapper
|
import io.javalin.json.ToJsonMapper
|
||||||
import net.dv8tion.jda.core.EmbedBuilder
|
import net.dv8tion.jda.core.EmbedBuilder
|
||||||
|
import net.dv8tion.jda.core.JDA
|
||||||
import net.dv8tion.jda.core.JDABuilder
|
import net.dv8tion.jda.core.JDABuilder
|
||||||
import net.dv8tion.jda.core.OnlineStatus
|
|
||||||
import net.dv8tion.jda.core.entities.Game
|
import net.dv8tion.jda.core.entities.Game
|
||||||
import net.dv8tion.jda.webhook.WebhookClient
|
import net.dv8tion.jda.webhook.WebhookClient
|
||||||
import net.dv8tion.jda.webhook.WebhookClientBuilder
|
import net.dv8tion.jda.webhook.WebhookClientBuilder
|
||||||
|
import nl.voidcorp.dbot.commands.helloCommand
|
||||||
|
import nl.voidcorp.dbot.commands.initMusic
|
||||||
import nl.voidcorp.dbot.storage.GitlabWebhook
|
import nl.voidcorp.dbot.storage.GitlabWebhook
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
|
|
||||||
|
val playerManager = DefaultAudioPlayerManager()
|
||||||
|
|
||||||
|
val cb = CommandClientBuilder()
|
||||||
|
|
||||||
|
val log = LoggerFactory.getLogger("UnityBot")
|
||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val log = LoggerFactory.getLogger("UnityBot")
|
|
||||||
|
playerManager.registerSourceManager(YoutubeAudioSourceManager(true))
|
||||||
|
|
||||||
JavalinJson.apply {
|
JavalinJson.apply {
|
||||||
toJsonMapper = object : ToJsonMapper {
|
toJsonMapper = object : ToJsonMapper {
|
||||||
|
@ -68,6 +83,19 @@ fun main(args: Array<String>) {
|
||||||
ctx.status(200).result("OK")
|
ctx.status(200).result("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
val bot = JDABuilder(args[0]).setStatus(OnlineStatus.ONLINE).setGame(Game.watching("fraud")).addEventListener(Events).build()
|
cb.setOwnerId("168743656738521088")
|
||||||
|
cb.setPrefix("!")
|
||||||
|
cb.setAlternativePrefix("+")
|
||||||
|
cb.addCommand(PingCommand())
|
||||||
|
cb.addCommand(helloCommand)
|
||||||
|
cb.setGame(Game.watching("fraud (and a broken music bot)"))
|
||||||
|
|
||||||
|
|
||||||
|
initMusic()
|
||||||
|
|
||||||
|
|
||||||
|
val client = cb.build()
|
||||||
|
bot = JDABuilder(args[0]).addEventListener(client).addEventListener(nl.voidcorp.dbot.Events).setAudioSendFactory(NativeAudioSendFactory()).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lateinit var bot: JDA
|
||||||
|
|
16
src/main/kotlin/nl/voidcorp/dbot/commands/Commands.kt
Normal file
16
src/main/kotlin/nl/voidcorp/dbot/commands/Commands.kt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package nl.voidcorp.dbot.commands
|
||||||
|
|
||||||
|
import com.jagrosh.jdautilities.command.CommandBuilder
|
||||||
|
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler
|
||||||
|
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException
|
||||||
|
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
|
||||||
|
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
|
||||||
|
import net.dv8tion.jda.core.MessageBuilder
|
||||||
|
import nl.voidcorp.dbot.playerManager
|
||||||
|
|
||||||
|
val helloCommand = CommandBuilder().setName("hello").setHelp("Say hello to Andy!")
|
||||||
|
.setGuildOnly(false)
|
||||||
|
.build { event ->
|
||||||
|
event.reply(MessageBuilder("Hello, ").append(event.author).build())
|
||||||
|
}
|
||||||
|
|
205
src/main/kotlin/nl/voidcorp/dbot/commands/Music.kt
Normal file
205
src/main/kotlin/nl/voidcorp/dbot/commands/Music.kt
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
package nl.voidcorp.dbot.commands
|
||||||
|
|
||||||
|
import com.jagrosh.jdautilities.command.CommandBuilder
|
||||||
|
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.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.time.LocalDateTime
|
||||||
|
|
||||||
|
|
||||||
|
val guildMusicMap = mutableMapOf<Long, TrackScheduler>()
|
||||||
|
|
||||||
|
fun initMusic() {
|
||||||
|
AudioSourceManagers.registerRemoteSources(playerManager)
|
||||||
|
|
||||||
|
|
||||||
|
val queueCommand = CommandBuilder().setName("queue").addAlias("q").setHelp("Queue's a song").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)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun playlistLoaded(playlist: AudioPlaylist) {
|
||||||
|
for (t in playlist.tracks) {
|
||||||
|
scheduler.queue(t, event.member)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val playCommand = CommandBuilder().setName("play").addAlias("p").setHelp("Plays the song instantly").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.insertFront(track, event.member)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun noMatches() {
|
||||||
|
getLinkFromSearch(event, scheduler)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun playlistLoaded(playlist: AudioPlaylist) {
|
||||||
|
for (t in playlist.tracks) {
|
||||||
|
scheduler.insertFront(t, event.member)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val skipCommand = CommandBuilder().setName("skip").addAlias("s").setHelp("Skips the currently playing track").build { event ->
|
||||||
|
val scheduler = guildMusicMap[event.guild.idLong]
|
||||||
|
if (scheduler == null) {
|
||||||
|
event.reply("There is no music playing?")
|
||||||
|
} else {
|
||||||
|
scheduler.skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cb.addCommands(playCommand, skipCommand, queueCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
event.replyError("I could really not find anything for '${event.args}'")
|
||||||
|
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
playerManager.loadItem("ytsearch:${event.args}", object : AudioLoadResultHandler {
|
||||||
|
override fun loadFailed(exception: FriendlyException) {
|
||||||
|
event.reply("Shit's fucked!")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun trackLoaded(track: AudioTrack) {
|
||||||
|
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)
|
||||||
|
.setDescription(r.snippet.description).build()
|
||||||
|
)*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun noMatches() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun playlistLoaded(playlist: AudioPlaylist) {
|
||||||
|
if (!playlist.isSearchResult)
|
||||||
|
for (t in playlist.tracks) {
|
||||||
|
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())
|
||||||
|
|
||||||
|
scheduler.queue(track, event.member)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AudioPlayerSendHandler(private val audioPlayer: AudioPlayer) : AudioSendHandler {
|
||||||
|
private var lastFrame: AudioFrame? = null
|
||||||
|
|
||||||
|
override fun canProvide(): Boolean {
|
||||||
|
lastFrame = audioPlayer.provide()
|
||||||
|
return lastFrame != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun provide20MsAudio(): ByteArray {
|
||||||
|
return lastFrame!!.data
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isOpus(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
109
src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt
Normal file
109
src/main/kotlin/nl/voidcorp/dbot/music/TrackScheduler.kt
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
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.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
|
||||||
|
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.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
|
class TrackScheduler(val player: AudioPlayer, val guild: Guild, channel: VoiceChannel) : AudioEventAdapter() {
|
||||||
|
val musicChannel: TextChannel
|
||||||
|
init {
|
||||||
|
player.addListener(this)
|
||||||
|
|
||||||
|
val audio = guild.audioManager
|
||||||
|
|
||||||
|
audio.sendingHandler = AudioPlayerSendHandler(player)
|
||||||
|
audio.openAudioConnection(channel)
|
||||||
|
|
||||||
|
musicChannel = guild.getTextChannelsByName("music", true).firstOrNull() ?: (guild.getTextChannelsByName("general", true).firstOrNull()
|
||||||
|
?: guild.defaultChannel!!)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val q = ArrayDeque<AudioTrack>()
|
||||||
|
/*override fun onEvent(event: AudioEvent) {
|
||||||
|
if (event is TrackEndEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) {
|
||||||
|
if (q.isNotEmpty()) {
|
||||||
|
player.playTrack(q.remove())
|
||||||
|
} else {
|
||||||
|
stopPlay(endReason)
|
||||||
|
player.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queue(track: AudioTrack, member: Member) {
|
||||||
|
track.userData = member
|
||||||
|
if (q.isEmpty() && player.playingTrack == null) {
|
||||||
|
log.info("Queue is empty, playing a track")
|
||||||
|
player.playTrack(track)
|
||||||
|
} else {
|
||||||
|
log.info("Track Queue'd")
|
||||||
|
q.addLast(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertFront(track: AudioTrack, member: Member) {
|
||||||
|
track.userData = member
|
||||||
|
if (q.isEmpty() && player.playingTrack == null) {
|
||||||
|
log.info("Queue is empty, playing a track")
|
||||||
|
player.playTrack(track)
|
||||||
|
} else {
|
||||||
|
log.info("Track inserted")
|
||||||
|
q.addFirst(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun skip() {
|
||||||
|
if (q.isNotEmpty()) {
|
||||||
|
log.info("skipped Track!")
|
||||||
|
player.playTrack(q.remove())
|
||||||
|
} else {
|
||||||
|
stopPlay(AudioTrackEndReason.REPLACED)
|
||||||
|
player.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isQueueEmpty() = q.isEmpty()
|
||||||
|
|
||||||
|
|
||||||
|
fun stopPlay(endReason: AudioTrackEndReason) {
|
||||||
|
|
||||||
|
if (endReason != AudioTrackEndReason.REPLACED) {
|
||||||
|
guildMusicMap.remove(guild.idLong)
|
||||||
|
musicChannel.sendMessage("Playlist has ended, leaving the voice channel!").queue()
|
||||||
|
thread { guild.audioManager.closeAudioConnection() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue