From 3f98bdb00e930ac76433d812843b104a8cbaf2be Mon Sep 17 00:00:00 2001 From: Julius de Jeu Date: Thu, 29 Aug 2019 22:11:26 +0200 Subject: [PATCH] Remove all music related stuff --- src/main/kotlin/nl/voidcorp/discord/Loader.kt | 16 +- .../discord/commands/music/ForceLeave.kt | 19 - .../voidcorp/discord/commands/music/Loop.kt | 19 - .../voidcorp/discord/commands/music/Play.kt | 35 -- .../discord/commands/music/PlaylistCommand.kt | 21 - .../voidcorp/discord/commands/music/Skip.kt | 14 - .../discord/music/AudioLoadHandler.kt | 31 -- .../discord/music/AudioPlayerSendHandler.kt | 22 - .../music/CustomYoutubeSourceManager.kt | 428 ------------------ .../voidcorp/discord/music/MusicAnnouncer.kt | 23 - .../discord/music/MusicAnnouncerImpl.kt | 15 - .../discord/music/NullMusicAnnouncer.kt | 5 - .../voidcorp/discord/music/PlayerManager.kt | 48 -- .../voidcorp/discord/music/TrackScheduler.kt | 55 --- src/main/resources/application.properties | 2 +- 15 files changed, 2 insertions(+), 751 deletions(-) delete mode 100644 src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/commands/music/Loop.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/commands/music/Play.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/commands/music/PlaylistCommand.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/commands/music/Skip.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/AudioLoadHandler.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/AudioPlayerSendHandler.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/CustomYoutubeSourceManager.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncer.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncerImpl.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/NullMusicAnnouncer.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/PlayerManager.kt delete mode 100644 src/main/kotlin/nl/voidcorp/discord/music/TrackScheduler.kt diff --git a/src/main/kotlin/nl/voidcorp/discord/Loader.kt b/src/main/kotlin/nl/voidcorp/discord/Loader.kt index 867cbf6..8524a57 100644 --- a/src/main/kotlin/nl/voidcorp/discord/Loader.kt +++ b/src/main/kotlin/nl/voidcorp/discord/Loader.kt @@ -1,21 +1,14 @@ 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.hooks.ListenerAdapter 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 org.springframework.stereotype.Service @Service -class Loader(listeners: List, playerManager: PlayerManager, store: ConfigStore) { +class Loader(listeners: List, store: ConfigStore) { init { val token = System.getenv("DISCORD_TOKEN") ?: throw RuntimeException("'DISCORD_TOKEN' not set!") val builder = DefaultShardManagerBuilder(token) @@ -27,12 +20,5 @@ class Loader(listeners: List, playerManager: PlayerManager, sto jda.setActivityProvider { Activity.playing("v${store.version} ($it)") } - - playerManager.registerSourceManager(SoundCloudAudioSourceManager()) - playerManager.registerSourceManager(BandcampAudioSourceManager()) - playerManager.registerSourceManager(VimeoAudioSourceManager()) - playerManager.registerSourceManager(TwitchStreamAudioSourceManager()) - playerManager.registerSourceManager(BeamAudioSourceManager()) - playerManager.registerSourceManager(CustomYoutubeSourceManager()) } } \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt b/src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt deleted file mode 100644 index b711ad1..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/commands/music/ForceLeave.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/commands/music/Loop.kt b/src/main/kotlin/nl/voidcorp/discord/commands/music/Loop.kt deleted file mode 100644 index 87d19b8..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/commands/music/Loop.kt +++ /dev/null @@ -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 - } -} \ 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 deleted file mode 100644 index abda119..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/commands/music/Play.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/commands/music/PlaylistCommand.kt b/src/main/kotlin/nl/voidcorp/discord/commands/music/PlaylistCommand.kt deleted file mode 100644 index 99b6d2c..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/commands/music/PlaylistCommand.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/commands/music/Skip.kt b/src/main/kotlin/nl/voidcorp/discord/commands/music/Skip.kt deleted file mode 100644 index 9266fb6..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/commands/music/Skip.kt +++ /dev/null @@ -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 - } -} \ 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 deleted file mode 100644 index 86b2bd2..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/AudioLoadHandler.kt +++ /dev/null @@ -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) - } - } -} diff --git a/src/main/kotlin/nl/voidcorp/discord/music/AudioPlayerSendHandler.kt b/src/main/kotlin/nl/voidcorp/discord/music/AudioPlayerSendHandler.kt deleted file mode 100644 index 81d455e..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/AudioPlayerSendHandler.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/music/CustomYoutubeSourceManager.kt b/src/main/kotlin/nl/voidcorp/discord/music/CustomYoutubeSourceManager.kt deleted file mode 100644 index 661c2c4..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/CustomYoutubeSourceManager.kt +++ /dev/null @@ -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) { - httpInterfaceManager.configureRequests(configurator) - searchProvider.configureRequests(configurator) - } - - override fun configureBuilder(configurator: Consumer) { - 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 { - 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() - 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("$html
", "") - - 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 - ): 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) - - 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 = "(?[a-zA-Z0-9_-]{11})" - private const val PLAYLIST_ID_REGEX = "(?(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) - } - } - - } - - } -} diff --git a/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncer.kt b/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncer.kt deleted file mode 100644 index 45f0425..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncer.kt +++ /dev/null @@ -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}") - } -} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncerImpl.kt b/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncerImpl.kt deleted file mode 100644 index 256ee6e..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/MusicAnnouncerImpl.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/discord/music/NullMusicAnnouncer.kt b/src/main/kotlin/nl/voidcorp/discord/music/NullMusicAnnouncer.kt deleted file mode 100644 index 22435e1..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/NullMusicAnnouncer.kt +++ /dev/null @@ -1,5 +0,0 @@ -package nl.voidcorp.discord.music - -import net.dv8tion.jda.api.entities.Guild - -class NullMusicAnnouncer(guild: Guild) : MusicAnnouncer(null, guild) \ 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 deleted file mode 100644 index 2c3c3e9..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/PlayerManager.kt +++ /dev/null @@ -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() - 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) -} \ 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 deleted file mode 100644 index 7fc829c..0000000 --- a/src/main/kotlin/nl/voidcorp/discord/music/TrackScheduler.kt +++ /dev/null @@ -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() - - 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 - get() = if (player.playingTrack == null) listOf() else mutableListOf(player.playingTrack).apply { addAll(queue) } - - fun skip() = player.stopTrack() - - fun loop(): Boolean { - loop = !loop - return loop - } -} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4a56d81..a738684 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,4 +8,4 @@ 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.7 \ No newline at end of file +ottobot.version=1.8 \ No newline at end of file