Add more music commands and clean them up a bit. Add version string to discord presence.

This commit is contained in:
Julius de Jeu 2019-05-28 22:35:24 +02:00
parent faab0c818c
commit 1a0b3a1cd0
14 changed files with 132 additions and 22 deletions

View file

@ -1,3 +1,5 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins { plugins {
id 'java' id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.3.31' id 'org.jetbrains.kotlin.jvm' version '1.3.31'
@ -5,6 +7,8 @@ plugins {
id "io.spring.dependency-management" version "1.0.7.RELEASE" id "io.spring.dependency-management" version "1.0.7.RELEASE"
id 'org.jetbrains.kotlin.plugin.spring' version '1.3.31' id 'org.jetbrains.kotlin.plugin.spring' version '1.3.31'
id 'org.jetbrains.kotlin.plugin.jpa' version '1.3.31' id 'org.jetbrains.kotlin.plugin.jpa' version '1.3.31'
id "org.jetbrains.kotlin.kapt" version "1.3.31"
} }
apply plugin: 'kotlin-jpa' apply plugin: 'kotlin-jpa'
@ -40,6 +44,7 @@ dependencies {
implementation "com.h2database:h2" implementation "com.h2database:h2"
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
kapt 'org.springframework.boot:spring-boot-configuration-processor'
implementation "org.springframework:spring-web" implementation "org.springframework:spring-web"
implementation "com.fasterxml.jackson.core:jackson-databind" implementation "com.fasterxml.jackson.core:jackson-databind"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin" implementation "com.fasterxml.jackson.module:jackson-module-kotlin"

View file

@ -1,24 +1,28 @@
package nl.voidcorp.discord package nl.voidcorp.discord
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers
import net.dv8tion.jda.api.entities.Activity
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder
import nl.voidcorp.discord.events.CommandListener import nl.voidcorp.discord.events.CommandListener
import nl.voidcorp.discord.events.OttoListener import nl.voidcorp.discord.events.OttoListener
import nl.voidcorp.discord.music.PlayerManager
import nl.voidcorp.discord.storage.ConfigStore
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class Loader(listener: CommandListener, playerManager: DefaultAudioPlayerManager) { class Loader(listener: CommandListener, playerManager: PlayerManager, store: ConfigStore, otto: OttoListener) {
init { init {
val token = System.getenv("DISCORD_TOKEN") ?: throw RuntimeException("'DISCORD_TOKEN' not set!") val token = System.getenv("DISCORD_TOKEN") ?: throw RuntimeException("'DISCORD_TOKEN' not set!")
val builder = DefaultShardManagerBuilder(token) val builder = DefaultShardManagerBuilder(token)
builder.addEventListeners(otto, listener)
builder.addEventListeners(OttoListener, listener)
jda = builder.build() jda = builder.build()
jda.setActivityProvider {
Activity.playing("v${store.version} ($it)")
}
AudioSourceManagers.registerRemoteSources(playerManager) AudioSourceManagers.registerRemoteSources(playerManager)
} }
} }

View file

@ -1,17 +1,21 @@
package nl.voidcorp.discord package nl.voidcorp.discord
import net.dv8tion.jda.api.sharding.ShardManager import net.dv8tion.jda.api.sharding.ShardManager
import nl.voidcorp.discord.storage.ConfigStore
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import java.util.jar.Manifest
val logger: Logger = LoggerFactory.getLogger("OttoBot") val logger: Logger = LoggerFactory.getLogger("OttoBot")
lateinit var jda: ShardManager lateinit var jda: ShardManager
val creator = 168743656738521088 const val creator = 168743656738521088
@SpringBootApplication @SpringBootApplication
@EnableConfigurationProperties(ConfigStore::class)
class SpringApp class SpringApp

View file

@ -1,10 +1,11 @@
package nl.voidcorp.discord.commands.music package nl.voidcorp.discord.commands.music
import nl.voidcorp.discord.command.* import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.music.PlayerManager
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class ForceLeave : Command( class ForceLeave(val playerManager: PlayerManager) : Command(
"forceleave", "forceleave",
group = CommandGroup.MUSIC, group = CommandGroup.MUSIC,
location = CommandSource.GUILD, location = CommandSource.GUILD,
@ -12,6 +13,7 @@ class ForceLeave : Command(
) { ) {
override fun handle(event: CommandMessage): CommandResult { override fun handle(event: CommandMessage): CommandResult {
event.guild!!.audioManager.closeAudioConnection() event.guild!!.audioManager.closeAudioConnection()
playerManager.delGuildPlayer(event.guild)
return CommandResult.SUCCESS return CommandResult.SUCCESS
} }
} }

View file

@ -7,20 +7,27 @@ import org.springframework.stereotype.Service
@Service @Service
class Play(val playerManager: PlayerManager) : class Play(val playerManager: PlayerManager) :
Command("play", location = CommandSource.GUILD, group = CommandGroup.MUSIC) { Command("play", location = CommandSource.GUILD, group = CommandGroup.MUSIC, usage = "play song url (or song name prepended with ytsearch:)") {
override fun handle(event: CommandMessage): CommandResult { override fun handle(event: CommandMessage): CommandResult {
val chan = event.member!!.voiceState!!.channel val chan = event.member!!.voiceState!!.channel
if (chan == null) { if (chan == null) {
event.reply("Please join a voice channel to play music!") event.reply("Please join a voice channel to play music!")
return CommandResult.SUCCESS return CommandResult.SUCCESS
} else if (event.params.drop(1).isEmpty()) {
event.reply("I'm going to need a url or a search term to actually find a song...")
return CommandResult.PARAMETERS
} }
val am = event.guild!!.audioManager val am = event.guild!!.audioManager
am.openAudioConnection(chan)
val ts = playerManager.getGuildPlayer(event.guild) val ts = playerManager.getGuildPlayer(event.guild)
playerManager.loadItem("https://www.youtube.com/watch?v=kPgiJUeSP6Q", AudioLoadHandler(ts)) if (!ts.playing) {
am.openAudioConnection(chan)
}
playerManager.loadItem(event.params.drop(1).joinToString(" "), AudioLoadHandler(ts))
return CommandResult.SUCCESS return CommandResult.SUCCESS

View file

@ -0,0 +1,14 @@
package nl.voidcorp.discord.commands.music
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.music.PlayerManager
import org.springframework.stereotype.Service
@Service
class Skip(val playerManager: PlayerManager) :
Command("skip", location = CommandSource.GUILD, group = CommandGroup.MUSIC) {
override fun handle(event: CommandMessage): CommandResult {
playerManager.getGuildPlayer(event.guild!!).skip()
return CommandResult.SUCCESS
}
}

View file

@ -1,11 +1,24 @@
package nl.voidcorp.discord.events package nl.voidcorp.discord.events
import net.dv8tion.jda.api.entities.Activity
import net.dv8tion.jda.api.events.ReadyEvent import net.dv8tion.jda.api.events.ReadyEvent
import net.dv8tion.jda.api.events.ReconnectedEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter import net.dv8tion.jda.api.hooks.ListenerAdapter
import nl.voidcorp.discord.logger import nl.voidcorp.discord.logger
import nl.voidcorp.discord.storage.ConfigStore
import org.springframework.stereotype.Service
object OttoListener : ListenerAdapter() { @Service
class OttoListener(val configStore: ConfigStore) : ListenerAdapter() {
override fun onReady(event: ReadyEvent) { override fun onReady(event: ReadyEvent) {
logger.info("Found ${event.guildTotalCount} different guilds!") logger.info("Found ${event.guildTotalCount} different guilds!")
} }
override fun onReconnect(event: ReconnectedEvent) {
val id = event.jda.shardInfo!!.shardId
val reconn = event.responseNumber
val version = configStore.version
event.jda.presence.activity = Activity.playing("v$version ($id~$reconn)")
}
} }

View file

@ -4,7 +4,6 @@ import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException import com.sedmelluq.discord.lavaplayer.tools.FriendlyException
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrack
import nl.voidcorp.discord.logger
class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadResultHandler { class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadResultHandler {
@ -14,7 +13,6 @@ class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadRe
} }
override fun trackLoaded(track: AudioTrack) { override fun trackLoaded(track: AudioTrack) {
logger.info("loaded track ${track.identifier}")
trackScheduler.queue(track) trackScheduler.queue(track)
} }
@ -23,6 +21,9 @@ class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadRe
} }
override fun playlistLoaded(playlist: AudioPlaylist) { override fun playlistLoaded(playlist: AudioPlaylist) {
if (playlist.isSearchResult) {
trackScheduler.queue(playlist.selectedTrack ?: playlist.tracks.first())
} else
for (t in playlist.tracks) { for (t in playlist.tracks) {
trackScheduler.queue(t) trackScheduler.queue(t)
} }

View file

@ -0,0 +1,12 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
import net.dv8tion.jda.api.entities.TextChannel
import nl.voidcorp.discord.logger
class MusicAnnouncer(val channel: TextChannel) {
fun sendPlayTrack(track: AudioTrack) {
logger.info(track.info.uri)
}
}

View file

@ -2,21 +2,42 @@ package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager
import net.dv8tion.jda.api.entities.Guild import net.dv8tion.jda.api.entities.Guild
import nl.voidcorp.discord.storage.GuildRepo
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class PlayerManager : DefaultAudioPlayerManager() { class PlayerManager(val repo: GuildRepo) : DefaultAudioPlayerManager() {
val guildPlayMap = mutableMapOf<Long, TrackScheduler>() val guildPlayMap = mutableMapOf<Long, TrackScheduler>()
fun getGuildPlayer(guild: Guild): TrackScheduler { fun getGuildPlayer(guild: Guild): TrackScheduler {
return if (guildPlayMap.containsKey(guild.idLong)) { return if (guildPlayMap.containsKey(guild.idLong)) {
guildPlayMap[guild.idLong] ?: error("oof?") guildPlayMap[guild.idLong] ?: error("oof?")
} else { } else {
val player = createPlayer() val player = createPlayer()
val ts = TrackScheduler(player) val store = repo.findByGuildId(guild.idLong) ?: GuildStore(guild.idLong)
val channel = store.musicChannels.firstOrNull()
?: store.botChannels.firstOrNull()
?: guild.textChannels.firstOrNull {
it.name.contains("music", true) && it.name.contains(
"bot",
true
)
}?.idLong
?: guild.textChannels.firstOrNull { it.name.contains("music", true) }?.idLong
?: guild.textChannels.firstOrNull { it.name.contains("bot", true) }?.idLong
?: guild.defaultChannel!!.idLong
player.volume = 50
val ts = TrackScheduler(player, MusicAnnouncer(guild.getTextChannelById(channel)!!)) {
delGuildPlayer(guild)
}
player.addListener(ts) player.addListener(ts)
guild.audioManager.sendingHandler = AudioPlayerSendHandler(player) guild.audioManager.sendingHandler = AudioPlayerSendHandler(player)
guildPlayMap[guild.idLong] = ts guildPlayMap[guild.idLong] = ts
ts ts
} }
} }
fun delGuildPlayer(guild: Guild) = guildPlayMap.remove(guild.idLong)
} }

View file

@ -4,22 +4,40 @@ import com.sedmelluq.discord.lavaplayer.player.AudioPlayer
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter
import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrack
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason
import nl.voidcorp.discord.logger
import java.util.* import java.util.*
class TrackScheduler(private val player: AudioPlayer) : AudioEventAdapter() { class TrackScheduler(private val player: AudioPlayer, private val announcer: MusicAnnouncer, val delet: () -> Unit) :
AudioEventAdapter() {
private val queue = ArrayDeque<AudioTrack>() private val queue = ArrayDeque<AudioTrack>()
fun queue(track: AudioTrack) { fun queue(track: AudioTrack) {
if (!player.startTrack(track, true)) { if (!player.startTrack(track, true)) {
queue.addLast(track) queue.addLast(track)
} }
} }
fun pop() = queue.pop() override fun onTrackStart(player: AudioPlayer, track: AudioTrack) {
announcer.sendPlayTrack(track)
}
override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) { override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) {
if (endReason.mayStartNext && queue.isNotEmpty()) { if (endReason.mayStartNext && queue.isNotEmpty()) {
player.startTrack(pop(), true) player.startTrack(queue.pop(), true)
} else if (queue.isEmpty()) {
announcer.channel.guild.audioManager.closeAudioConnection()
delet()
}
}
val playing
get() = player.playingTrack != null
fun skip() {
if (queue.isEmpty()) {
player.stopTrack()
} else {
player.startTrack(queue.pop(), false)
} }
} }
} }

View file

@ -0,0 +1,8 @@
package nl.voidcorp.discord.storage
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("ottobot")
class ConfigStore {
lateinit var version: String
}

View file

@ -5,5 +5,4 @@ import org.springframework.data.jpa.repository.JpaRepository
interface GuildRepo : JpaRepository<GuildStore, Long> { interface GuildRepo : JpaRepository<GuildStore, Long> {
fun findByGuildId(id: Long): GuildStore? fun findByGuildId(id: Long): GuildStore?
fun existsByGuildId(id: Long): Boolean
} }

View file

@ -7,3 +7,5 @@ spring.datasource.url=jdbc:h2:mem:
spring.jpa.generate-ddl=true spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
ottobot.version=1.0