1515import java .net .URLEncoder ;
1616import java .nio .charset .StandardCharsets ;
1717import java .util .ArrayList ;
18+ import java .util .Collections ;
1819import java .util .HashMap ;
1920import java .util .List ;
2021import java .util .Map ;
@@ -50,52 +51,50 @@ private YoutubeApiDecoder() {
5051 @ Nonnull
5152 static String decodeSignature (@ Nonnull final String playerId ,
5253 @ Nonnull final String signature ) throws ParsingException {
53- return decode (playerId , "sig" , signature );
54+ return decode ("sig" , signature );
5455 }
5556
5657 /**
5758 * Decode a throttling parameter (n parameter) using the PipePipe API.
5859 *
5960 * @param playerId the YouTube player ID (8-character hash)
60- * @param nParameter the obfuscated n parameter to decode
61+ * @param nParameter the obfuscated n parameter to decode
6162 * @return the deobfuscated n parameter
6263 * @throws ParsingException if the API call fails or returns invalid data
6364 */
6465 @ Nonnull
6566 static String decodeThrottlingParameter (@ Nonnull final String playerId ,
6667 @ Nonnull final String nParameter )
6768 throws ParsingException {
68- return decode (playerId , "n" , nParameter );
69+ return decode ( "n" , nParameter );
6970 }
7071
7172 /**
7273 * Generic decode method that calls the PipePipe API.
7374 *
74- * @param playerId the YouTube player ID (8-character hash)
75- * @param paramType the parameter type ("sig" or "n")
76- * @param value the obfuscated value to decode
75+ * @param paramType the parameter type ("sig" or "n")
76+ * @param value the obfuscated value to decode
7777 * @return the deobfuscated value
7878 * @throws ParsingException if the API call fails or returns invalid data
7979 */
8080 @ Nonnull
81- private static String decode (@ Nonnull final String playerId ,
82- @ Nonnull final String paramType ,
81+ private static String decode (@ Nonnull final String paramType ,
8382 @ Nonnull final String value ) throws ParsingException {
8483 // Check cache first
85- final String cacheKey = playerId + ":" + paramType + ":" + value ;
84+ final String cacheKey = paramType + ":" + value ;
8685 final String cachedResult = DECODE_CACHE .get (cacheKey );
8786 if (cachedResult != null ) {
8887 return cachedResult ;
8988 }
9089
9190 try {
92- // Build API URL
91+ // Build API URL (no player parameter)
9392 final String encodedValue = URLEncoder .encode (value , StandardCharsets .UTF_8 .name ());
94- final String url = API_BASE_URL + "?player=" + playerId + "& " + paramType + "=" + encodedValue ;
93+ final String url = API_BASE_URL + "?" + paramType + "=" + encodedValue ;
9594
9695 // Set headers
97- final Map <String , java . util . List <String >> headers = new HashMap <>();
98- headers .put ("User-Agent" , java . util . Collections .singletonList (USER_AGENT ));
96+ final Map <String , List <String >> headers = new HashMap <>();
97+ headers .put ("User-Agent" , Collections .singletonList (USER_AGENT ));
9998
10099 // Make API call
101100 final Response response = NewPipe .getDownloader ().get (url , headers , Localization .DEFAULT );
@@ -106,20 +105,24 @@ private static String decode(@Nonnull final String playerId,
106105
107106 // Validate response structure
108107 if (!"result" .equals (jsonResponse .getString ("type" ))) {
109- throw new ParsingException ("API returned unexpected type: " + jsonResponse .getString ("type" ));
108+ throw new ParsingException (
109+ "API returned unexpected type: " + jsonResponse .getString ("type" ));
110110 }
111111
112112 // Extract decoded value
113113 final JsonObject firstResponse = jsonResponse .getArray ("responses" ).getObject (0 );
114114 if (!"result" .equals (firstResponse .getString ("type" ))) {
115- throw new ParsingException ("API response item has unexpected type: " + firstResponse .getString ("type" ));
115+ throw new ParsingException (
116+ "API response item has unexpected type: "
117+ + firstResponse .getString ("type" ));
116118 }
117119
118120 final JsonObject data = firstResponse .getObject ("data" );
119121 final String decodedValue = data .getString (value );
120122
121123 if (decodedValue == null || decodedValue .isEmpty ()) {
122- throw new ParsingException ("API returned empty decoded value for: " + value );
124+ throw new ParsingException (
125+ "API returned empty decoded value for: " + value );
123126 }
124127
125128 // Cache the result
@@ -130,6 +133,8 @@ private static String decode(@Nonnull final String playerId,
130133 throw new ParsingException ("Failed to call decode API" , e );
131134 } catch (final JsonParserException e ) {
132135 throw new ParsingException ("Failed to parse API response" , e );
136+ } catch (final ParsingException e ) {
137+ throw e ;
133138 } catch (final Exception e ) {
134139 throw new ParsingException ("Unexpected error during decoding" , e );
135140 }
@@ -154,18 +159,15 @@ static int getCacheSize() {
154159 /**
155160 * Batch decode multiple signatures and throttling parameters in a single API call.
156161 *
157- * @param playerId the YouTube player ID (8-character hash)
158162 * @param signatureParams list of obfuscated signatures to decode (can be null or empty)
159163 * @param nParams list of obfuscated n parameters to decode (can be null or empty)
160164 * @return a BatchDecodeResult containing the decoded values
161165 * @throws ParsingException if the API call fails or returns invalid data
162166 */
163167 @ Nonnull
164- static BatchDecodeResult decodeBatch (@ Nonnull final String playerId ,
165- @ Nullable final List <String > signatureParams ,
168+ static BatchDecodeResult decodeBatch (@ Nullable final List <String > signatureParams ,
166169 @ Nullable final List <String > nParams )
167170 throws ParsingException {
168- // Validate input
169171 final boolean hasSigs = signatureParams != null && !signatureParams .isEmpty ();
170172 final boolean hasNs = nParams != null && !nParams .isEmpty ();
171173
@@ -181,7 +183,7 @@ static BatchDecodeResult decodeBatch(@Nonnull final String playerId,
181183
182184 if (hasSigs ) {
183185 for (final String sig : signatureParams ) {
184- final String cacheKey = playerId + ": sig:" + sig ;
186+ final String cacheKey = " sig:" + sig ;
185187 final String cachedResult = DECODE_CACHE .get (cacheKey );
186188 if (cachedResult != null ) {
187189 sigResults .put (sig , cachedResult );
@@ -193,7 +195,7 @@ static BatchDecodeResult decodeBatch(@Nonnull final String playerId,
193195
194196 if (hasNs ) {
195197 for (final String n : nParams ) {
196- final String cacheKey = playerId + ": n:" + n ;
198+ final String cacheKey = " n:" + n ;
197199 final String cachedResult = DECODE_CACHE .get (cacheKey );
198200 if (cachedResult != null ) {
199201 nResults .put (n , cachedResult );
@@ -209,44 +211,50 @@ static BatchDecodeResult decodeBatch(@Nonnull final String playerId,
209211 }
210212
211213 try {
212- // Build API URL with batch parameters
214+ // Build API URL without player parameter
213215 final StringBuilder urlBuilder = new StringBuilder (API_BASE_URL );
214- urlBuilder . append ( "?player=" ). append ( playerId ) ;
216+ boolean firstParam = true ;
215217
216218 if (!uncachedNs .isEmpty ()) {
217- urlBuilder .append ("&n=" );
219+ urlBuilder .append (firstParam ? '?' : '&' );
220+ firstParam = false ;
221+ urlBuilder .append ("n=" );
218222 for (int i = 0 ; i < uncachedNs .size (); i ++) {
219223 if (i > 0 ) {
220224 urlBuilder .append (',' );
221225 }
222- urlBuilder .append (URLEncoder .encode (uncachedNs .get (i ), StandardCharsets .UTF_8 .name ()));
226+ urlBuilder .append (URLEncoder .encode (
227+ uncachedNs .get (i ), StandardCharsets .UTF_8 .name ()));
223228 }
224229 }
225230
226231 if (!uncachedSigs .isEmpty ()) {
227- urlBuilder .append ("&sig=" );
232+ urlBuilder .append (firstParam ? '?' : '&' );
233+ urlBuilder .append ("sig=" );
228234 for (int i = 0 ; i < uncachedSigs .size (); i ++) {
229235 if (i > 0 ) {
230236 urlBuilder .append (',' );
231237 }
232- urlBuilder .append (URLEncoder .encode (uncachedSigs .get (i ), StandardCharsets .UTF_8 .name ()));
238+ urlBuilder .append (URLEncoder .encode (
239+ uncachedSigs .get (i ), StandardCharsets .UTF_8 .name ()));
233240 }
234241 }
235242
236243 // Set headers
237- final Map <String , java . util . List <String >> headers = new HashMap <>();
238- headers .put ("User-Agent" , java . util . Collections .singletonList (USER_AGENT ));
244+ final Map <String , List <String >> headers = new HashMap <>();
245+ headers .put ("User-Agent" , Collections .singletonList (USER_AGENT ));
239246
240247 // Make API call
241- final Response response = NewPipe .getDownloader ().get (urlBuilder .toString (), headers , Localization .DEFAULT );
248+ final Response response = NewPipe .getDownloader ().get (
249+ urlBuilder .toString (), headers , Localization .DEFAULT );
242250
243251 // Parse response
244252 final String responseBody = response .responseBody ();
245253 final JsonObject jsonResponse = JsonParser .object ().from (responseBody );
246254
247- // Validate response structure
248255 if (!"result" .equals (jsonResponse .getString ("type" ))) {
249- throw new ParsingException ("API returned unexpected type: " + jsonResponse .getString ("type" ));
256+ throw new ParsingException (
257+ "API returned unexpected type: " + jsonResponse .getString ("type" ));
250258 }
251259
252260 final JsonArray responses = jsonResponse .getArray ("responses" );
@@ -256,37 +264,41 @@ static BatchDecodeResult decodeBatch(@Nonnull final String playerId,
256264 if (!uncachedNs .isEmpty ()) {
257265 final JsonObject nResponse = responses .getObject (responseIndex ++);
258266 if (!"result" .equals (nResponse .getString ("type" ))) {
259- throw new ParsingException ("N parameter response has unexpected type: " + nResponse .getString ("type" ));
267+ throw new ParsingException (
268+ "N parameter response has unexpected type: "
269+ + nResponse .getString ("type" ));
260270 }
261271
262272 final JsonObject nData = nResponse .getObject ("data" );
263273 for (final String nParam : uncachedNs ) {
264274 final String decodedValue = nData .getString (nParam );
265275 if (decodedValue == null || decodedValue .isEmpty ()) {
266- throw new ParsingException ("API returned empty decoded value for n parameter: " + nParam );
276+ throw new ParsingException (
277+ "API returned empty decoded value for n parameter: " + nParam );
267278 }
268279 nResults .put (nParam , decodedValue );
269- // Cache the result
270- DECODE_CACHE .put (playerId + ":n:" + nParam , decodedValue );
280+ DECODE_CACHE .put ("n:" + nParam , decodedValue );
271281 }
272282 }
273283
274284 // Process signature parameters (if present)
275285 if (!uncachedSigs .isEmpty ()) {
276286 final JsonObject sigResponse = responses .getObject (responseIndex );
277287 if (!"result" .equals (sigResponse .getString ("type" ))) {
278- throw new ParsingException ("Signature response has unexpected type: " + sigResponse .getString ("type" ));
288+ throw new ParsingException (
289+ "Signature response has unexpected type: "
290+ + sigResponse .getString ("type" ));
279291 }
280292
281293 final JsonObject sigData = sigResponse .getObject ("data" );
282294 for (final String sig : uncachedSigs ) {
283295 final String decodedValue = sigData .getString (sig );
284296 if (decodedValue == null || decodedValue .isEmpty ()) {
285- throw new ParsingException ("API returned empty decoded value for signature: " + sig );
297+ throw new ParsingException (
298+ "API returned empty decoded value for signature: " + sig );
286299 }
287300 sigResults .put (sig , decodedValue );
288- // Cache the result
289- DECODE_CACHE .put (playerId + ":sig:" + sig , decodedValue );
301+ DECODE_CACHE .put ("sig:" + sig , decodedValue );
290302 }
291303 }
292304
@@ -295,6 +307,8 @@ static BatchDecodeResult decodeBatch(@Nonnull final String playerId,
295307 throw new ParsingException ("Failed to call batch decode API" , e );
296308 } catch (final JsonParserException e ) {
297309 throw new ParsingException ("Failed to parse batch API response" , e );
310+ } catch (final ParsingException e ) {
311+ throw e ;
298312 } catch (final Exception e ) {
299313 throw new ParsingException ("Unexpected error during batch decoding" , e );
300314 }
0 commit comments