2022-07-16 17:42:51 +02:00
package app.revanced.integrations.returnyoutubedislike.requests ;
2022-01-17 14:54:11 +01:00
2022-12-21 22:19:34 +01:00
import static app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute ;
import static app.revanced.integrations.sponsorblock.StringRef.str ;
2022-11-25 00:10:58 +01:00
import android.util.Base64 ;
2022-12-21 22:19:34 +01:00
import android.widget.Toast ;
2022-11-25 00:10:58 +01:00
import androidx.annotation.Nullable ;
2022-01-17 14:54:11 +01:00
2022-12-03 17:23:00 +01:00
import org.json.JSONException ;
2022-01-17 14:54:11 +01:00
import org.json.JSONObject ;
import java.io.OutputStream ;
import java.net.HttpURLConnection ;
2022-11-25 00:10:58 +01:00
import java.net.ProtocolException ;
2022-01-17 14:54:11 +01:00
import java.nio.charset.StandardCharsets ;
2022-11-25 00:10:58 +01:00
import java.security.MessageDigest ;
2022-11-30 19:58:11 +01:00
import java.security.NoSuchAlgorithmException ;
2022-11-25 00:10:58 +01:00
import java.security.SecureRandom ;
import java.util.Objects ;
2022-01-17 14:54:11 +01:00
2022-11-05 03:19:09 +01:00
import app.revanced.integrations.requests.Requester ;
2022-11-25 00:10:58 +01:00
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike ;
import app.revanced.integrations.utils.LogHelper ;
import app.revanced.integrations.utils.ReVancedUtils ;
2022-01-17 14:54:11 +01:00
2022-07-16 17:42:51 +02:00
public class ReturnYouTubeDislikeApi {
2022-12-21 22:19:34 +01:00
/ * *
* { @link # fetchVotes ( String ) } TCP connection timeout
* /
private static final int API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS = 2000 ;
2022-01-17 14:54:11 +01:00
2022-11-30 19:58:11 +01:00
/ * *
2022-12-21 22:19:34 +01:00
* { @link # fetchVotes ( String ) } HTTP read timeout
* To locally debug and force timeouts , change this to a very small number ( ie : 100 )
2022-11-30 19:58:11 +01:00
* /
2022-12-21 22:19:34 +01:00
private static final int API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS = 4000 ;
2022-11-30 19:58:11 +01:00
/ * *
* Default connection and response timeout for voting and registration .
*
* Voting and user registration runs in the background and has has no urgency
* so this can be a larger value .
* /
2022-12-21 22:19:34 +01:00
private static final int API_REGISTER_VOTE_TIMEOUT_MILLISECONDS = 90000 ;
2022-11-30 19:58:11 +01:00
/ * *
* Response code of a successful API call
* /
private static final int SUCCESS_HTTP_STATUS_CODE = 200 ;
2022-11-25 00:10:58 +01:00
/ * *
* Indicates a client rate limit has been reached
* /
private static final int RATE_LIMIT_HTTP_STATUS_CODE = 429 ;
/ * *
2022-11-30 19:58:11 +01:00
* How long to wait until API calls are resumed , if a rate limit is hit .
2022-11-25 00:10:58 +01:00
* No clear guideline of how long to backoff . Using 60 seconds for now .
* /
private static final int RATE_LIMIT_BACKOFF_SECONDS = 60 ;
/ * *
* Last time a { @link # RATE_LIMIT_HTTP_STATUS_CODE } was reached .
* zero if has not been reached .
* /
2022-12-21 22:19:34 +01:00
private static volatile long lastTimeRateLimitWasHit ; // must be volatile, since different threads read/write to this
/ * *
* Number of times { @link # RATE_LIMIT_HTTP_STATUS_CODE } was requested by RYD api .
* Does not include network calls attempted while rate limit is in effect
* /
private static volatile int numberOfRateLimitRequestsEncountered ;
/ * *
* Number of network calls made in { @link # fetchVotes ( String ) }
* /
private static volatile int fetchCallCount ;
/ * *
* Number of times { @link # fetchVotes ( String ) } failed due to timeout or any other error .
* This does not include when rate limit requests are encountered .
* /
private static volatile int fetchCallNumberOfFailures ;
/ * *
* Total time spent waiting for { @link # fetchVotes ( String ) } network call to complete .
* Value does does not persist on app shut down .
* /
private static volatile long fetchCallResponseTimeTotal ;
/ * *
* Round trip network time for the most recent call to { @link # fetchVotes ( String ) }
* /
private static volatile long fetchCallResponseTimeLast ;
private static volatile long fetchCallResponseTimeMin ;
private static volatile long fetchCallResponseTimeMax ;
2022-12-31 07:38:47 +01:00
public static final int FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT = - 1 ;
2022-12-21 22:19:34 +01:00
/ * *
* If rate limit was hit , this returns { @link # FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT }
* /
public static long getFetchCallResponseTimeLast ( ) {
return fetchCallResponseTimeLast ;
}
public static long getFetchCallResponseTimeMin ( ) {
return fetchCallResponseTimeMin ;
}
public static long getFetchCallResponseTimeMax ( ) {
return fetchCallResponseTimeMax ;
}
public static long getFetchCallResponseTimeAverage ( ) {
return fetchCallCount = = 0 ? 0 : ( fetchCallResponseTimeTotal / fetchCallCount ) ;
}
public static int getFetchCallCount ( ) {
return fetchCallCount ;
}
public static int getFetchCallNumberOfFailures ( ) {
return fetchCallNumberOfFailures ;
}
public static int getNumberOfRateLimitRequestsEncountered ( ) {
return numberOfRateLimitRequestsEncountered ;
}
2022-11-25 00:10:58 +01:00
2022-07-16 17:42:51 +02:00
private ReturnYouTubeDislikeApi ( ) {
2022-11-25 00:10:58 +01:00
} // utility class
2022-11-30 19:58:11 +01:00
/ * *
2022-12-21 22:19:34 +01:00
* Simulates a slow response by doing meaningless calculations .
2022-12-31 07:38:47 +01:00
* Used to debug the app UI and verify UI timeout logic works
2022-11-30 19:58:11 +01:00
*
* @param maximumTimeToWait maximum time to wait
* /
private static long randomlyWaitIfLocallyDebugging ( long maximumTimeToWait ) {
2022-12-21 22:19:34 +01:00
final boolean DEBUG_RANDOMLY_DELAY_NETWORK_CALLS = false ; // set true to debug UI
2022-11-30 19:58:11 +01:00
if ( DEBUG_RANDOMLY_DELAY_NETWORK_CALLS ) {
final long amountOfTimeToWaste = ( long ) ( Math . random ( ) * maximumTimeToWait ) ;
final long timeCalculationStarted = System . currentTimeMillis ( ) ;
LogHelper . printDebug ( ( ) - > " Artificially creating network delay of: " + amountOfTimeToWaste + " ms " ) ;
long meaninglessValue = 0 ;
while ( System . currentTimeMillis ( ) - timeCalculationStarted < amountOfTimeToWaste ) {
// could do a thread sleep, but that will trigger an exception if the thread is interrupted
meaninglessValue + = Long . numberOfLeadingZeros ( ( long ) ( Math . random ( ) * Long . MAX_VALUE ) ) ;
}
// return the value, otherwise the compiler or VM might optimize and remove the meaningless time wasting work,
// leaving an empty loop that hammers on the System.currentTimeMillis native call
return meaninglessValue ;
}
return 0 ;
}
2022-11-25 00:10:58 +01:00
/ * *
* @return True , if api rate limit is in effect .
* /
private static boolean checkIfRateLimitInEffect ( String apiEndPointName ) {
2022-12-21 22:19:34 +01:00
if ( lastTimeRateLimitWasHit = = 0 ) {
2022-11-25 00:10:58 +01:00
return false ;
}
2022-12-21 22:19:34 +01:00
final long numberOfSecondsSinceLastRateLimit = ( System . currentTimeMillis ( ) - lastTimeRateLimitWasHit ) / 1000 ;
2022-11-25 00:10:58 +01:00
if ( numberOfSecondsSinceLastRateLimit < RATE_LIMIT_BACKOFF_SECONDS ) {
2022-11-30 00:49:26 +01:00
LogHelper . printDebug ( ( ) - > " Ignoring api call " + apiEndPointName + " as only "
2022-11-25 00:10:58 +01:00
+ numberOfSecondsSinceLastRateLimit + " seconds has passed since last rate limit. " ) ;
return true ;
}
return false ;
2022-06-24 00:16:32 +02:00
}
2022-01-17 14:54:11 +01:00
2022-11-25 00:10:58 +01:00
/ * *
2022-12-21 22:19:34 +01:00
* @return True , if a client rate limit was requested
2022-11-25 00:10:58 +01:00
* /
private static boolean checkIfRateLimitWasHit ( int httpResponseCode ) {
2022-12-21 22:19:34 +01:00
final boolean DEBUG_RATE_LIMIT = false ; // set to true, to verify rate limit works
2022-11-25 00:10:58 +01:00
if ( DEBUG_RATE_LIMIT ) {
2022-12-21 22:19:34 +01:00
final double RANDOM_RATE_LIMIT_PERCENTAGE = 0 . 2 ; // 20% chance of a triggering a rate limit
2022-11-25 00:10:58 +01:00
if ( Math . random ( ) < RANDOM_RATE_LIMIT_PERCENTAGE ) {
2022-11-30 00:49:26 +01:00
LogHelper . printDebug ( ( ) - > " Artificially triggering rate limit for debug purposes " ) ;
2022-11-25 00:10:58 +01:00
httpResponseCode = RATE_LIMIT_HTTP_STATUS_CODE ;
}
}
if ( httpResponseCode = = RATE_LIMIT_HTTP_STATUS_CODE ) {
2022-12-21 22:19:34 +01:00
lastTimeRateLimitWasHit = System . currentTimeMillis ( ) ;
2022-11-30 00:49:26 +01:00
LogHelper . printDebug ( ( ) - > " API rate limit was hit. Stopping API calls for the next "
2022-11-25 00:10:58 +01:00
+ RATE_LIMIT_BACKOFF_SECONDS + " seconds " ) ;
2023-01-28 08:38:31 +01:00
ReVancedUtils . runOnMainThread ( ( ) - > { // must show toasts on main thread
Toast . makeText ( ReVancedUtils . getContext ( ) , str ( " revanced_ryd_failure_client_rate_limit_requested " ) , Toast . LENGTH_LONG ) . show ( ) ;
} ) ;
2022-11-25 00:10:58 +01:00
return true ;
}
return false ;
}
2023-01-28 08:38:31 +01:00
@SuppressWarnings ( " NonAtomicOperationOnVolatileField " ) // do not want to pay performance cost of full synchronization for debug fields that are only estimates anyways
2022-12-21 22:19:34 +01:00
private static void updateStatistics ( long timeNetworkCallStarted , long timeNetworkCallEnded , boolean connectionError , boolean rateLimitHit ) {
if ( connectionError & & rateLimitHit ) {
throw new IllegalArgumentException ( " both connection error and rate limit parameter were true " ) ;
}
final long responseTimeOfFetchCall = timeNetworkCallEnded - timeNetworkCallStarted ;
fetchCallResponseTimeTotal + = responseTimeOfFetchCall ;
fetchCallResponseTimeMin = ( fetchCallResponseTimeMin = = 0 ) ? responseTimeOfFetchCall : Math . min ( responseTimeOfFetchCall , fetchCallResponseTimeMin ) ;
fetchCallResponseTimeMax = Math . max ( responseTimeOfFetchCall , fetchCallResponseTimeMax ) ;
fetchCallCount + + ;
if ( connectionError ) {
fetchCallResponseTimeLast = responseTimeOfFetchCall ;
fetchCallNumberOfFailures + + ;
} else if ( rateLimitHit ) {
fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT ;
numberOfRateLimitRequestsEncountered + + ;
} else {
fetchCallResponseTimeLast = responseTimeOfFetchCall ;
}
}
2022-11-25 00:10:58 +01:00
/ * *
2022-12-03 17:23:00 +01:00
* @return NULL if fetch failed , or if a rate limit is in effect .
2022-11-25 00:10:58 +01:00
* /
@Nullable
2022-12-03 17:23:00 +01:00
public static RYDVoteData fetchVotes ( String videoId ) {
2022-11-25 00:10:58 +01:00
ReVancedUtils . verifyOffMainThread ( ) ;
Objects . requireNonNull ( videoId ) ;
2022-11-30 19:58:11 +01:00
2022-12-21 22:19:34 +01:00
if ( checkIfRateLimitInEffect ( " fetchVotes " ) ) {
return null ;
}
LogHelper . printDebug ( ( ) - > " Fetching votes for: " + videoId ) ;
final long timeNetworkCallStarted = System . currentTimeMillis ( ) ;
2023-01-28 08:38:31 +01:00
String connectionErrorMessageStringKey = " revanced_ryd_failure_connection_timeout " ;
2022-12-21 22:19:34 +01:00
try {
HttpURLConnection connection = getRYDConnectionFromRoute ( ReturnYouTubeDislikeRoutes . GET_DISLIKES , videoId ) ;
2022-11-30 19:58:11 +01:00
// request headers, as per https://returnyoutubedislike.com/docs/fetching
// the documentation says to use 'Accept:text/html', but the RYD browser plugin uses 'Accept:application/json'
connection . setRequestProperty ( " Accept " , " application/json " ) ;
connection . setRequestProperty ( " Connection " , " keep-alive " ) ; // keep-alive is on by default with http 1.1, but specify anyways
connection . setRequestProperty ( " Pragma " , " no-cache " ) ;
connection . setRequestProperty ( " Cache-Control " , " no-cache " ) ;
connection . setUseCaches ( false ) ;
2022-12-21 22:19:34 +01:00
connection . setConnectTimeout ( API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS ) ; // timeout for TCP connection to server
connection . setReadTimeout ( API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS ) ; // timeout for server response
2022-11-30 19:58:11 +01:00
2022-12-21 22:19:34 +01:00
randomlyWaitIfLocallyDebugging ( 2 * ( API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS + API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS ) ) ;
2022-11-30 19:58:11 +01:00
2022-11-25 00:10:58 +01:00
final int responseCode = connection . getResponseCode ( ) ;
if ( checkIfRateLimitWasHit ( responseCode ) ) {
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // rate limit hit, should disconnect
updateStatistics ( timeNetworkCallStarted , System . currentTimeMillis ( ) , false , true ) ;
2022-11-25 00:10:58 +01:00
return null ;
2022-11-30 19:58:11 +01:00
}
2022-12-21 22:19:34 +01:00
2022-11-30 19:58:11 +01:00
if ( responseCode = = SUCCESS_HTTP_STATUS_CODE ) {
2022-12-21 22:19:34 +01:00
final long timeNetworkCallEnded = System . currentTimeMillis ( ) ; // record end time before parsing
// do not disconnect, the same server connection will likely be used again soon
JSONObject json = Requester . parseJSONObject ( connection ) ;
2022-12-03 17:23:00 +01:00
try {
RYDVoteData votingData = new RYDVoteData ( json ) ;
2022-12-21 22:19:34 +01:00
updateStatistics ( timeNetworkCallStarted , timeNetworkCallEnded , false , false ) ;
2022-12-03 17:23:00 +01:00
LogHelper . printDebug ( ( ) - > " Voting data fetched: " + votingData ) ;
return votingData ;
} catch ( JSONException ex ) {
LogHelper . printException ( ( ) - > " Failed to parse video: " + videoId + " json: " + json , ex ) ;
2022-12-21 22:19:34 +01:00
// fall thru to update statistics
2022-12-03 17:23:00 +01:00
}
2022-12-21 22:19:34 +01:00
} else {
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to fetch votes for video: " + videoId
+ " response code was: " + responseCode , null , str ( connectionErrorMessageStringKey ) ) ;
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // something went wrong, might as well disconnect
2022-01-17 14:54:11 +01:00
}
2022-12-21 22:19:34 +01:00
} catch ( Exception ex ) { // connection timed out, response timeout, or some other network error
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to fetch votes " , ex , str ( connectionErrorMessageStringKey ) ) ;
2022-01-17 14:54:11 +01:00
}
2022-12-21 22:19:34 +01:00
updateStatistics ( timeNetworkCallStarted , System . currentTimeMillis ( ) , true , false ) ;
2022-11-25 00:10:58 +01:00
return null ;
2022-01-17 14:54:11 +01:00
}
2022-11-25 00:10:58 +01:00
/ * *
* @return The newly created and registered user id . Returns NULL if registration failed .
* /
@Nullable
public static String registerAsNewUser ( ) {
ReVancedUtils . verifyOffMainThread ( ) ;
2022-01-17 14:54:11 +01:00
try {
2022-11-25 00:10:58 +01:00
if ( checkIfRateLimitInEffect ( " registerAsNewUser " ) ) {
return null ;
}
String userId = randomString ( 36 ) ;
2022-11-30 19:58:11 +01:00
LogHelper . printDebug ( ( ) - > " Trying to register new user: " + userId ) ;
2022-11-25 00:10:58 +01:00
2022-12-21 22:19:34 +01:00
HttpURLConnection connection = getRYDConnectionFromRoute ( ReturnYouTubeDislikeRoutes . GET_REGISTRATION , userId ) ;
2022-11-30 19:58:11 +01:00
connection . setRequestProperty ( " Accept " , " application/json " ) ;
2022-12-21 22:19:34 +01:00
connection . setConnectTimeout ( API_REGISTER_VOTE_TIMEOUT_MILLISECONDS ) ;
connection . setReadTimeout ( API_REGISTER_VOTE_TIMEOUT_MILLISECONDS ) ;
2022-11-30 19:58:11 +01:00
2022-11-25 00:10:58 +01:00
final int responseCode = connection . getResponseCode ( ) ;
if ( checkIfRateLimitWasHit ( responseCode ) ) {
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // disconnect, as no more connections will be made for a little while
2022-11-25 00:10:58 +01:00
return null ;
2022-11-30 19:58:11 +01:00
}
if ( responseCode = = SUCCESS_HTTP_STATUS_CODE ) {
2022-12-21 22:19:34 +01:00
JSONObject json = Requester . parseJSONObject ( connection ) ;
2022-01-17 14:54:11 +01:00
String challenge = json . getString ( " challenge " ) ;
int difficulty = json . getInt ( " difficulty " ) ;
2022-11-30 00:49:26 +01:00
2022-11-25 00:10:58 +01:00
String solution = solvePuzzle ( challenge , difficulty ) ;
return confirmRegistration ( userId , solution ) ;
2022-01-17 14:54:11 +01:00
}
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to register new user: " + userId
2022-11-30 19:58:11 +01:00
+ " response code was: " + responseCode ) ;
connection . disconnect ( ) ;
2022-06-24 00:16:32 +02:00
} catch ( Exception ex ) {
2022-11-30 19:58:11 +01:00
LogHelper . printException ( ( ) - > " Failed to register user " , ex ) ;
2022-01-17 14:54:11 +01:00
}
return null ;
}
2022-11-25 00:10:58 +01:00
@Nullable
private static String confirmRegistration ( String userId , String solution ) {
ReVancedUtils . verifyOffMainThread ( ) ;
Objects . requireNonNull ( userId ) ;
Objects . requireNonNull ( solution ) ;
2022-01-17 14:54:11 +01:00
try {
2022-11-25 00:10:58 +01:00
if ( checkIfRateLimitInEffect ( " confirmRegistration " ) ) {
return null ;
}
2022-11-30 19:58:11 +01:00
LogHelper . printDebug ( ( ) - > " Trying to confirm registration for user: " + userId + " with solution: " + solution ) ;
2022-01-17 14:54:11 +01:00
2022-12-21 22:19:34 +01:00
HttpURLConnection connection = getRYDConnectionFromRoute ( ReturnYouTubeDislikeRoutes . CONFIRM_REGISTRATION , userId ) ;
2022-11-30 19:58:11 +01:00
applyCommonPostRequestSettings ( connection ) ;
2022-01-17 14:54:11 +01:00
String jsonInputString = " { \" solution \" : \" " + solution + " \" } " ;
2022-06-24 00:16:32 +02:00
try ( OutputStream os = connection . getOutputStream ( ) ) {
2022-01-17 14:54:11 +01:00
byte [ ] input = jsonInputString . getBytes ( StandardCharsets . UTF_8 ) ;
os . write ( input , 0 , input . length ) ;
}
2022-11-25 00:10:58 +01:00
final int responseCode = connection . getResponseCode ( ) ;
if ( checkIfRateLimitWasHit ( responseCode ) ) {
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // disconnect, as no more connections will be made for a little while
2022-11-25 00:10:58 +01:00
return null ;
}
2022-11-30 19:58:11 +01:00
if ( responseCode = = SUCCESS_HTTP_STATUS_CODE ) {
2022-12-21 22:19:34 +01:00
String result = Requester . parseJson ( connection ) ;
2022-01-17 14:54:11 +01:00
if ( result . equalsIgnoreCase ( " true " ) ) {
2022-11-30 19:58:11 +01:00
LogHelper . printDebug ( ( ) - > " Registration confirmation successful for user: " + userId ) ;
2022-01-17 14:54:11 +01:00
return userId ;
}
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to confirm registration for user: " + userId
2022-11-30 19:58:11 +01:00
+ " solution: " + solution + " response string was: " + result ) ;
2022-12-21 22:19:34 +01:00
} else {
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to confirm registration for user: " + userId
2022-12-21 22:19:34 +01:00
+ " solution: " + solution + " response code was: " + responseCode ) ;
2022-01-17 14:54:11 +01:00
}
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // something went wrong, might as well disconnect
2022-06-24 00:16:32 +02:00
} catch ( Exception ex ) {
2022-11-30 19:58:11 +01:00
LogHelper . printException ( ( ) - > " Failed to confirm registration for user: " + userId
+ " solution: " + solution , ex ) ;
2022-01-17 14:54:11 +01:00
}
return null ;
}
2022-11-25 00:10:58 +01:00
public static boolean sendVote ( String videoId , String userId , ReturnYouTubeDislike . Vote vote ) {
ReVancedUtils . verifyOffMainThread ( ) ;
Objects . requireNonNull ( videoId ) ;
Objects . requireNonNull ( userId ) ;
Objects . requireNonNull ( vote ) ;
2022-01-17 14:54:11 +01:00
try {
2022-11-30 19:58:11 +01:00
if ( checkIfRateLimitInEffect ( " sendVote " ) ) {
return false ;
}
LogHelper . printDebug ( ( ) - > " Trying to vote for video: "
+ videoId + " with vote: " + vote + " user: " + userId ) ;
2022-12-21 22:19:34 +01:00
HttpURLConnection connection = getRYDConnectionFromRoute ( ReturnYouTubeDislikeRoutes . SEND_VOTE ) ;
2022-11-30 19:58:11 +01:00
applyCommonPostRequestSettings ( connection ) ;
2022-01-17 14:54:11 +01:00
2022-11-25 00:10:58 +01:00
String voteJsonString = " { \" userId \" : \" " + userId + " \" , \" videoId \" : \" " + videoId + " \" , \" value \" : \" " + vote . value + " \" } " ;
2022-06-24 00:16:32 +02:00
try ( OutputStream os = connection . getOutputStream ( ) ) {
2022-01-17 14:54:11 +01:00
byte [ ] input = voteJsonString . getBytes ( StandardCharsets . UTF_8 ) ;
os . write ( input , 0 , input . length ) ;
}
2022-11-25 00:10:58 +01:00
final int responseCode = connection . getResponseCode ( ) ;
if ( checkIfRateLimitWasHit ( responseCode ) ) {
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // disconnect, as no more connections will be made for a little while
2022-11-25 00:10:58 +01:00
return false ;
}
2022-11-30 19:58:11 +01:00
if ( responseCode = = SUCCESS_HTTP_STATUS_CODE ) {
2022-12-21 22:19:34 +01:00
JSONObject json = Requester . parseJSONObject ( connection ) ;
2022-01-17 14:54:11 +01:00
String challenge = json . getString ( " challenge " ) ;
int difficulty = json . getInt ( " difficulty " ) ;
2022-11-25 00:10:58 +01:00
String solution = solvePuzzle ( challenge , difficulty ) ;
2022-01-17 14:54:11 +01:00
return confirmVote ( videoId , userId , solution ) ;
}
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to send vote for video: " + videoId
2022-11-30 19:58:11 +01:00
+ " userId: " + userId + " vote: " + vote + " response code was: " + responseCode ) ;
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // something went wrong, might as well disconnect
2022-06-24 00:16:32 +02:00
} catch ( Exception ex ) {
2022-11-30 19:58:11 +01:00
LogHelper . printException ( ( ) - > " Failed to send vote for video: " + videoId
+ " user: " + userId + " vote: " + vote , ex ) ;
2022-01-17 14:54:11 +01:00
}
return false ;
}
private static boolean confirmVote ( String videoId , String userId , String solution ) {
2022-11-25 00:10:58 +01:00
ReVancedUtils . verifyOffMainThread ( ) ;
Objects . requireNonNull ( videoId ) ;
Objects . requireNonNull ( userId ) ;
Objects . requireNonNull ( solution ) ;
2022-01-17 14:54:11 +01:00
try {
2022-11-30 19:58:11 +01:00
if ( checkIfRateLimitInEffect ( " confirmVote " ) ) {
return false ;
}
LogHelper . printDebug ( ( ) - > " Trying to confirm vote for video: "
+ videoId + " user: " + userId + " solution: " + solution ) ;
2022-12-21 22:19:34 +01:00
HttpURLConnection connection = getRYDConnectionFromRoute ( ReturnYouTubeDislikeRoutes . CONFIRM_VOTE ) ;
2022-11-30 19:58:11 +01:00
applyCommonPostRequestSettings ( connection ) ;
2022-01-17 14:54:11 +01:00
String jsonInputString = " { \" userId \" : \" " + userId + " \" , \" videoId \" : \" " + videoId + " \" , \" solution \" : \" " + solution + " \" } " ;
2022-06-24 00:16:32 +02:00
try ( OutputStream os = connection . getOutputStream ( ) ) {
2022-01-17 14:54:11 +01:00
byte [ ] input = jsonInputString . getBytes ( StandardCharsets . UTF_8 ) ;
os . write ( input , 0 , input . length ) ;
}
2022-11-25 00:10:58 +01:00
final int responseCode = connection . getResponseCode ( ) ;
if ( checkIfRateLimitWasHit ( responseCode ) ) {
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // disconnect, as no more connections will be made for a little while
2022-11-25 00:10:58 +01:00
return false ;
}
2022-11-30 19:58:11 +01:00
if ( responseCode = = SUCCESS_HTTP_STATUS_CODE ) {
2022-12-21 22:19:34 +01:00
String result = Requester . parseJson ( connection ) ;
2022-01-17 14:54:11 +01:00
if ( result . equalsIgnoreCase ( " true " ) ) {
2022-11-30 19:58:11 +01:00
LogHelper . printDebug ( ( ) - > " Vote confirm successful for video: " + videoId ) ;
2022-01-17 14:54:11 +01:00
return true ;
}
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to confirm vote for video: " + videoId
2022-11-30 19:58:11 +01:00
+ " user: " + userId + " solution: " + solution + " response string was: " + result ) ;
2022-12-21 22:19:34 +01:00
} else {
2023-01-28 08:38:31 +01:00
LogHelper . printException ( ( ) - > " Failed to confirm vote for video: " + videoId
2022-12-21 22:19:34 +01:00
+ " user: " + userId + " solution: " + solution + " response code was: " + responseCode ) ;
2022-01-17 14:54:11 +01:00
}
2022-12-21 22:19:34 +01:00
connection . disconnect ( ) ; // something went wrong, might as well disconnect
2022-06-24 00:16:32 +02:00
} catch ( Exception ex ) {
2022-11-30 19:58:11 +01:00
LogHelper . printException ( ( ) - > " Failed to confirm vote for video: " + videoId
+ " user: " + userId + " solution: " + solution , ex ) ;
2022-01-17 14:54:11 +01:00
}
return false ;
}
2022-11-30 19:58:11 +01:00
private static void applyCommonPostRequestSettings ( HttpURLConnection connection ) throws ProtocolException {
2022-01-17 14:54:11 +01:00
connection . setRequestMethod ( " POST " ) ;
connection . setRequestProperty ( " Content-Type " , " application/json " ) ;
connection . setRequestProperty ( " Accept " , " application/json " ) ;
2022-11-30 19:58:11 +01:00
connection . setRequestProperty ( " Pragma " , " no-cache " ) ;
connection . setRequestProperty ( " Cache-Control " , " no-cache " ) ;
connection . setUseCaches ( false ) ;
2022-01-17 14:54:11 +01:00
connection . setDoOutput ( true ) ;
2022-12-21 22:19:34 +01:00
connection . setConnectTimeout ( API_REGISTER_VOTE_TIMEOUT_MILLISECONDS ) ; // timeout for TCP connection to server
connection . setReadTimeout ( API_REGISTER_VOTE_TIMEOUT_MILLISECONDS ) ; // timeout for server response
2022-01-17 14:54:11 +01:00
}
2022-11-25 00:10:58 +01:00
private static String solvePuzzle ( String challenge , int difficulty ) {
2022-11-30 19:58:11 +01:00
final long timeSolveStarted = System . currentTimeMillis ( ) ;
2022-11-25 00:10:58 +01:00
byte [ ] decodedChallenge = Base64 . decode ( challenge , Base64 . NO_WRAP ) ;
byte [ ] buffer = new byte [ 20 ] ;
2023-01-28 08:38:31 +01:00
for ( int i = 4 ; i < 20 ; i + + ) { // FIXME replace with System.arrayCopy
2022-11-25 00:10:58 +01:00
buffer [ i ] = decodedChallenge [ i - 4 ] ;
}
2022-11-30 19:58:11 +01:00
MessageDigest md ;
2022-11-25 00:10:58 +01:00
try {
2022-11-30 19:58:11 +01:00
md = MessageDigest . getInstance ( " SHA-512 " ) ;
} catch ( NoSuchAlgorithmException ex ) {
throw new IllegalStateException ( ex ) ; // should never happen
}
final int maxCount = ( int ) ( Math . pow ( 2 , difficulty + 1 ) * 5 ) ;
for ( int i = 0 ; i < maxCount ; i + + ) {
buffer [ 0 ] = ( byte ) i ;
buffer [ 1 ] = ( byte ) ( i > > 8 ) ;
buffer [ 2 ] = ( byte ) ( i > > 16 ) ;
buffer [ 3 ] = ( byte ) ( i > > 24 ) ;
byte [ ] messageDigest = md . digest ( buffer ) ;
if ( countLeadingZeroes ( messageDigest ) > = difficulty ) {
String solution = Base64 . encodeToString ( new byte [ ] { buffer [ 0 ] , buffer [ 1 ] , buffer [ 2 ] , buffer [ 3 ] } , Base64 . NO_WRAP ) ;
LogHelper . printDebug ( ( ) - > " Found puzzle solution: " + solution + " of difficulty: " + difficulty
+ " in: " + ( System . currentTimeMillis ( ) - timeSolveStarted ) + " ms " ) ;
return solution ;
2022-11-25 00:10:58 +01:00
}
}
2022-11-30 19:58:11 +01:00
// should never be reached
throw new IllegalStateException ( " Failed to solve puzzle challenge: " + challenge + " of difficulty: " + difficulty ) ;
2022-11-25 00:10:58 +01:00
}
// https://stackoverflow.com/a/157202
private static String randomString ( int len ) {
String AB = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 " ;
SecureRandom rnd = new SecureRandom ( ) ;
StringBuilder sb = new StringBuilder ( len ) ;
for ( int i = 0 ; i < len ; i + + )
sb . append ( AB . charAt ( rnd . nextInt ( AB . length ( ) ) ) ) ;
return sb . toString ( ) ;
}
private static int countLeadingZeroes ( byte [ ] uInt8View ) {
int zeroes = 0 ;
int value = 0 ;
for ( int i = 0 ; i < uInt8View . length ; i + + ) {
value = uInt8View [ i ] & 0xFF ;
if ( value = = 0 ) {
zeroes + = 8 ;
} else {
int count = 1 ;
if ( value > > > 4 = = 0 ) {
count + = 4 ;
value < < = 4 ;
}
if ( value > > > 6 = = 0 ) {
count + = 2 ;
value < < = 2 ;
}
zeroes + = count - ( value > > > 7 ) ;
break ;
}
}
return zeroes ;
}
2022-10-06 19:39:12 +02:00
}