2828import java .time .OffsetDateTime ;
2929import java .time .ZoneOffset ;
3030import java .time .format .DateTimeParseException ;
31- import java .util .ArrayList ;
32- import java .util .Collections ;
33- import java .util .HashMap ;
34- import java .util .List ;
35- import java .util .Map ;
36- import java .util .Objects ;
31+ import java .util .*;
3732
3833import javax .annotation .Nonnull ;
3934import javax .annotation .Nullable ;
@@ -79,6 +74,17 @@ private YoutubeParsingHelper() {
7974 private static final String [] HARDCODED_YOUTUBE_MUSIC_KEYS = {"AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30" , "67" , "0.1" };
8075 private static String [] youtubeMusicKeys ;
8176
77+ /**
78+ * <code>PENDING+</code> means that the user did not yet submit their choices.
79+ * Therefore, YouTube & Google should not track the user, because they did not give consent.
80+ * The three digits at the end can be random, but are required.
81+ */
82+ public static final String CONSENT_COOKIE_VALUE = "PENDING+" + (100 + new Random ().nextInt (900 ));
83+ /**
84+ * Youtube <code>CONSENT</code> cookie. Should prevent redirect to consent.youtube.com
85+ */
86+ public static final String CONSENT_COOKIE = "CONSENT=" + CONSENT_COOKIE_VALUE ;
87+
8288 private static final String FEED_BASE_CHANNEL_ID = "https://www.youtube.com/feeds/videos.xml?channel_id=" ;
8389 private static final String FEED_BASE_USER = "https://www.youtube.com/feeds/videos.xml?user=" ;
8490
@@ -427,6 +433,7 @@ public static boolean areHardcodedYoutubeMusicKeysValid() throws IOException, Re
427433 headers .put ("Origin" , Collections .singletonList ("https://music.youtube.com" ));
428434 headers .put ("Referer" , Collections .singletonList ("music.youtube.com" ));
429435 headers .put ("Content-Type" , Collections .singletonList ("application/json" ));
436+ addCookieHeader (headers );
430437
431438 final String response = getDownloader ().post (url , headers , json ).responseBody ();
432439
@@ -629,34 +636,19 @@ public static String getValidJsonResponseBody(final Response response)
629636 public static Response getResponse (final String url , final Localization localization )
630637 throws IOException , ExtractionException {
631638 final Map <String , List <String >> headers = new HashMap <>();
632- headers .put ("X-YouTube-Client-Name" , Collections .singletonList ("1" ));
633- headers .put ("X-YouTube-Client-Version" , Collections .singletonList (getClientVersion ()));
639+ addYouTubeHeaders (headers );
634640
635641 final Response response = getDownloader ().get (url , headers , localization );
636642 getValidJsonResponseBody (response );
637643
638644 return response ;
639645 }
640646
641- public static String extractCookieValue (final String cookieName , final Response response ) {
642- final List <String > cookies = response .responseHeaders ().get ("set-cookie" );
643- int startIndex ;
644- String result = "" ;
645- for (final String cookie : cookies ) {
646- startIndex = cookie .indexOf (cookieName );
647- if (startIndex != -1 ) {
648- result = cookie .substring (startIndex + cookieName .length () + "=" .length (),
649- cookie .indexOf (";" , startIndex ));
650- }
651- }
652- return result ;
653- }
654-
655647 public static JsonArray getJsonResponse (final String url , final Localization localization )
656648 throws IOException , ExtractionException {
657649 Map <String , List <String >> headers = new HashMap <>();
658- headers . put ( "X-YouTube-Client-Name" , Collections . singletonList ( "1" ) );
659- headers . put ( "X-YouTube-Client-Version" , Collections . singletonList ( getClientVersion ()));
650+ addYouTubeHeaders ( headers );
651+
660652 final Response response = getDownloader ().get (url , headers , localization );
661653
662654 return JsonUtils .toJsonArray (getValidJsonResponseBody (response ));
@@ -665,17 +657,65 @@ public static JsonArray getJsonResponse(final String url, final Localization loc
665657 public static JsonArray getJsonResponse (final Page page , final Localization localization )
666658 throws IOException , ExtractionException {
667659 final Map <String , List <String >> headers = new HashMap <>();
668- if (!isNullOrEmpty (page .getCookies ())) {
669- headers .put ("Cookie" , Collections .singletonList (join (";" , "=" , page .getCookies ())));
670- }
671- headers .put ("X-YouTube-Client-Name" , Collections .singletonList ("1" ));
672- headers .put ("X-YouTube-Client-Version" , Collections .singletonList (getClientVersion ()));
660+ addYouTubeHeaders (headers );
673661
674662 final Response response = getDownloader ().get (page .getUrl (), headers , localization );
675663
676664 return JsonUtils .toJsonArray (getValidJsonResponseBody (response ));
677665 }
678666
667+ /**
668+ * Add required headers and cookies to an existing headers Map.
669+ * @see #addClientInfoHeaders(Map)
670+ * @see #addCookieHeader(Map)
671+ */
672+ public static void addYouTubeHeaders (final Map <String , List <String >> headers )
673+ throws IOException , ExtractionException {
674+ addClientInfoHeaders (headers );
675+ addCookieHeader (headers );
676+ }
677+
678+ /**
679+ * Add the <code>X-YouTube-Client-Name</code> and <code>X-YouTube-Client-Version</code> headers.
680+ * @param headers The headers which should be completed
681+ */
682+ public static void addClientInfoHeaders (final Map <String , List <String >> headers )
683+ throws IOException , ExtractionException {
684+ if (headers .get ("X-YouTube-Client-Name" ) == null ) {
685+ headers .put ("X-YouTube-Client-Name" , Collections .singletonList ("1" ));
686+ }
687+ if (headers .get ("X-YouTube-Client-Version" ) == null ) {
688+ headers .put ("X-YouTube-Client-Version" , Collections .singletonList (getClientVersion ()));
689+ }
690+ }
691+
692+ /**
693+ * Add the <code>CONSENT</code> cookie to prevent redirect to <code>consent.youtube.com</code>
694+ * @see #CONSENT_COOKIE
695+ * @param headers the headers which should be completed
696+ */
697+ public static void addCookieHeader (final Map <String , List <String >> headers ) {
698+ if (headers .get ("Cookie" ) == null ) {
699+ headers .put ("Cookie" , Arrays .asList (CONSENT_COOKIE ));
700+ } else {
701+ headers .get ("Cookie" ).add (CONSENT_COOKIE );
702+ }
703+ }
704+
705+ public static String extractCookieValue (final String cookieName , final Response response ) {
706+ final List <String > cookies = response .responseHeaders ().get ("set-cookie" );
707+ int startIndex ;
708+ String result = "" ;
709+ for (final String cookie : cookies ) {
710+ startIndex = cookie .indexOf (cookieName );
711+ if (startIndex != -1 ) {
712+ result = cookie .substring (startIndex + cookieName .length () + "=" .length (),
713+ cookie .indexOf (";" , startIndex ));
714+ }
715+ }
716+ return result ;
717+ }
718+
679719 /**
680720 * Shared alert detection function, multiple endpoints return the error similarly structured.
681721 * <p>
0 commit comments