diff --git a/build.gradle b/build.gradle index bd9428a..716abf7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import org.apache.tools.ant.filters.ReplaceTokens + plugins { id 'java' 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 'org.jetbrains.kotlin.plugin.spring' 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' @@ -40,6 +44,7 @@ dependencies { implementation "com.h2database:h2" implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + kapt 'org.springframework.boot:spring-boot-configuration-processor' implementation "org.springframework:spring-web" implementation "com.fasterxml.jackson.core:jackson-databind" implementation "com.fasterxml.jackson.module:jackson-module-kotlin" diff --git a/src/main/kotlin/nl/voidcorp/discord/Loader.kt b/src/main/kotlin/nl/voidcorp/discord/Loader.kt index adbf09a..1692f1c 100644 --- a/src/main/kotlin/nl/voidcorp/discord/Loader.kt +++ b/src/main/kotlin/nl/voidcorp/discord/Loader.kt @@ -1,24 +1,28 @@ package nl.voidcorp.discord -import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers +import net.dv8tion.jda.api.entities.Activity import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder import nl.voidcorp.discord.events.CommandListener import nl.voidcorp.discord.events.OttoListener +import nl.voidcorp.discord.music.PlayerManager +import nl.voidcorp.discord.storage.ConfigStore import org.springframework.stereotype.Service @Service -class Loader(listener: CommandListener, playerManager: DefaultAudioPlayerManager) { +class Loader(listener: CommandListener, playerManager: PlayerManager, store: ConfigStore, otto: OttoListener) { init { val token = System.getenv("DISCORD_TOKEN") ?: throw RuntimeException("'DISCORD_TOKEN' not set!") val builder = DefaultShardManagerBuilder(token) - - builder.addEventListeners(OttoListener, listener) + builder.addEventListeners(otto, listener) jda = builder.build() + jda.setActivityProvider { + Activity.playing("v${store.version} ($it)") + } AudioSourceManagers.registerRemoteSources(playerManager) } } \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/Main.kt b/src/main/kotlin/nl/voidcorp/discord/Main.kt index ebb899d..7075004 100644 --- a/src/main/kotlin/nl/voidcorp/discord/Main.kt +++ b/src/main/kotlin/nl/voidcorp/discord/Main.kt @@ -1,17 +1,21 @@ package nl.voidcorp.discord import net.dv8tion.jda.api.sharding.ShardManager +import nl.voidcorp.discord.storage.ConfigStore import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication +import java.util.jar.Manifest val logger: Logger = LoggerFactory.getLogger("OttoBot") lateinit var jda: ShardManager -val creator = 168743656738521088 +const val creator = 168743656738521088 @SpringBootApplication +@EnableConfigurationProperties(ConfigStore::class) class SpringApp diff --git a/src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt b/src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt index 4d0ff51..b711ad1 100644 --- a/src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt +++ b/src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt @@ -1,10 +1,11 @@ package nl.voidcorp.discord.commands.music import nl.voidcorp.discord.command.* +import nl.voidcorp.discord.music.PlayerManager import org.springframework.stereotype.Service @Service -class ForceLeave : Command( +class ForceLeave(val playerManager: PlayerManager) : Command( "forceleave", group = CommandGroup.MUSIC, location = CommandSource.GUILD, @@ -12,6 +13,7 @@ class ForceLeave : Command( ) { override fun handle(event: CommandMessage): CommandResult { event.guild!!.audioManager.closeAudioConnection() + playerManager.delGuildPlayer(event.guild) return CommandResult.SUCCESS } } \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/commands/music/Play.kt b/src/main/kotlin/nl/voidcorp/discord/commands/music/Play.kt index 6d671e6..abda119 100644 --- a/src/main/kotlin/nl/voidcorp/discord/commands/music/Play.kt +++ b/src/main/kotlin/nl/voidcorp/discord/commands/music/Play.kt @@ -7,20 +7,27 @@ import org.springframework.stereotype.Service @Service 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 { val chan = event.member!!.voiceState!!.channel if (chan == null) { event.reply("Please join a voice channel to play music!") 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 - am.openAudioConnection(chan) 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 diff --git a/src/main/kotlin/nl/voidcorp/discord/commands/music/Skip.kt b/src/main/kotlin/nl/voidcorp/discord/commands/music/Skip.kt new file mode 100644 index 0000000..9266fb6 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/discord/commands/music/Skip.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/events/OttoListener.kt b/src/main/kotlin/nl/voidcorp/discord/events/OttoListener.kt index 9e8d223..536b20f 100644 --- a/src/main/kotlin/nl/voidcorp/discord/events/OttoListener.kt +++ b/src/main/kotlin/nl/voidcorp/discord/events/OttoListener.kt @@ -1,11 +1,24 @@ 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.ReconnectedEvent import net.dv8tion.jda.api.hooks.ListenerAdapter 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) { 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)") + + } } \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/music/AudioLoadHandler.kt b/src/main/kotlin/nl/voidcorp/discord/music/AudioLoadHandler.kt index a25cfb3..86b2bd2 100644 --- a/src/main/kotlin/nl/voidcorp/discord/music/AudioLoadHandler.kt +++ b/src/main/kotlin/nl/voidcorp/discord/music/AudioLoadHandler.kt @@ -4,7 +4,6 @@ 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 nl.voidcorp.discord.logger class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadResultHandler { @@ -14,7 +13,6 @@ class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadRe } override fun trackLoaded(track: AudioTrack) { - logger.info("loaded track ${track.identifier}") trackScheduler.queue(track) } @@ -23,8 +21,11 @@ class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadRe } override fun playlistLoaded(playlist: AudioPlaylist) { - for (t in playlist.tracks) { - trackScheduler.queue(t) - } + if (playlist.isSearchResult) { + trackScheduler.queue(playlist.selectedTrack ?: playlist.tracks.first()) + } else + for (t in playlist.tracks) { + trackScheduler.queue(t) + } } } diff --git a/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncer.kt b/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncer.kt new file mode 100644 index 0000000..f732da5 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncer.kt @@ -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) + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/music/PlayerManager.kt b/src/main/kotlin/nl/voidcorp/discord/music/PlayerManager.kt index 3771047..2608cbb 100644 --- a/src/main/kotlin/nl/voidcorp/discord/music/PlayerManager.kt +++ b/src/main/kotlin/nl/voidcorp/discord/music/PlayerManager.kt @@ -2,21 +2,42 @@ package nl.voidcorp.discord.music import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager import net.dv8tion.jda.api.entities.Guild +import nl.voidcorp.discord.storage.GuildRepo +import nl.voidcorp.discord.storage.GuildStore import org.springframework.stereotype.Service @Service -class PlayerManager : DefaultAudioPlayerManager() { +class PlayerManager(val repo: GuildRepo) : DefaultAudioPlayerManager() { val guildPlayMap = mutableMapOf() fun getGuildPlayer(guild: Guild): TrackScheduler { return if (guildPlayMap.containsKey(guild.idLong)) { guildPlayMap[guild.idLong] ?: error("oof?") } else { 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) guild.audioManager.sendingHandler = AudioPlayerSendHandler(player) guildPlayMap[guild.idLong] = ts ts } } + + fun delGuildPlayer(guild: Guild) = guildPlayMap.remove(guild.idLong) } \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/music/TrackScheduler.kt b/src/main/kotlin/nl/voidcorp/discord/music/TrackScheduler.kt index 4b25d75..6453ea0 100644 --- a/src/main/kotlin/nl/voidcorp/discord/music/TrackScheduler.kt +++ b/src/main/kotlin/nl/voidcorp/discord/music/TrackScheduler.kt @@ -4,22 +4,40 @@ 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 nl.voidcorp.discord.logger 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() + fun queue(track: AudioTrack) { if (!player.startTrack(track, true)) { 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) { 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) } } } \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/storage/ConfigStore.kt b/src/main/kotlin/nl/voidcorp/discord/storage/ConfigStore.kt new file mode 100644 index 0000000..b2a28e4 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/discord/storage/ConfigStore.kt @@ -0,0 +1,8 @@ +package nl.voidcorp.discord.storage + +import org.springframework.boot.context.properties.ConfigurationProperties + +@ConfigurationProperties("ottobot") +class ConfigStore { + lateinit var version: String +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/storage/GuildRepo.kt b/src/main/kotlin/nl/voidcorp/discord/storage/GuildRepo.kt index 95725ed..5e59c11 100644 --- a/src/main/kotlin/nl/voidcorp/discord/storage/GuildRepo.kt +++ b/src/main/kotlin/nl/voidcorp/discord/storage/GuildRepo.kt @@ -5,5 +5,4 @@ import org.springframework.data.jpa.repository.JpaRepository interface GuildRepo : JpaRepository { fun findByGuildId(id: Long): GuildStore? - fun existsByGuildId(id: Long): Boolean } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 650daf6..3255abc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,3 +7,5 @@ spring.datasource.url=jdbc:h2:mem: spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true + +ottobot.version=1.0 \ No newline at end of file