Skip to content

Commit

Permalink
Fixed #401 Can't open playlist, caused by YouTube Changing API
Browse files Browse the repository at this point in the history
  • Loading branch information
maxrave-dev committed Jun 23, 2024
1 parent 78b5408 commit c87312b
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 39 deletions.
4 changes: 2 additions & 2 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.maxrave.simpmusic.data.parser
import android.content.Context
import android.util.Log
import com.maxrave.kotlinytmusicscraper.models.MusicShelfRenderer
import com.maxrave.kotlinytmusicscraper.models.SectionListRenderer
import com.maxrave.kotlinytmusicscraper.models.response.BrowseResponse
import com.maxrave.kotlinytmusicscraper.models.response.SearchResponse
import com.maxrave.kotlinytmusicscraper.pages.PodcastItem
Expand Down Expand Up @@ -72,6 +73,27 @@ fun parsePlaylistData(
header.header.musicDetailHeaderRenderer.thumbnail.croppedSquareThumbnailRenderer?.thumbnail?.thumbnails?.toListThumbnail()
?.let { listThumbnails.addAll(it) }
}
else if (header is SectionListRenderer.Content.MusicResponsiveHeaderRenderer?) {
title += header.title?.runs?.firstOrNull()?.text
Log.d("PlaylistParser", "title: $title")
val author = Author(id = header.straplineTextOne?.runs?.get(0)?.navigationEndpoint?.browseEndpoint?.browseId ?: "", name = header.straplineTextOne?.runs?.get(0)?.text ?: "")
listAuthor.add(author)
Log.d("PlaylistParser", "author: $author")
if (header.secondSubtitle?.runs?.size!! > 4) {
duration += header.secondSubtitle?.runs?.get(4)?.text
}
else if (header.secondSubtitle?.runs?.size!! == 3) {
duration += header.secondSubtitle?.runs?.get(2)?.text
}
Log.d("PlaylistParser", "duration: $duration")
if (!header.description?.musicDescriptionShelfRenderer?.description?.runs.isNullOrEmpty()) {
for (run in header.description?.musicDescriptionShelfRenderer?.description?.runs!!) {
description += (run.text)
}
}
header.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.toListThumbnail()
?.let { listThumbnails.addAll(it) }
}
Log.d("PlaylistParser", "description: $description")
val listTrack: MutableList<Track> = arrayListOf()
for (content in listContent){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,9 @@ class MainRepository
)?.tabRenderer?.content?.sectionListRenderer?.contents?.get(
0,
)?.musicPlaylistShelfRenderer?.contents
?: result.contents?.twoColumnBrowseResultsRenderer
?.secondaryContents?.sectionListRenderer?.contents?.get(0)
?.musicPlaylistShelfRenderer?.contents
if (data != null) {
Log.d("Data", "data: $data")
Log.d("Data", "data size: ${data.size}")
Expand All @@ -1099,6 +1102,9 @@ class MainRepository
val header =
result.header?.musicDetailHeaderRenderer
?: result.header?.musicEditablePlaylistDetailHeaderRenderer
?: result.contents?.twoColumnBrowseResultsRenderer?.tabs?.get(0)
?.tabRenderer?.content?.sectionListRenderer?.contents?.get(0)
?.musicResponsiveHeaderRenderer
Log.d("Header", "header: $header")
var continueParam =
result.contents?.singleColumnBrowseResultsRenderer?.tabs?.get(
Expand All @@ -1107,11 +1113,15 @@ class MainRepository
0,
)?.musicPlaylistShelfRenderer?.continuations?.get(
0,
)?.nextContinuationData?.continuation
)?.nextContinuationData?.continuation ?:
result.contents?.twoColumnBrowseResultsRenderer?.secondaryContents?.sectionListRenderer?.contents?.firstOrNull()
?.musicPlaylistShelfRenderer?.continuations?.firstOrNull()?.nextContinuationData?.continuation
var count = 0
Log.d("Repository", "playlist data: ${listContent.size}")
Log.d("Repository", "continueParam: $continueParam")
while (continueParam != null) {
// else {
// var listTrack = playlistBrowse.tracks.toMutableList()
while (continueParam != null) {
YouTube.customQuery(
browseId = "",
continuation = continueParam,
Expand Down Expand Up @@ -1139,6 +1149,7 @@ class MainRepository
emit(Resource.Success<PlaylistBrowse>(playlist))
} ?: emit(Resource.Error<PlaylistBrowse>("Error"))
}.onFailure { e ->
Log.e("Playlist Data", e.message ?: "Error")
emit(Resource.Error<PlaylistBrowse>(e.message.toString()))
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx4608m
org.gradle.jvmargs=-Xmx4g
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
android = "8.4.2"
android = "8.5.0"
kotlin = "1.9.23"
serialization = "1.9.23"
ksp = "1.9.23-1.0.19"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -917,57 +917,82 @@ object YouTube {
endpoint.params,
continuation,
).body<NextResponse>()
Log.w("YouTube", response.toString())
val playlistPanelRenderer =
response.continuationContents?.playlistPanelContinuation
?: response.contents.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer!!
// load automix items
if (playlistPanelRenderer.contents.lastOrNull()?.automixPreviewVideoRenderer?.content?.automixPlaylistVideoRenderer?.navigationEndpoint?.watchPlaylistEndpoint != null) {
return@runCatching next(
playlistPanelRenderer.contents.lastOrNull()?.automixPreviewVideoRenderer?.content?.automixPlaylistVideoRenderer?.navigationEndpoint?.watchPlaylistEndpoint!!,
).getOrThrow()
.let { result ->
result.copy(
title = playlistPanelRenderer.title,
items =
?: response.contents.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer
if (playlistPanelRenderer != null) {
// load automix items
if (playlistPanelRenderer.contents.lastOrNull()?.automixPreviewVideoRenderer?.content?.automixPlaylistVideoRenderer?.navigationEndpoint?.watchPlaylistEndpoint != null) {
return@runCatching next(
playlistPanelRenderer.contents.lastOrNull()?.automixPreviewVideoRenderer?.content?.automixPlaylistVideoRenderer?.navigationEndpoint?.watchPlaylistEndpoint!!,
).getOrThrow()
.let { result ->
result.copy(
title = playlistPanelRenderer.title,
items =
playlistPanelRenderer.contents.mapNotNull {
it.playlistPanelVideoRenderer?.let { renderer ->
NextPage.fromPlaylistPanelVideoRenderer(renderer)
}
} + result.items,
lyricsEndpoint =
lyricsEndpoint =
response.contents.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs?.getOrNull(
1,
)?.tabRenderer?.endpoint?.browseEndpoint,
relatedEndpoint =
relatedEndpoint =
response.contents.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs?.getOrNull(
2,
)?.tabRenderer?.endpoint?.browseEndpoint,
currentIndex = playlistPanelRenderer.currentIndex,
endpoint = playlistPanelRenderer.contents.lastOrNull()?.automixPreviewVideoRenderer?.content?.automixPlaylistVideoRenderer?.navigationEndpoint?.watchPlaylistEndpoint!!,
)
}
}
currentIndex = playlistPanelRenderer.currentIndex,
endpoint = playlistPanelRenderer.contents.lastOrNull()?.automixPreviewVideoRenderer?.content?.automixPlaylistVideoRenderer?.navigationEndpoint?.watchPlaylistEndpoint!!,
)
}
}
// else if (playlistPanelRenderer.contents.firstOrNull()?.playlistPanelVideoRenderer?.navigationEndpoint?.watchPlaylistEndpoint != null) {
//
// }
NextResult(
title = playlistPanelRenderer.title,
items =
return@runCatching NextResult(
title = playlistPanelRenderer.title,
items =
playlistPanelRenderer.contents.mapNotNull {
it.playlistPanelVideoRenderer?.let(NextPage::fromPlaylistPanelVideoRenderer)
},
currentIndex = playlistPanelRenderer.currentIndex,
lyricsEndpoint =
currentIndex = playlistPanelRenderer.currentIndex,
lyricsEndpoint =
response.contents.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs?.getOrNull(
1,
)?.tabRenderer?.endpoint?.browseEndpoint,
relatedEndpoint =
relatedEndpoint =
response.contents.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer?.tabs?.getOrNull(
2,
)?.tabRenderer?.endpoint?.browseEndpoint,
continuation = playlistPanelRenderer.continuations?.getContinuation(),
endpoint = endpoint,
)
continuation = playlistPanelRenderer.continuations?.getContinuation(),
endpoint = endpoint,
)
}
else {
Log.e("YouTube", response.toString())
val musicPlaylistShelfContinuation = response.continuationContents?.musicPlaylistShelfContinuation!!
return@runCatching NextResult(
items = musicPlaylistShelfContinuation.contents.mapNotNull {
it.musicResponsiveListItemRenderer?.let { renderer ->
NextPage.fromMusicResponsiveListItemRenderer(renderer)
}
},
continuation = musicPlaylistShelfContinuation.continuations?.firstOrNull()?.nextContinuationData?.continuation,
endpoint = WatchEndpoint(
videoId = null,
playlistId = null,
playlistSetVideoId = null,
params = null,
index = null,
watchEndpointMusicSupportedConfigs = null
)

)
}

}

suspend fun lyrics(endpoint: BrowseEndpoint): Result<String?> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ data class SectionListRenderer(
val straplineThumbnail: StraplineThumbnail?,
val thumbnail: ThumbnailRenderer?,
val title: Title?,
val secondSubtitle: MusicShelfRenderer.Content.MusicMultiRowListItemRenderer.Subtitle?
) {
@Serializable
data class Description(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ data class NextResponse(

@Serializable
data class ContinuationContents(
val playlistPanelContinuation: PlaylistPanelRenderer,
val playlistPanelContinuation: PlaylistPanelRenderer?,
val sectionListContinuation: BrowseResponse.ContinuationContents.SectionListContinuation?,
val musicPlaylistShelfContinuation: BrowseResponse.ContinuationContents.MusicPlaylistShelfContinuation?
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.maxrave.kotlinytmusicscraper.pages
import com.maxrave.kotlinytmusicscraper.models.Album
import com.maxrave.kotlinytmusicscraper.models.Artist
import com.maxrave.kotlinytmusicscraper.models.BrowseEndpoint
import com.maxrave.kotlinytmusicscraper.models.MusicResponsiveListItemRenderer
import com.maxrave.kotlinytmusicscraper.models.PlaylistPanelVideoRenderer
import com.maxrave.kotlinytmusicscraper.models.SongItem
import com.maxrave.kotlinytmusicscraper.models.WatchEndpoint
Expand All @@ -21,6 +22,37 @@ data class NextResult(
)

object NextPage {
fun fromMusicResponsiveListItemRenderer(renderer: MusicResponsiveListItemRenderer): SongItem? {
val videoId = renderer.playlistItemData?.videoId ?: return null
val artistRuns = renderer.flexColumns.getOrNull(1)?.musicResponsiveListItemFlexColumnRenderer?.
text?.runs?.oddElements() ?: return null
val albumRuns = renderer.flexColumns.getOrNull(2)?.musicResponsiveListItemFlexColumnRenderer?.text
?.runs?.firstOrNull()
return SongItem(
id = videoId,
title = renderer.flexColumns.firstOrNull()?.musicResponsiveListItemFlexColumnRenderer
?.text?.runs?.firstOrNull()?.text ?: return null,
artists = artistRuns.map {
Artist(
name = it.text,
id = it.navigationEndpoint?.browseEndpoint?.browseId
)
},
album = albumRuns?.let {
Album(
name = it.text,
id = it.navigationEndpoint?.browseEndpoint?.browseId ?: ""
)
},
duration = renderer.fixedColumns?.firstOrNull()?.musicResponsiveListItemFlexColumnRenderer
?.text?.runs?.firstOrNull()?.text?.parseTime(),
thumbnail = renderer.thumbnail?.musicThumbnailRenderer?.getThumbnailUrl() ?: "",
explicit = false,
endpoint = renderer.flexColumns.firstOrNull()?.musicResponsiveListItemFlexColumnRenderer
?.text?.runs?.firstOrNull()?.navigationEndpoint?.watchEndpoint,
thumbnails = renderer.thumbnail?.musicThumbnailRenderer?.thumbnail
)
}
fun fromPlaylistPanelVideoRenderer(renderer: PlaylistPanelVideoRenderer): SongItem? {
val longByLineRuns = renderer.longBylineText?.runs?.splitBySeparator() ?: return null
return SongItem(
Expand Down
3 changes: 1 addition & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@ dependencyResolutionManagement {
}
rootProject.name = "SimpMusic"
include("app")
include(":kotlinYtmusicScraper")
include(":okhttp")
include(":kotlinYtmusicScraper")

0 comments on commit c87312b

Please sign in to comment.