Remove all music related stuff

This commit is contained in:
Julius 2019-08-29 22:11:26 +02:00
parent 704341bfe3
commit 3f98bdb00e
15 changed files with 2 additions and 751 deletions

View file

@ -1,21 +1,14 @@
package nl.voidcorp.discord package nl.voidcorp.discord
import com.sedmelluq.discord.lavaplayer.source.bandcamp.BandcampAudioSourceManager
import com.sedmelluq.discord.lavaplayer.source.beam.BeamAudioSourceManager
import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager
import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager
import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager
import net.dv8tion.jda.api.entities.Activity import net.dv8tion.jda.api.entities.Activity
import net.dv8tion.jda.api.hooks.ListenerAdapter import net.dv8tion.jda.api.hooks.ListenerAdapter
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder
import nl.voidcorp.discord.music.CustomYoutubeSourceManager
import nl.voidcorp.discord.music.PlayerManager
import nl.voidcorp.discord.storage.ConfigStore import nl.voidcorp.discord.storage.ConfigStore
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class Loader(listeners: List<ListenerAdapter>, playerManager: PlayerManager, store: ConfigStore) { class Loader(listeners: List<ListenerAdapter>, store: ConfigStore) {
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)
@ -27,12 +20,5 @@ class Loader(listeners: List<ListenerAdapter>, playerManager: PlayerManager, sto
jda.setActivityProvider { jda.setActivityProvider {
Activity.playing("v${store.version} ($it)") Activity.playing("v${store.version} ($it)")
} }
playerManager.registerSourceManager(SoundCloudAudioSourceManager())
playerManager.registerSourceManager(BandcampAudioSourceManager())
playerManager.registerSourceManager(VimeoAudioSourceManager())
playerManager.registerSourceManager(TwitchStreamAudioSourceManager())
playerManager.registerSourceManager(BeamAudioSourceManager())
playerManager.registerSourceManager(CustomYoutubeSourceManager())
} }
} }

View file

@ -1,19 +0,0 @@
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(val playerManager: PlayerManager) : Command(
"forceleave",
group = CommandGroup.MUSIC,
location = CommandSource.GUILD,
commandLevel = CommandLevel.MODERATOR
) {
override fun handle(event: CommandMessage): CommandResult {
event.guild!!.audioManager.closeAudioConnection()
playerManager.delGuildPlayer(event.guild)
return CommandResult.SUCCESS
}
}

View file

@ -1,19 +0,0 @@
package nl.voidcorp.discord.commands.music
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.music.PlayerManager
import org.springframework.stereotype.Service
@Service
class Loop(val playerManager: PlayerManager) :
Command("loop", location = CommandSource.GUILD, group = CommandGroup.MUSIC) {
override fun handle(event: CommandMessage): CommandResult {
val loop = playerManager.getGuildPlayer(event.guild!!).loop()
if (loop){
event.reply("Now looping!")
}else{
event.reply("No longer looping...")
}
return CommandResult.SUCCESS
}
}

View file

@ -1,35 +0,0 @@
package nl.voidcorp.discord.commands.music
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.music.AudioLoadHandler
import nl.voidcorp.discord.music.PlayerManager
import org.springframework.stereotype.Service
@Service
class Play(val playerManager: PlayerManager) :
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
val ts = playerManager.getGuildPlayer(event.guild)
if (!ts.playing) {
am.openAudioConnection(chan)
}
playerManager.loadItem(event.params.drop(1).joinToString(" "), AudioLoadHandler(ts))
return CommandResult.SUCCESS
}
}

View file

@ -1,21 +0,0 @@
package nl.voidcorp.discord.commands.music
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.music.PlayerManager
import org.springframework.stereotype.Service
@Service
class PlaylistCommand(val playerManager: PlayerManager) :
Command("playlist", location = CommandSource.GUILD, group = CommandGroup.MUSIC) {
override fun handle(event: CommandMessage): CommandResult {
val player = playerManager.getGuildPlayer(event.guild!!)
val list = player.totalList.take(10)
.mapIndexed { index, audioTrack -> "${index + 1} - ${audioTrack.info.title}" }
.joinToString("\n")
if (list.isNotBlank())
event.reply(list)
else
event.reply("The playlist is still empty...")
return CommandResult.SUCCESS
}
}

View file

@ -1,14 +0,0 @@
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,31 +0,0 @@
package nl.voidcorp.discord.music
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
class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadResultHandler {
override fun loadFailed(exception: FriendlyException) {
throw exception
}
override fun trackLoaded(track: AudioTrack) {
trackScheduler.queue(track)
}
override fun noMatches() {
}
override fun playlistLoaded(playlist: AudioPlaylist) {
if (playlist.isSearchResult) {
trackScheduler.queue(playlist.selectedTrack ?: playlist.tracks.first())
} else
for (t in playlist.tracks) {
trackScheduler.queue(t)
}
}
}

View file

@ -1,22 +0,0 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame
import net.dv8tion.jda.api.audio.AudioSendHandler
import java.nio.ByteBuffer
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() = ByteBuffer.wrap(lastFrame!!.data)
override fun isOpus(): Boolean {
return true
}
}

View file

@ -1,428 +0,0 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeMixProvider
import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeSearchProvider
import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools
import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools.convertToMapLayout
import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools
import com.sedmelluq.discord.lavaplayer.tools.io.HttpConfigurable
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterfaceManager
import com.sedmelluq.discord.lavaplayer.track.*
import org.apache.commons.io.IOUtils
import org.apache.http.client.config.RequestConfig
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.utils.URIBuilder
import org.apache.http.client.utils.URLEncodedUtils
import org.apache.http.impl.client.HttpClientBuilder
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.slf4j.LoggerFactory
import java.io.DataInput
import java.io.DataOutput
import java.io.IOException
import java.net.URISyntaxException
import java.net.URLEncoder
import java.nio.charset.Charset
import java.util.*
import java.util.function.Consumer
import java.util.regex.Pattern
/**
* Audio source manager that implements finding Youtube videos or playlists based on an URL or ID.
*/
class CustomYoutubeSourceManager : YoutubeAudioSourceManager(), AudioSourceManager, HttpConfigurable {
private val extractors = arrayOf(
Extractor(directVideoIdPattern) { id -> loadTrackWithVideoId(id, false) },
Extractor(
Pattern.compile("^$PLAYLIST_ID_REGEX$")
) { id -> loadPlaylistWithId(id, null) },
Extractor(
Pattern.compile("^$PROTOCOL_REGEX$DOMAIN_REGEX/.*")
) {
println(it)
this.loadFromMainDomain(it) },
Extractor(
Pattern.compile("^$PROTOCOL_REGEX$SHORT_DOMAIN_REGEX/.*")
) { this.loadFromShortDomain(it) }
)
private val httpInterfaceManager: HttpInterfaceManager = HttpClientTools.createDefaultThreadLocalManager()
private val searchProvider: YoutubeSearchProvider = YoutubeSearchProvider(this)
private val mixProvider: YoutubeMixProvider = YoutubeMixProvider(this)
override fun getSourceName(): String {
return "youtube"
}
override fun loadItem(manager: DefaultAudioPlayerManager, reference: AudioReference): AudioItem? {
return try {
loadItemOnce(reference)
} catch (exception: FriendlyException) {
// In case of a connection reset exception, try once more.
if (HttpClientTools.isRetriableNetworkException(exception.cause)) {
loadItemOnce(reference)
} else {
throw exception
}
}
}
private fun loadFromShortDomain(identifier: String): AudioItem {
val urlInfo = getUrlInfo(identifier, true)
return loadFromUrlWithVideoId(urlInfo.path.substring(1), urlInfo)
}
private fun loadFromMainDomain(identifier: String): AudioItem? {
val urlInfo = getUrlInfo(identifier, true)
println(urlInfo.parameters)
if ("/watch" == urlInfo.path) {
val videoId = urlInfo.parameters["v"]
if (videoId != null) {
return loadFromUrlWithVideoId(videoId, urlInfo)
}
} else if ("/playlist" == urlInfo.path) {
val playlistId = urlInfo.parameters["list"]
if (playlistId != null) {
return loadPlaylistWithId(playlistId, null)
}
} else if ("/watch_videos" == urlInfo.path) {
val videoIds = urlInfo.parameters["video_ids"]
if (videoIds != null) {
return loadAnonymous(videoIds)
}
}
return null
}
override fun isTrackEncodable(track: AudioTrack): Boolean {
return true
}
override fun encodeTrack(track: AudioTrack, output: DataOutput) {
// No custom values that need saving
}
override fun decodeTrack(trackInfo: AudioTrackInfo, input: DataInput): AudioTrack {
return YoutubeAudioTrack(trackInfo, this)
}
override fun shutdown() {
ExceptionTools.closeWithWarnings(httpInterfaceManager)
mixProvider.shutdown()
}
override fun configureRequests(configurator: java.util.function.Function<RequestConfig, RequestConfig>) {
httpInterfaceManager.configureRequests(configurator)
searchProvider.configureRequests(configurator)
}
override fun configureBuilder(configurator: Consumer<HttpClientBuilder>) {
httpInterfaceManager.configureBuilder(configurator)
searchProvider.configureBuilder(configurator)
}
private fun loadItemOnce(reference: AudioReference): AudioItem? {
return loadNonSearch(reference.identifier) ?: searchProvider.loadSearchResult(
reference.identifier.trim())
}
private fun loadAnonymous(videoIds: String): AudioItem? {
try {
httpInterface.use { httpInterface ->
httpInterface.execute(HttpGet("https://www.youtube.com/watch_videos?video_ids=$videoIds"))
.use { response ->
val statusCode = response.statusLine.statusCode
val context = httpInterface.context
if (statusCode != 200) {
throw IOException("Invalid status code for playlist response: $statusCode")
}
// youtube currently transforms watch_video links into a link with a video id and a list id.
// because thats what happens, we can simply re-process with the redirected link
val redirects = context.redirectLocations
return if (redirects != null && !redirects.isEmpty()) {
loadNonSearch(redirects[0].toString())
} else {
throw FriendlyException(
"Unable to process youtube watch_videos link", FriendlyException.Severity.SUSPICIOUS,
IllegalStateException("Expected youtube to redirect watch_videos link to a watch?v={id}&list={list_id} link, but it did not redirect at all")
)
}
}
}
} catch (e: Exception) {
throw ExceptionTools.wrapUnfriendlyExceptions(e)
}
}
private fun loadFromUrlWithVideoId(videoId: String, urlInfo: UrlInfo): AudioItem {
var videoId = videoId
if (videoId.length > 11) {
// YouTube allows extra junk in the end, it redirects to the correct video.
videoId = videoId.substring(0, 11)
}
if (!directVideoIdPattern.matcher(videoId).matches()) {
return AudioReference.NO_TRACK
} else if (urlInfo.parameters.containsKey("list")) {
val playlistId = urlInfo.parameters["list"]
return if (playlistId!!.startsWith("RD")) {
mixProvider.loadMixWithId(playlistId, videoId)
} else {
loadLinkedPlaylistWithId(urlInfo.parameters.getValue("list"), videoId)
}
} else {
return loadTrackWithVideoId(videoId, false)
}
}
private fun loadNonSearch(identifier: String): AudioItem? {
println(identifier)
for (extractor in extractors) {
if (extractor.pattern.matcher(identifier).matches()) {
val item = extractor.loader(identifier)
if (item != null) {
return item
}
}
}
return null
}
private fun loadLinkedPlaylistWithId(playlistId: String, videoId: String): AudioItem {
val playlist = loadPlaylistWithId(playlistId, videoId)
return playlist ?: loadTrackWithVideoId(videoId, false)
}
private fun determineFailureReasonFromStatus(status: String?, reason: String?, mustExist: Boolean): Boolean {
if ("fail" == status) {
if (("This video does not exist." == reason || "This video is unavailable." == reason) && !mustExist) {
return true
} else if (reason != null) {
throw FriendlyException(reason, FriendlyException.Severity.COMMON, null)
}
} else if ("ok" == status) {
return false
}
throw FriendlyException(
"Track is unavailable for an unknown reason.", FriendlyException.Severity.SUSPICIOUS,
IllegalStateException("Main page had no video, but video info has no error.")
)
}
@Throws(IOException::class)
private fun getTrackInfoFromEmbedPage(httpInterface: HttpInterface, videoId: String): JsonBrowser {
val basicInfo = loadTrackBaseInfoFromEmbedPage(httpInterface, videoId)
basicInfo.put("args", loadTrackArgsFromVideoInfoPage(httpInterface, videoId, basicInfo.get("sts").text()))
return basicInfo
}
@Throws(IOException::class)
private fun loadTrackBaseInfoFromEmbedPage(httpInterface: HttpInterface, videoId: String): JsonBrowser {
httpInterface.execute(HttpGet("https://www.youtube.com/embed/$videoId")).use { response ->
val statusCode = response.statusLine.statusCode
if (statusCode != 200) {
throw IOException("Invalid status code for embed video page response: $statusCode")
}
val html = IOUtils.toString(response.entity.content, Charset.forName(CHARSET))
val configJson = DataFormatTools.extractBetween(html, "'PLAYER_CONFIG': ", "});writeEmbed();")
if (configJson != null) {
return JsonBrowser.parse(configJson)
}
}
throw FriendlyException(
"Track information is unavailable.", FriendlyException.Severity.SUSPICIOUS,
IllegalStateException("Expected player config is not present in embed page.")
)
}
@Throws(IOException::class)
private fun loadTrackArgsFromVideoInfoPage(
httpInterface: HttpInterface,
videoId: String,
sts: String
): Map<String, String> {
val videoApiUrl = "https://youtube.googleapis.com/v/$videoId"
val encodedApiUrl = URLEncoder.encode(videoApiUrl, CHARSET)
val url =
"https://www.youtube.com/get_video_info?sts=" + sts + "&video_id=" + videoId + "&eurl=" + encodedApiUrl +
"hl=en_GB"
httpInterface.execute(HttpGet(url)).use { response ->
val statusCode = response.statusLine.statusCode
if (statusCode != 200) {
throw IOException("Invalid status code for video info response: $statusCode")
}
return convertToMapLayout(URLEncodedUtils.parse(response.entity))
}
}
private fun loadPlaylistWithId(playlistId: String, selectedVideoId: String?): AudioPlaylist? {
log.debug("Starting to load playlist with ID {}", playlistId)
try {
httpInterface.use { httpInterface ->
httpInterface.execute(HttpGet("https://www.youtube.com/playlist?list=$playlistId")).use { response ->
val statusCode = response.statusLine.statusCode
if (statusCode != 200) {
throw IOException("Invalid status code for playlist response: $statusCode")
}
val document = Jsoup.parse(response.entity.content, CHARSET, "")
return buildPlaylist(httpInterface, document, selectedVideoId)
}
}
} catch (e: Exception) {
throw ExceptionTools.wrapUnfriendlyExceptions(e)
}
}
@Throws(IOException::class)
private fun buildPlaylist(
httpInterface: HttpInterface,
document: Document,
selectedVideoId: String?
): AudioPlaylist? {
val isAccessible = !document.select("#pl-header").isEmpty()
if (!isAccessible) {
return if (selectedVideoId != null) {
null
} else {
throw FriendlyException("The playlist is private.", FriendlyException.Severity.COMMON, null)
}
}
val container = document.select("#pl-header").first().parent()
val playlistName = container.select(".pl-header-title").first().text()
val tracks = ArrayList<AudioTrack>()
var loadMoreUrl = extractPlaylistTracks(container, container, tracks)
var loadCount = 0
val pageCount = 6
// Also load the next pages, each result gives us a JSON with separate values for list html and next page loader html
while (loadMoreUrl != null && ++loadCount < pageCount) {
httpInterface.execute(HttpGet("https://www.youtube.com$loadMoreUrl")).use { response ->
val statusCode = response.statusLine.statusCode
if (statusCode != 200) {
throw IOException("Invalid status code for playlist response: $statusCode")
}
val json = JsonBrowser.parse(response.entity.content)
val html = json.get("content_html").text()
val videoContainer = Jsoup.parse("<table>$html</table>", "")
val moreHtml = json.get("load_more_widget_html").text()
val moreContainer = if (moreHtml != null) Jsoup.parse(moreHtml) else null
loadMoreUrl = extractPlaylistTracks(videoContainer, moreContainer, tracks)
}
}
return BasicAudioPlaylist(playlistName, tracks, findSelectedTrack(tracks, selectedVideoId), false)
}
private fun extractPlaylistTracks(
videoContainer: Element,
loadMoreContainer: Element?,
tracks: MutableList<AudioTrack>
): String? {
for (video in videoContainer.select(".pl-video")) {
val lengthElements = video.select(".timestamp span")
// If the timestamp element does not exist, it means the video is private
if (!lengthElements.isEmpty()) {
val videoId = video.attr("data-video-id").trim { it <= ' ' }
val title = video.attr("data-title").trim { it <= ' ' }
val author = video.select(".pl-video-owner a").text().trim { it <= ' ' }
val duration = DataFormatTools.durationTextToMillis(lengthElements.first().text())
tracks.add(buildTrackObject(videoId, title, author, false, duration))
}
}
if (loadMoreContainer != null) {
val more = loadMoreContainer.select(".load-more-button")
if (!more.isEmpty()) {
return more.first().attr("data-uix-load-more-href")
}
}
return null
}
private class UrlInfo constructor(val path: String, val parameters: Map<String, String>)
private class Extractor constructor(
val pattern: Pattern,
val loader: (String) -> AudioItem?
)
companion object {
private val log = LoggerFactory.getLogger(YoutubeAudioSourceManager::class.java)
internal val CHARSET = "UTF-8"
private const val PROTOCOL_REGEX = "(?:http://|https://|)"
private const val DOMAIN_REGEX = "(?:www\\.|m\\.|music\\.|)youtube\\.com"
private const val SHORT_DOMAIN_REGEX = "(?:www\\.|)youtu\\.be"
private const val VIDEO_ID_REGEX = "(?<v>[a-zA-Z0-9_-]{11})"
private const val PLAYLIST_ID_REGEX = "(?<list>(PL|LL|FL|UU)[a-zA-Z0-9_-]+)"
private val directVideoIdPattern = Pattern.compile("^$VIDEO_ID_REGEX$")
private fun getUrlInfo(url: String, retryValidPart: Boolean): UrlInfo {
var url = url
try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "https://$url"
}
val builder = URIBuilder(url)
return UrlInfo(builder.path, builder.queryParams.map { it.name to it.value }.toMap())
} catch (e: URISyntaxException) {
return if (retryValidPart) {
getUrlInfo(url.substring(0, e.index - 1), false)
} else {
throw FriendlyException("Not a valid URL: $url", FriendlyException.Severity.COMMON, e)
}
}
}
}
}

View file

@ -1,23 +0,0 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
import net.dv8tion.jda.api.entities.Guild
import net.dv8tion.jda.api.entities.TextChannel
import org.apache.logging.log4j.LogManager
abstract class MusicAnnouncer(open val channel: TextChannel?, private val guild: Guild) {
private val logger = LogManager.getLogger("Music - ${guild.name}")
fun close() {
guild.audioManager.closeAudioConnection()
}
open fun sendPlayTrack(track: AudioTrack) {
logger.info("Playing ${track.info.title} (${track.identifier})")
}
open fun sendQueueTrack(track: AudioTrack) {
logger.info("Queued ${track.info.title}")
}
}

View file

@ -1,15 +0,0 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
import net.dv8tion.jda.api.entities.Guild
import net.dv8tion.jda.api.entities.TextChannel
class MusicAnnouncerImpl(override val channel: TextChannel, guild: Guild) : MusicAnnouncer(channel, guild) {
override fun sendPlayTrack(track: AudioTrack) {
channel.sendMessage("Now playing ${track.info.title.replace("@here", "@hеre").replace("@everyone", "@еveryone")}").queue()
}
override fun sendQueueTrack(track: AudioTrack) {
channel.sendMessage("Queue'd ${track.info.title.replace("@here", "@hеre").replace("@everyone", "@еveryone")}!").queue()
}
}

View file

@ -1,5 +0,0 @@
package nl.voidcorp.discord.music
import net.dv8tion.jda.api.entities.Guild
class NullMusicAnnouncer(guild: Guild) : MusicAnnouncer(null, guild)

View file

@ -1,48 +0,0 @@
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(val repo: GuildRepo) : DefaultAudioPlayerManager() {
val guildPlayMap = mutableMapOf<Long, TrackScheduler>()
fun getGuildPlayer(guild: Guild): TrackScheduler {
return if (guildPlayMap.containsKey(guild.idLong)) {
guildPlayMap[guild.idLong] ?: error("oof?")
} else {
val player = createPlayer()
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
player.volume = 50
val ts = TrackScheduler(
player,
if (channel != null) MusicAnnouncerImpl(
guild.getTextChannelById(channel)!!,
guild
) else NullMusicAnnouncer(guild)
) {
delGuildPlayer(guild)
}
player.addListener(ts)
guild.audioManager.sendingHandler = AudioPlayerSendHandler(player)
guildPlayMap[guild.idLong] = ts
ts
}
}
fun delGuildPlayer(guild: Guild) = guildPlayMap.remove(guild.idLong)
}

View file

@ -1,55 +0,0 @@
package nl.voidcorp.discord.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 java.util.*
class TrackScheduler(
private val player: AudioPlayer,
private val announcer: MusicAnnouncer,
private val delet: () -> Unit
) :
AudioEventAdapter() {
private val queue = ArrayDeque<AudioTrack>()
private var loop = false
fun queue(track: AudioTrack) {
if (!player.startTrack(track, true)) {
queue.addLast(track)
announcer.sendQueueTrack(track)
}
}
override fun onTrackStart(player: AudioPlayer, track: AudioTrack) {
announcer.sendPlayTrack(track)
}
override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) {
if (loop && endReason.mayStartNext) {
queue.addLast(track.makeClone())
}
if ((endReason.mayStartNext || endReason == AudioTrackEndReason.STOPPED) && queue.isNotEmpty()) {
player.startTrack(queue.pop(), true)
} else if (queue.isEmpty()) {
announcer.close()
delet()
}
}
val playing
get() = player.playingTrack != null
val totalList: List<AudioTrack>
get() = if (player.playingTrack == null) listOf() else mutableListOf(player.playingTrack).apply { addAll(queue) }
fun skip() = player.stopTrack()
fun loop(): Boolean {
loop = !loop
return loop
}
}

View file

@ -8,4 +8,4 @@ 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.7 ottobot.version=1.8