@@ -687,87 +687,103 @@ public static String getYoutubeMusicClientVersion()
687687 return youtubeMusicClientVersion ;
688688 }
689689
690- @ Nullable
691- public static String getUrlFromNavigationEndpoint (
690+ @ Nonnull
691+ public static Optional < String > getUrlFromNavigationEndpoint (
692692 @ Nonnull final JsonObject navigationEndpoint ) {
693- if (navigationEndpoint .has ("urlEndpoint" )) {
694- String internUrl = navigationEndpoint .getObject ("urlEndpoint" )
695- .getString ("url" );
696- if (internUrl .startsWith ("https://www.youtube.com/redirect?" )) {
697- // remove https://www.youtube.com part to fall in the next if block
698- internUrl = internUrl .substring (23 );
699- }
700-
701- if (internUrl .startsWith ("/redirect?" )) {
702- // q parameter can be the first parameter
703- internUrl = internUrl .substring (10 );
704- final String [] params = internUrl .split ("&" );
705- for (final String param : params ) {
706- if (param .split ("=" )[0 ].equals ("q" )) {
707- return Utils .decodeUrlUtf8 (param .split ("=" )[1 ]);
693+ return Optional .ofNullable (navigationEndpoint .getObject ("urlEndpoint" )
694+ .getString ("url" ))
695+ .map (internUrl -> {
696+ if (internUrl .startsWith ("https://www.youtube.com/redirect?" )) {
697+ // remove https://www.youtube.com part to fall in the next if block
698+ internUrl = internUrl .substring (23 );
708699 }
709- }
710- } else if (internUrl .startsWith ("http" )) {
711- return internUrl ;
712- } else if (internUrl .startsWith ("/channel" ) || internUrl .startsWith ("/user" )
713- || internUrl .startsWith ("/watch" )) {
714- return "https://www.youtube.com" + internUrl ;
715- }
716- }
717-
718- if (navigationEndpoint .has ("browseEndpoint" )) {
719- final JsonObject browseEndpoint = navigationEndpoint .getObject ("browseEndpoint" );
720- final String canonicalBaseUrl = browseEndpoint .getString ("canonicalBaseUrl" );
721- final String browseId = browseEndpoint .getString ("browseId" );
722-
723- if (browseId != null ) {
724- if (browseId .startsWith ("UC" )) {
725- // All channel IDs are prefixed with UC
726- return "https://www.youtube.com/channel/" + browseId ;
727- } else if (browseId .startsWith ("VL" )) {
728- // All playlist IDs are prefixed with VL, which needs to be removed from the
729- // playlist ID
730- return "https://www.youtube.com/playlist?list=" + browseId .substring (2 );
731- }
732- }
733700
734- if (!isNullOrEmpty (canonicalBaseUrl )) {
735- return "https://www.youtube.com" + canonicalBaseUrl ;
736- }
737- }
738-
739- if (navigationEndpoint .has ("watchEndpoint" )) {
740- final StringBuilder url = new StringBuilder ();
741- url .append ("https://www.youtube.com/watch?v=" )
742- .append (navigationEndpoint .getObject ("watchEndpoint" )
743- .getString (VIDEO_ID ));
744- if (navigationEndpoint .getObject ("watchEndpoint" ).has ("playlistId" )) {
745- url .append ("&list=" ).append (navigationEndpoint .getObject ("watchEndpoint" )
746- .getString ("playlistId" ));
747- }
748- if (navigationEndpoint .getObject ("watchEndpoint" ).has ("startTimeSeconds" )) {
749- url .append ("&t=" )
750- .append (navigationEndpoint .getObject ("watchEndpoint" )
751- .getInt ("startTimeSeconds" ));
752- }
753- return url .toString ();
754- }
755-
756- if (navigationEndpoint .has ("watchPlaylistEndpoint" )) {
757- return "https://www.youtube.com/playlist?list="
758- + navigationEndpoint .getObject ("watchPlaylistEndpoint" )
759- .getString ("playlistId" );
760- }
701+ if (internUrl .startsWith ("/redirect?" )) {
702+ // q parameter can be the first parameter
703+ internUrl = internUrl .substring (10 );
704+ final String [] params = internUrl .split ("&" );
705+ for (final String param : params ) {
706+ final String [] nameAndValue = param .split ("=" );
707+ if (nameAndValue [0 ].equals ("q" )) {
708+ return Utils .decodeUrlUtf8 (nameAndValue [1 ]);
709+ }
710+ }
711+ } else if (internUrl .startsWith ("http" )) {
712+ return internUrl ;
713+ } else if (internUrl .startsWith ("/channel" ) || internUrl .startsWith ("/user" )
714+ || internUrl .startsWith ("/watch" )) {
715+ return "https://www.youtube.com" + internUrl ;
716+ }
761717
762- if (navigationEndpoint .has ("commandMetadata" )) {
763- final JsonObject metadata = navigationEndpoint .getObject ("commandMetadata" )
764- .getObject ("webCommandMetadata" );
765- if (metadata .has ("url" )) {
766- return "https://www.youtube.com" + metadata .getString ("url" );
767- }
768- }
718+ return null ;
719+ })
720+ .or (() -> {
721+ final var browseEndpoint = navigationEndpoint .getObject ("browseEndpoint" );
722+ final var baseUrl = browseEndpoint .getString ("canonicalBaseUrl" );
723+ final var browseId = browseEndpoint .getString ("browseId" );
724+
725+ return Optional .ofNullable (browseId )
726+ .map (id -> {
727+ if (id .startsWith ("UC" )) {
728+ // All channel IDs are prefixed with UC
729+ return "https://www.youtube.com/channel/" + id ;
730+ } else if (id .startsWith ("VL" )) {
731+ // All playlist IDs are prefixed with VL, which needs to be
732+ // removed from the playlist ID
733+ return "https://www.youtube.com/playlist?list="
734+ + id .substring (2 );
735+ }
736+ return null ;
737+ })
738+ .or (() -> {
739+ if (!isNullOrEmpty (baseUrl )) {
740+ return Optional .of ("https://www.youtube.com" + baseUrl );
741+ } else {
742+ return Optional .empty ();
743+ }
744+ });
745+ })
746+ .or (() -> {
747+ final var watchEndpoint = navigationEndpoint .getObject ("watchEndpoint" );
748+ final var videoId = watchEndpoint .getString (VIDEO_ID );
749+ final var playlistId = watchEndpoint .getString ("playlistId" );
750+ final var startTime = watchEndpoint .getInt ("startTimeSeconds" , -1 );
751+ final String url = "https://www.youtube.com/watch?v=" + videoId
752+ + (playlistId != null ? "&list=" + playlistId : "" )
753+ + (startTime != -1 ? "&t=" + startTime : "" );
754+ return Optional .of (url );
755+ })
756+ .or (() -> {
757+ final var playlistId = navigationEndpoint .getObject ("watchPlaylistEndpoint" )
758+ .getString ("playlistId" );
759+ return Optional .ofNullable (playlistId )
760+ .map (id -> "https://www.youtube.com/playlist?list=" + id );
761+ })
762+ .or (() -> {
763+ final var metadata = navigationEndpoint .getObject ("commandMetadata" )
764+ .getObject ("webCommandMetadata" );
765+ return Optional .ofNullable (metadata .getString ("url" ))
766+ .map (url -> "https://www.youtube.com" + url );
767+ })
768+ .filter (url -> !url .isEmpty ());
769+ }
769770
770- return null ;
771+ @ Nonnull
772+ public static Optional <String > getMusicUploaderUrlFromMenu (@ Nonnull final JsonObject object ) {
773+ final var items = object .getObject ("menu" )
774+ .getObject ("menuRenderer" )
775+ .getArray ("items" );
776+ return items .streamAsJsonObjects ()
777+ .flatMap (item -> {
778+ final var renderer = item .getObject ("menuNavigationItemRenderer" );
779+ final var iconType = renderer .getObject ("icon" ).getString ("iconType" );
780+ final var endpoint = renderer .getObject ("navigationEndpoint" );
781+
782+ return "ARTIST" .equals (iconType )
783+ ? getUrlFromNavigationEndpoint (endpoint ).stream ()
784+ : Stream .empty ();
785+ })
786+ .findFirst ();
771787 }
772788
773789 /**
@@ -790,8 +806,8 @@ public static Optional<String> getTextFromObject(@Nonnull final JsonObject textO
790806
791807 if (html ) {
792808 final String url = getUrlFromNavigationEndpoint (
793- run .getObject ("navigationEndpoint" ));
794- if (! isNullOrEmpty ( url ) ) {
809+ run .getObject ("navigationEndpoint" )). orElse ( null ) ;
810+ if (url != null ) {
795811 textString = "<a href=\" " + Entities .escape (url ) + "\" >"
796812 + Entities .escape (textString ) + "</a>" ;
797813 }
@@ -838,9 +854,9 @@ public static Optional<String> getTextFromObject(@Nonnull final JsonObject textO
838854 @ Nonnull
839855 public static Optional <String > getUrlFromObject (@ Nonnull final JsonObject textObject ) {
840856 return textObject .getArray ("runs" ).streamAsJsonObjects ()
841- .map (textPart -> getUrlFromNavigationEndpoint (textPart
842- .getObject ("navigationEndpoint" )))
843- . filter ( url -> ! isNullOrEmpty ( url ))
857+ .flatMap (textPart -> getUrlFromNavigationEndpoint (textPart
858+ .getObject ("navigationEndpoint" ))
859+ . stream ( ))
844860 .findFirst ();
845861 }
846862
0 commit comments