mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-12-12 13:37:45 +01:00
chore: merge branch dev
to main
(#323)
This commit is contained in:
commit
b612cbf2c0
@ -34,7 +34,7 @@
|
|||||||
[
|
[
|
||||||
"@saithodev/semantic-release-backmerge",
|
"@saithodev/semantic-release-backmerge",
|
||||||
{
|
{
|
||||||
backmergeBranches: [{"from": "dev", "to": "main"}],
|
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||||
clearWorkspace: true
|
clearWorkspace: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,3 +1,27 @@
|
|||||||
|
# [0.99.0-dev.4](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.3...v0.99.0-dev.4) (2023-02-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/general-ads:** check for quick actions in path instead of component identifier ([476902e](https://github.com/revanced/revanced-integrations/commit/476902e9cedbc068a815897dd22eabdb52dee84a))
|
||||||
|
* **youtube/general-ads:** use correct setting to hide related videos in quick actions ([e626bd0](https://github.com/revanced/revanced-integrations/commit/e626bd08c1249cb5594d15c77d06cae8ae27e055))
|
||||||
|
* **youtube/general-ads:** use correct setting to hide related videos in quick actions ([05bfc68](https://github.com/revanced/revanced-integrations/commit/05bfc689078beb9a21adc6c4555afe0862e304f7))
|
||||||
|
|
||||||
|
# [0.99.0-dev.3](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.2...v0.99.0-dev.3) (2023-02-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube/return-youtube-dislike:** support for shorts ([#312](https://github.com/revanced/revanced-integrations/issues/312)) ([0aef5e6](https://github.com/revanced/revanced-integrations/commit/0aef5e60e280b63490dac8d1b706e896fdf913a2))
|
||||||
|
|
||||||
|
# [0.99.0-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.99.0-dev.1...v0.99.0-dev.2) (2023-02-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube/general-ads:** hide quick actions in fullscreen ([ae862cb](https://github.com/revanced/revanced-integrations/commit/ae862cbac6f9a9b717617469d202b48923a1d3b4))
|
||||||
|
* **youtube/general-ads:** hide related videos in quick action ([cfc571c](https://github.com/revanced/revanced-integrations/commit/cfc571c12cb012b86c8bfa8bf7df1c77b9711a21))
|
||||||
|
|
||||||
# [0.99.0-dev.1](https://github.com/revanced/revanced-integrations/compare/v0.98.0...v0.99.0-dev.1) (2023-02-22)
|
# [0.99.0-dev.1](https://github.com/revanced/revanced-integrations/compare/v0.98.0...v0.99.0-dev.1) (2023-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ public final class GeneralAdsPatch extends Filter {
|
|||||||
var webLinkPanel = new BlockRule(SettingsEnum.ADREMOVER_WEB_SEARCH_RESULTS, "web_link_panel");
|
var webLinkPanel = new BlockRule(SettingsEnum.ADREMOVER_WEB_SEARCH_RESULTS, "web_link_panel");
|
||||||
var horizontalVideoShelf = new BlockRule(SettingsEnum.ADREMOVER_HORIZONTAL_VIDEO_SHELF, "horizontal_video_shelf");
|
var horizontalVideoShelf = new BlockRule(SettingsEnum.ADREMOVER_HORIZONTAL_VIDEO_SHELF, "horizontal_video_shelf");
|
||||||
var channelBar = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "channel_bar");
|
var channelBar = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "channel_bar");
|
||||||
|
var relatedVideos = new BlockRule(SettingsEnum.ADREMOVER_RELATED_VIDEOS, "fullscreen_related_videos");
|
||||||
|
var quickActions = new BlockRule(SettingsEnum.ADREMOVER_QUICK_ACTIONS, "quick_actions");
|
||||||
var graySeparator = new BlockRule(SettingsEnum.ADREMOVER_GRAY_SEPARATOR,
|
var graySeparator = new BlockRule(SettingsEnum.ADREMOVER_GRAY_SEPARATOR,
|
||||||
"cell_divider" // layout residue (gray line above the buttoned ad),
|
"cell_divider" // layout residue (gray line above the buttoned ad),
|
||||||
);
|
);
|
||||||
@ -78,6 +80,8 @@ public final class GeneralAdsPatch extends Filter {
|
|||||||
movieAds,
|
movieAds,
|
||||||
chapterTeaser,
|
chapterTeaser,
|
||||||
communityGuidelines,
|
communityGuidelines,
|
||||||
|
quickActions,
|
||||||
|
relatedVideos,
|
||||||
compactBanner,
|
compactBanner,
|
||||||
inFeedSurvey,
|
inFeedSurvey,
|
||||||
viewProducts,
|
viewProducts,
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
package app.revanced.integrations.patches;
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import android.text.Spanned;
|
||||||
|
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by app.revanced.patches.youtube.layout.returnyoutubedislike.patch.ReturnYouTubeDislikePatch
|
* Used by app.revanced.patches.youtube.layout.returnyoutubedislike.patch.ReturnYouTubeDislikePatch
|
||||||
*/
|
*/
|
||||||
public class ReturnYouTubeDislikePatch {
|
public class ReturnYouTubeDislikePatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the video id changes
|
* Injection point
|
||||||
*/
|
*/
|
||||||
public static void newVideoLoaded(String videoId) {
|
public static void newVideoLoaded(String videoId) {
|
||||||
ReturnYouTubeDislike.newVideoLoaded(videoId);
|
ReturnYouTubeDislike.newVideoLoaded(videoId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Injection point
|
||||||
|
*
|
||||||
* Called when a litho text component is created
|
* Called when a litho text component is created
|
||||||
*/
|
*/
|
||||||
public static void onComponentCreated(Object conversionContext, AtomicReference<Object> textRef) {
|
public static void onComponentCreated(Object conversionContext, AtomicReference<Object> textRef) {
|
||||||
@ -24,16 +27,22 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Injection point
|
||||||
|
*
|
||||||
|
* Called when a Shorts dislike Spannable is created
|
||||||
|
*/
|
||||||
|
public static Spanned onShortsComponentCreated(Spanned dislike) {
|
||||||
|
return ReturnYouTubeDislike.onShortsComponentCreated(dislike);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point
|
||||||
|
*
|
||||||
* Called when the like/dislike button is clicked
|
* Called when the like/dislike button is clicked
|
||||||
*
|
*
|
||||||
* @param vote -1 (dislike), 0 (none) or 1 (like)
|
* @param vote -1 (dislike), 0 (none) or 1 (like)
|
||||||
*/
|
*/
|
||||||
public static void sendVote(int vote) {
|
public static void sendVote(int vote) {
|
||||||
for (ReturnYouTubeDislike.Vote v : ReturnYouTubeDislike.Vote.values()) {
|
ReturnYouTubeDislike.sendVote(vote);
|
||||||
if (v.value == vote) {
|
|
||||||
ReturnYouTubeDislike.sendVote(v);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,31 @@
|
|||||||
package app.revanced.integrations.returnyoutubedislike;
|
package app.revanced.integrations.returnyoutubedislike;
|
||||||
|
|
||||||
import static app.revanced.integrations.sponsorblock.StringRef.str;
|
|
||||||
|
|
||||||
import android.icu.text.CompactDecimalFormat;
|
import android.icu.text.CompactDecimalFormat;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.Spannable;
|
import android.text.*;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.CharacterStyle;
|
import android.text.style.CharacterStyle;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.RelativeSizeSpan;
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.text.style.ScaleXSpan;
|
import android.text.style.ScaleXSpan;
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import app.revanced.integrations.returnyoutubedislike.requests.RYDVoteData;
|
import app.revanced.integrations.returnyoutubedislike.requests.RYDVoteData;
|
||||||
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.shared.PlayerType;
|
import app.revanced.integrations.shared.PlayerType;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
import app.revanced.integrations.utils.SharedPrefHelper;
|
|
||||||
import app.revanced.integrations.utils.ThemeHelper;
|
import app.revanced.integrations.utils.ThemeHelper;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.sponsorblock.StringRef.str;
|
||||||
|
|
||||||
public class ReturnYouTubeDislike {
|
public class ReturnYouTubeDislike {
|
||||||
/**
|
/**
|
||||||
* Maximum amount of time to block the UI from updates while waiting for network call to complete.
|
* Maximum amount of time to block the UI from updates while waiting for network call to complete.
|
||||||
@ -42,31 +33,56 @@ public class ReturnYouTubeDislike {
|
|||||||
* Must be less than 5 seconds, as per:
|
* Must be less than 5 seconds, as per:
|
||||||
* https://developer.android.com/topic/performance/vitals/anr
|
* https://developer.android.com/topic/performance/vitals/anr
|
||||||
*/
|
*/
|
||||||
private static final long MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE = 4000;
|
private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE = 4000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator character to use for segmented like/dislike
|
||||||
|
*/
|
||||||
|
private static final char MIDDLE_SEPARATOR_CHARACTER = '•';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to send votes, one by one, in the same order the user created them
|
* Used to send votes, one by one, in the same order the user created them
|
||||||
*/
|
*/
|
||||||
private static final ExecutorService voteSerialExecutor = Executors.newSingleThreadExecutor();
|
private static final ExecutorService voteSerialExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
// Must be volatile, since this is read/write from different threads
|
|
||||||
private static volatile boolean isEnabled = SettingsEnum.RYD_ENABLED.getBoolean();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to guard {@link #currentVideoId} and {@link #voteFetchFuture},
|
* Used to guard {@link #currentVideoId} and {@link #voteFetchFuture},
|
||||||
* as multiple threads access this class.
|
* as multiple threads access this class.
|
||||||
*/
|
*/
|
||||||
private static final Object videoIdLockObject = new Object();
|
private static final Object videoIdLockObject = new Object();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@GuardedBy("videoIdLockObject")
|
@GuardedBy("videoIdLockObject")
|
||||||
private static String currentVideoId;
|
private static String currentVideoId;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #currentVideoId} and the RYD data is for the last shorts loaded
|
||||||
|
*/
|
||||||
|
private static volatile boolean lastVideoLoadedWasShort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the results of the vote api fetch, and used as a barrier to wait until fetch completes
|
* Stores the results of the vote api fetch, and used as a barrier to wait until fetch completes
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
@GuardedBy("videoIdLockObject")
|
@GuardedBy("videoIdLockObject")
|
||||||
private static Future<RYDVoteData> voteFetchFuture;
|
private static Future<RYDVoteData> voteFetchFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original dislike span, before modifications.
|
||||||
|
* Required for segmented layout
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@GuardedBy("videoIdLockObject")
|
||||||
|
private static Spanned originalDislikeSpan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replacement like/dislike span that includes formatted dislikes and is ready to display
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@GuardedBy("videoIdLockObject")
|
||||||
|
private static Spanned replacementLikeDislikeSpan;
|
||||||
|
|
||||||
public enum Vote {
|
public enum Vote {
|
||||||
LIKE(1),
|
LIKE(1),
|
||||||
DISLIKE(-1),
|
DISLIKE(-1),
|
||||||
@ -95,44 +111,58 @@ public class ReturnYouTubeDislike {
|
|||||||
private static NumberFormat dislikePercentageFormatter;
|
private static NumberFormat dislikePercentageFormatter;
|
||||||
|
|
||||||
public static void onEnabledChange(boolean enabled) {
|
public static void onEnabledChange(boolean enabled) {
|
||||||
synchronized (videoIdLockObject) {
|
if (!enabled) {
|
||||||
isEnabled = enabled;
|
// Must clear old values, to protect against using stale data
|
||||||
if (!enabled) {
|
// if the user re-enables RYD while watching a video.
|
||||||
// must clear old values, to protect against using stale data
|
setCurrentVideoId(null);
|
||||||
// if the user re-enables RYD while watching a video
|
|
||||||
LogHelper.printDebug(() -> "Clearing previously fetched RYD vote data");
|
|
||||||
currentVideoId = null;
|
|
||||||
voteFetchFuture = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setCurrentVideoId(@Nullable String videoId) {
|
||||||
|
synchronized (videoIdLockObject) {
|
||||||
|
if (videoId == null && currentVideoId != null) {
|
||||||
|
LogHelper.printDebug(() -> "Clearing data");
|
||||||
|
}
|
||||||
|
currentVideoId = videoId;
|
||||||
|
lastVideoLoadedWasShort = false;
|
||||||
|
voteFetchFuture = null;
|
||||||
|
originalDislikeSpan = null;
|
||||||
|
replacementLikeDislikeSpan = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static String getCurrentVideoId() {
|
private static String getCurrentVideoId() {
|
||||||
synchronized (videoIdLockObject) {
|
synchronized (videoIdLockObject) {
|
||||||
return currentVideoId;
|
return currentVideoId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static Future<RYDVoteData> getVoteFetchFuture() {
|
private static Future<RYDVoteData> getVoteFetchFuture() {
|
||||||
synchronized (videoIdLockObject) {
|
synchronized (videoIdLockObject) {
|
||||||
return voteFetchFuture;
|
return voteFetchFuture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is unclear if this method is always called on the main thread (since the YouTube app is the one making the call)
|
public static void newVideoLoaded(@NonNull String videoId) {
|
||||||
// treat this as if any thread could call this method
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
||||||
public static void newVideoLoaded(String videoId) {
|
|
||||||
if (!isEnabled) return;
|
|
||||||
try {
|
try {
|
||||||
Objects.requireNonNull(videoId);
|
Objects.requireNonNull(videoId);
|
||||||
|
|
||||||
PlayerType currentPlayerType = PlayerType.getCurrent();
|
PlayerType currentPlayerType = PlayerType.getCurrent();
|
||||||
if (currentPlayerType == PlayerType.INLINE_MINIMAL) {
|
if (currentPlayerType == PlayerType.INLINE_MINIMAL) {
|
||||||
LogHelper.printDebug(() -> "Ignoring inline playback of video: "+ videoId);
|
LogHelper.printDebug(() -> "Ignoring inline playback of video: " + videoId);
|
||||||
|
setCurrentVideoId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LogHelper.printDebug(() -> " new video loaded: " + videoId + " playerType: " + currentPlayerType);
|
|
||||||
synchronized (videoIdLockObject) {
|
synchronized (videoIdLockObject) {
|
||||||
currentVideoId = videoId;
|
if (videoId.equals(currentVideoId)) {
|
||||||
|
return; // already loaded
|
||||||
|
}
|
||||||
|
LogHelper.printDebug(() -> "New video loaded: " + videoId + " playerType: " + currentPlayerType);
|
||||||
|
setCurrentVideoId(videoId);
|
||||||
// no need to wrap the call in a try/catch,
|
// no need to wrap the call in a try/catch,
|
||||||
// as any exceptions are propagated out in the later Future#Get call
|
// as any exceptions are propagated out in the later Future#Get call
|
||||||
voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
|
voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
|
||||||
@ -144,86 +174,149 @@ public class ReturnYouTubeDislike {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
|
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
|
||||||
* <p>
|
|
||||||
* This method can be called multiple times for the same UI element (including after dislikes was added)
|
* This method can be called multiple times for the same UI element (including after dislikes was added)
|
||||||
* This code should avoid needlessly replacing the same UI element with identical versions.
|
|
||||||
*/
|
*/
|
||||||
public static void onComponentCreated(Object conversionContext, AtomicReference<Object> textRef) {
|
public static void onComponentCreated(@NonNull Object conversionContext, @NonNull AtomicReference<Object> textRef) {
|
||||||
if (!isEnabled) return;
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
||||||
|
|
||||||
|
// do not set lastVideoLoadedWasShort to false. It will be cleared when the next regular video is loaded.
|
||||||
|
if (lastVideoLoadedWasShort) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (PlayerType.getCurrent().isNoneOrHidden()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String conversionContextString = conversionContext.toString();
|
||||||
|
final boolean isSegmentedButton;
|
||||||
|
if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) {
|
||||||
|
isSegmentedButton = true;
|
||||||
|
} else if (conversionContextString.contains("|dislike_button.eml|")) {
|
||||||
|
isSegmentedButton = false;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String conversionContextString = conversionContext.toString();
|
Spanned replacement = waitForFetchAndUpdateReplacementSpan((Spanned) textRef.get(), isSegmentedButton);
|
||||||
|
if (replacement != null) {
|
||||||
final boolean isSegmentedButton;
|
textRef.set(replacement);
|
||||||
if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) {
|
|
||||||
isSegmentedButton = true;
|
|
||||||
} else if (conversionContextString.contains("|dislike_button.eml|")) {
|
|
||||||
isSegmentedButton = false;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Have to block the current thread until fetching is done
|
|
||||||
// There's no known way to edit the text after creation yet
|
|
||||||
RYDVoteData votingData;
|
|
||||||
long fetchStartTime = 0;
|
|
||||||
try {
|
|
||||||
Future<RYDVoteData> fetchFuture = getVoteFetchFuture();
|
|
||||||
if (fetchFuture == null) {
|
|
||||||
LogHelper.printDebug(() -> "fetch future not available (user enabled RYD while video was playing?)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (SettingsEnum.DEBUG.getBoolean() && !fetchFuture.isDone()) {
|
|
||||||
fetchStartTime = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
votingData = fetchFuture.get(MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
LogHelper.printDebug(() -> "UI timed out waiting for fetch votes to complete");
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
recordTimeUISpentWaitingForNetworkCall(fetchStartTime);
|
|
||||||
}
|
|
||||||
if (votingData == null) {
|
|
||||||
LogHelper.printDebug(() -> "Cannot add dislike to UI (RYD data not available)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateDislike(textRef, isSegmentedButton, votingData)) {
|
|
||||||
LogHelper.printDebug(() -> "Updated dislike span to: " + textRef.get());
|
|
||||||
} else {
|
|
||||||
LogHelper.printDebug(() -> "Ignoring already updated dislike span: " + textRef.get());
|
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Error while trying to update dislikes", ex);
|
LogHelper.printException(() -> "Error while trying to update dislikes", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendVote(Vote vote) {
|
public static void sendVote(int vote) {
|
||||||
if (!isEnabled) return;
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Objects.requireNonNull(vote);
|
for (ReturnYouTubeDislike.Vote v : ReturnYouTubeDislike.Vote.values()) {
|
||||||
|
if (v.value == vote) {
|
||||||
if (PlayerType.getCurrent() == PlayerType.NONE) { // should occur if shorts is playing
|
ReturnYouTubeDislike.sendVote(v);
|
||||||
LogHelper.printDebug(() -> "Ignoring vote during Shorts playback");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
if (SharedPrefHelper.getBoolean(SharedPrefHelper.SharedPrefNames.YOUTUBE, "user_signed_out", true)) {
|
LogHelper.printException(() -> "Unknown vote type: " + vote);
|
||||||
LogHelper.printDebug(() -> "User is logged out, ignoring voting");
|
} catch (Exception ex) {
|
||||||
return;
|
LogHelper.printException(() -> "sendVote failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Spanned onShortsComponentCreated(Spanned span) {
|
||||||
|
try {
|
||||||
|
if (SettingsEnum.RYD_ENABLED.getBoolean()) {
|
||||||
|
lastVideoLoadedWasShort = true;
|
||||||
|
Spanned replacement = waitForFetchAndUpdateReplacementSpan(span, false);
|
||||||
|
if (replacement != null) {
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "onShortsComponentCreated failure", ex);
|
||||||
|
}
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPreviouslyCreatedSegmentedSpan(Spanned span) {
|
||||||
|
return span.toString().indexOf(MIDDLE_SEPARATOR_CHARACTER) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return NULL if the span does not need changing or if RYD is not available
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Spanned waitForFetchAndUpdateReplacementSpan(Spanned oldSpannable, boolean isSegmentedButton) {
|
||||||
|
if (oldSpannable == null) {
|
||||||
|
LogHelper.printDebug(() -> "Cannot add dislikes (injection code was called with null Span)");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Must block the current thread until fetching is done
|
||||||
|
// There's no known way to edit the text after creation yet
|
||||||
|
long fetchStartTime = 0;
|
||||||
|
try {
|
||||||
|
synchronized (videoIdLockObject) {
|
||||||
|
if (oldSpannable == replacementLikeDislikeSpan) {
|
||||||
|
LogHelper.printDebug(() -> "Ignoring previously created dislike span");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isSegmentedButton) {
|
||||||
|
if (isPreviouslyCreatedSegmentedSpan(oldSpannable)) {
|
||||||
|
// need to recreate using original, as oldSpannable has prior outdated dislike values
|
||||||
|
oldSpannable = originalDislikeSpan;
|
||||||
|
} else {
|
||||||
|
originalDislikeSpan = oldSpannable; // most up to date original
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<RYDVoteData> fetchFuture = getVoteFetchFuture();
|
||||||
|
if (fetchFuture == null) {
|
||||||
|
LogHelper.printDebug(() -> "fetch future not available (user enabled RYD while video was playing?)");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (SettingsEnum.DEBUG.getBoolean() && !fetchFuture.isDone()) {
|
||||||
|
fetchStartTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
RYDVoteData votingData = fetchFuture.get(MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS);
|
||||||
|
if (votingData == null) {
|
||||||
|
LogHelper.printDebug(() -> "Cannot add dislike to UI (RYD data not available)");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Spanned replacement = createDislikeSpan(oldSpannable, isSegmentedButton, votingData);
|
||||||
|
synchronized (videoIdLockObject) {
|
||||||
|
replacementLikeDislikeSpan = replacement;
|
||||||
|
}
|
||||||
|
final Spanned oldSpannableLogging = oldSpannable;
|
||||||
|
LogHelper.printDebug(() -> "Replaced: '" + oldSpannableLogging + "' with: '" + replacement + "'");
|
||||||
|
return replacement;
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
LogHelper.printDebug(() -> "UI timed out while waiting for fetch votes to complete"); // show no toast
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogHelper.printException(() -> "createReplacementSpan failure", e); // should never happen
|
||||||
|
} finally {
|
||||||
|
recordTimeUISpentWaitingForNetworkCall(fetchStartTime);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendVote(@NonNull Vote vote) {
|
||||||
|
ReVancedUtils.verifyOnMainThread();
|
||||||
|
Objects.requireNonNull(vote);
|
||||||
|
try {
|
||||||
// Must make a local copy of videoId, since it may change between now and when the vote thread runs
|
// Must make a local copy of videoId, since it may change between now and when the vote thread runs
|
||||||
String videoIdToVoteFor = getCurrentVideoId();
|
String videoIdToVoteFor = getCurrentVideoId();
|
||||||
if (videoIdToVoteFor == null) {
|
if (videoIdToVoteFor == null || (lastVideoLoadedWasShort && !PlayerType.getCurrent().isNoneOrHidden())) {
|
||||||
// user enabled RYD after starting playback of a video
|
// User enabled RYD after starting playback of a video.
|
||||||
LogHelper.printException(() -> "Cannot vote, current video is is null (user enabled RYD while video was playing?)",
|
// Or shorts was loaded with regular video present, then shorts was closed, and then user voted on the now visible original video
|
||||||
|
LogHelper.printException(() -> "Cannot send vote",
|
||||||
null, str("revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted"));
|
null, str("revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
voteSerialExecutor.execute(() -> {
|
voteSerialExecutor.execute(() -> {
|
||||||
// must wrap in try/catch to properly log exceptions
|
try { // must wrap in try/catch to properly log exceptions
|
||||||
try {
|
|
||||||
String userId = getUserId();
|
String userId = getUserId();
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
ReturnYouTubeDislikeApi.sendVote(videoIdToVoteFor, userId, vote);
|
ReturnYouTubeDislikeApi.sendVote(videoIdToVoteFor, userId, vote);
|
||||||
@ -232,8 +325,27 @@ public class ReturnYouTubeDislike {
|
|||||||
LogHelper.printException(() -> "Failed to send vote", ex);
|
LogHelper.printException(() -> "Failed to send vote", ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// update the downloaded vote data
|
||||||
|
synchronized (videoIdLockObject) {
|
||||||
|
replacementLikeDislikeSpan = null; // ui values need updating
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<RYDVoteData> future = getVoteFetchFuture();
|
||||||
|
if (future == null) {
|
||||||
|
LogHelper.printException(() -> "Cannot update UI dislike count - vote fetch is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// the future should always be completed before user can like/dislike, but use a timeout just in case
|
||||||
|
RYDVoteData voteData = future.get(MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS);
|
||||||
|
if (voteData == null) {
|
||||||
|
// RYD fetch failed
|
||||||
|
LogHelper.printDebug(() -> "Cannot update UI (vote data not available)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
voteData.updateUsingVote(vote);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Error while trying to send vote", ex);
|
LogHelper.printException(() -> "Error trying to send vote", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,122 +373,104 @@ public class ReturnYouTubeDislike {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param isSegmentedButton if UI is using the segmented single UI component for both like and dislike
|
* @param isSegmentedButton if UI is using the segmented single UI component for both like and dislike
|
||||||
* @return false, if the text reference already has dislike information and no changes were made.
|
|
||||||
*/
|
*/
|
||||||
private static boolean updateDislike(AtomicReference<Object> textRef, boolean isSegmentedButton, RYDVoteData voteData) {
|
private static Spanned createDislikeSpan(Spanned oldSpannable, boolean isSegmentedButton, RYDVoteData voteData) {
|
||||||
Spannable oldSpannable = (Spannable) textRef.get();
|
if (!isSegmentedButton) {
|
||||||
String oldLikesString = oldSpannable.toString();
|
// simple replacement of 'dislike' with a number/percentage
|
||||||
Spannable replacementSpannable;
|
return newSpannableWithDislikes(oldSpannable, voteData);
|
||||||
|
}
|
||||||
|
|
||||||
// note: some locales use right to left layout (arabic, hebrew, etc),
|
// note: some locales use right to left layout (arabic, hebrew, etc),
|
||||||
// and care must be taken to retain the existing RTL encoding character on the likes string
|
// and care must be taken to retain the existing RTL encoding character on the likes string
|
||||||
// otherwise text will incorrectly show as left to right
|
// otherwise text will incorrectly show as left to right
|
||||||
// if making changes to this code, change device settings to a RTL language and verify layout is correct
|
// if making changes to this code, change device settings to a RTL language and verify layout is correct
|
||||||
|
String oldLikesString = oldSpannable.toString();
|
||||||
|
|
||||||
if (!isSegmentedButton) {
|
// YouTube creators can hide the like count on a video,
|
||||||
// simple replacement of 'dislike' with a number/percentage
|
// and the like count appears as a device language specific string that says 'Like'
|
||||||
if (stringContainsNumber(oldLikesString)) {
|
// check if the string contains any numbers
|
||||||
// already is a number, and was modified in a previous call to this method
|
if (!stringContainsNumber(oldLikesString)) {
|
||||||
return false;
|
// likes are hidden.
|
||||||
}
|
// RYD does not provide usable data for these types of videos,
|
||||||
replacementSpannable = newSpannableWithDislikes(oldSpannable, voteData);
|
// and the API returns bogus data (zero likes and zero dislikes)
|
||||||
} else {
|
//
|
||||||
final boolean useCompactLayout = SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean();
|
// example video: https://www.youtube.com/watch?v=UnrU5vxCHxw
|
||||||
// if compact layout, use a "half space" character
|
// RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw
|
||||||
String middleSegmentedSeparatorString = useCompactLayout ? "\u2009 • \u2009" : " • ";
|
//
|
||||||
|
// discussion about this: https://github.com/Anarios/return-youtube-dislike/discussions/530
|
||||||
|
|
||||||
if (oldLikesString.contains(middleSegmentedSeparatorString)) {
|
//
|
||||||
return false; // dislikes was previously added
|
// Change the "Likes" string to show that likes and dislikes are hidden
|
||||||
}
|
//
|
||||||
|
String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner");
|
||||||
// YouTube creators can hide the like count on a video,
|
return newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString);
|
||||||
// and the like count appears as a device language specific string that says 'Like'
|
|
||||||
// check if the string contains any numbers
|
|
||||||
if (!stringContainsNumber(oldLikesString)) {
|
|
||||||
// likes are hidden.
|
|
||||||
// RYD does not provide usable data for these types of videos,
|
|
||||||
// and the API returns bogus data (zero likes and zero dislikes)
|
|
||||||
//
|
|
||||||
// example video: https://www.youtube.com/watch?v=UnrU5vxCHxw
|
|
||||||
// RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw
|
|
||||||
//
|
|
||||||
// discussion about this: https://github.com/Anarios/return-youtube-dislike/discussions/530
|
|
||||||
|
|
||||||
//
|
|
||||||
// Change the "Likes" string to show that likes and dislikes are hidden
|
|
||||||
//
|
|
||||||
String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner");
|
|
||||||
if (hiddenMessageString.equals(oldLikesString)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
replacementSpannable = newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString);
|
|
||||||
} else {
|
|
||||||
Spannable likesSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, oldLikesString);
|
|
||||||
|
|
||||||
// middle separator
|
|
||||||
Spannable middleSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, middleSegmentedSeparatorString);
|
|
||||||
final int separatorColor = ThemeHelper.isDarkTheme()
|
|
||||||
? 0x29AAAAAA // transparent dark gray
|
|
||||||
: 0xFFD9D9D9; // light gray
|
|
||||||
addSpanStyling(middleSeparatorSpan, new ForegroundColorSpan(separatorColor));
|
|
||||||
CharacterStyle noAntiAliasingStyle = new CharacterStyle() {
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.setAntiAlias(false); // draw without anti-aliasing, to give a sharper edge
|
|
||||||
}
|
|
||||||
};
|
|
||||||
addSpanStyling(middleSeparatorSpan, noAntiAliasingStyle);
|
|
||||||
|
|
||||||
Spannable dislikeSpan = newSpannableWithDislikes(oldSpannable, voteData);
|
|
||||||
|
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
|
||||||
|
|
||||||
if (!useCompactLayout) {
|
|
||||||
String leftSegmentedSeparatorString = ReVancedUtils.isRightToLeftTextLayout() ? "\u200F| " : "| ";
|
|
||||||
Spannable leftSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, leftSegmentedSeparatorString);
|
|
||||||
addSpanStyling(leftSeparatorSpan, new ForegroundColorSpan(separatorColor));
|
|
||||||
addSpanStyling(leftSeparatorSpan, noAntiAliasingStyle);
|
|
||||||
|
|
||||||
// Use a left separator with a larger font and visually match the stock right separator.
|
|
||||||
// But with a larger font, the entire span (including the like/dislike text) becomes shifted downward.
|
|
||||||
// To correct this, use additional spans to move the alignment back upward by a relative amount.
|
|
||||||
setSegmentedAdjustmentValues();
|
|
||||||
class RelativeVerticalOffsetSpan extends CharacterStyle {
|
|
||||||
final float relativeVerticalShiftRatio;
|
|
||||||
|
|
||||||
RelativeVerticalOffsetSpan(float relativeVerticalShiftRatio) {
|
|
||||||
this.relativeVerticalShiftRatio = relativeVerticalShiftRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.baselineShift -= (int) (relativeVerticalShiftRatio * tp.getFontMetrics().top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// each section needs it's own Relative span, otherwise alignment is wrong
|
|
||||||
addSpanStyling(leftSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedLeftSeparatorVerticalShiftRatio));
|
|
||||||
|
|
||||||
addSpanStyling(likesSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio));
|
|
||||||
addSpanStyling(middleSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio));
|
|
||||||
addSpanStyling(dislikeSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio));
|
|
||||||
|
|
||||||
// important: must add size scaling after vertical offset (otherwise alignment gets off)
|
|
||||||
addSpanStyling(leftSeparatorSpan, new RelativeSizeSpan(segmentedLeftSeparatorFontRatio));
|
|
||||||
addSpanStyling(leftSeparatorSpan, new ScaleXSpan(segmentedLeftSeparatorHorizontalScaleRatio));
|
|
||||||
// middle separator does not need resizing
|
|
||||||
|
|
||||||
builder.append(leftSeparatorSpan);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append(likesSpan);
|
|
||||||
builder.append(middleSeparatorSpan);
|
|
||||||
builder.append(dislikeSpan);
|
|
||||||
replacementSpannable = new SpannableString(builder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textRef.set(replacementSpannable);
|
Spannable likesSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, oldLikesString);
|
||||||
return true;
|
|
||||||
|
// middle separator
|
||||||
|
final boolean useCompactLayout = SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean();
|
||||||
|
final int separatorColor = ThemeHelper.isDarkTheme()
|
||||||
|
? 0x29AAAAAA // transparent dark gray
|
||||||
|
: 0xFFD9D9D9; // light gray
|
||||||
|
|
||||||
|
String middleSegmentedSeparatorString = useCompactLayout
|
||||||
|
? "\u2009 " + MIDDLE_SEPARATOR_CHARACTER + " \u2009" // u2009 = "half space" character
|
||||||
|
: " " + MIDDLE_SEPARATOR_CHARACTER + " ";
|
||||||
|
Spannable middleSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, middleSegmentedSeparatorString);
|
||||||
|
addSpanStyling(middleSeparatorSpan, new ForegroundColorSpan(separatorColor));
|
||||||
|
CharacterStyle noAntiAliasingStyle = new CharacterStyle() {
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint tp) {
|
||||||
|
tp.setAntiAlias(false); // draw without anti-aliasing, to give a sharper edge
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addSpanStyling(middleSeparatorSpan, noAntiAliasingStyle);
|
||||||
|
|
||||||
|
Spannable dislikeSpan = newSpannableWithDislikes(oldSpannable, voteData);
|
||||||
|
|
||||||
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
|
if (!useCompactLayout) {
|
||||||
|
String leftSegmentedSeparatorString = ReVancedUtils.isRightToLeftTextLayout() ? "\u200F| " : "| "; // u200f = right to left character
|
||||||
|
Spannable leftSeparatorSpan = newSpanUsingStylingOfAnotherSpan(oldSpannable, leftSegmentedSeparatorString);
|
||||||
|
addSpanStyling(leftSeparatorSpan, new ForegroundColorSpan(separatorColor));
|
||||||
|
addSpanStyling(leftSeparatorSpan, noAntiAliasingStyle);
|
||||||
|
|
||||||
|
// Use a left separator with a larger font and visually match the stock right separator.
|
||||||
|
// But with a larger font, the entire span (including the like/dislike text) becomes shifted downward.
|
||||||
|
// To correct this, use additional spans to move the alignment back upward by a relative amount.
|
||||||
|
setSegmentedAdjustmentValues();
|
||||||
|
class RelativeVerticalOffsetSpan extends CharacterStyle {
|
||||||
|
final float relativeVerticalShiftRatio;
|
||||||
|
|
||||||
|
RelativeVerticalOffsetSpan(float relativeVerticalShiftRatio) {
|
||||||
|
this.relativeVerticalShiftRatio = relativeVerticalShiftRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrawState(TextPaint tp) {
|
||||||
|
tp.baselineShift -= (int) (relativeVerticalShiftRatio * tp.getFontMetrics().top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// each section needs it's own Relative span, otherwise alignment is wrong
|
||||||
|
addSpanStyling(leftSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedLeftSeparatorVerticalShiftRatio));
|
||||||
|
|
||||||
|
addSpanStyling(likesSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio));
|
||||||
|
addSpanStyling(middleSeparatorSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio));
|
||||||
|
addSpanStyling(dislikeSpan, new RelativeVerticalOffsetSpan(segmentedVerticalShiftRatio));
|
||||||
|
|
||||||
|
// important: must add size scaling after vertical offset (otherwise alignment gets off)
|
||||||
|
addSpanStyling(leftSeparatorSpan, new RelativeSizeSpan(segmentedLeftSeparatorFontRatio));
|
||||||
|
addSpanStyling(leftSeparatorSpan, new ScaleXSpan(segmentedLeftSeparatorHorizontalScaleRatio));
|
||||||
|
// middle separator does not need resizing
|
||||||
|
|
||||||
|
builder.append(leftSeparatorSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(likesSpan);
|
||||||
|
builder.append(middleSeparatorSpan);
|
||||||
|
builder.append(dislikeSpan);
|
||||||
|
return new SpannableString(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean segmentedValuesSet = false;
|
private static boolean segmentedValuesSet = false;
|
||||||
@ -455,14 +549,14 @@ public class ReturnYouTubeDislike {
|
|||||||
destination.setSpan(styling, 0, destination.length(), 0);
|
destination.setSpan(styling, 0, destination.length(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Spannable newSpannableWithDislikes(Spannable sourceStyling, RYDVoteData voteData) {
|
private static Spannable newSpannableWithDislikes(Spanned sourceStyling, RYDVoteData voteData) {
|
||||||
return newSpanUsingStylingOfAnotherSpan(sourceStyling,
|
return newSpanUsingStylingOfAnotherSpan(sourceStyling,
|
||||||
SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean()
|
SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean()
|
||||||
? formatDislikePercentage(voteData.dislikePercentage)
|
? formatDislikePercentage(voteData.getDislikePercentage())
|
||||||
: formatDislikeCount(voteData.dislikeCount));
|
: formatDislikeCount(voteData.getDislikeCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Spannable newSpanUsingStylingOfAnotherSpan(Spannable sourceStyle, String newSpanText) {
|
private static Spannable newSpanUsingStylingOfAnotherSpan(Spanned sourceStyle, String newSpanText) {
|
||||||
SpannableString destination = new SpannableString(newSpanText);
|
SpannableString destination = new SpannableString(newSpanText);
|
||||||
Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class);
|
Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class);
|
||||||
for (Object span : spans) {
|
for (Object span : spans) {
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package app.revanced.integrations.returnyoutubedislike.requests;
|
package app.revanced.integrations.returnyoutubedislike.requests;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.util.Objects;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ReturnYouTubeDislike API estimated like/dislike/view counts.
|
* ReturnYouTubeDislike API estimated like/dislike/view counts.
|
||||||
@ -12,7 +16,7 @@ import java.util.Objects;
|
|||||||
* So these values may lag behind what YouTube shows.
|
* So these values may lag behind what YouTube shows.
|
||||||
*/
|
*/
|
||||||
public final class RYDVoteData {
|
public final class RYDVoteData {
|
||||||
|
@NonNull
|
||||||
public final String videoId;
|
public final String videoId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,46 +24,87 @@ public final class RYDVoteData {
|
|||||||
*/
|
*/
|
||||||
public final long viewCount;
|
public final long viewCount;
|
||||||
|
|
||||||
|
private final long fetchedLikeCount;
|
||||||
|
private volatile long likeCount; // read/write from different threads
|
||||||
|
private volatile float likePercentage;
|
||||||
|
|
||||||
|
private final long fetchedDislikeCount;
|
||||||
|
private volatile long dislikeCount; // read/write from different threads
|
||||||
|
private volatile float dislikePercentage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws JSONException if JSON parse error occurs, or if the values make no sense (ie: negative values)
|
||||||
|
*/
|
||||||
|
public RYDVoteData(@NonNull JSONObject json) throws JSONException {
|
||||||
|
videoId = json.getString("id");
|
||||||
|
viewCount = json.getLong("viewCount");
|
||||||
|
fetchedLikeCount = json.getLong("likes");
|
||||||
|
fetchedDislikeCount = json.getLong("dislikes");
|
||||||
|
if (viewCount < 0 || fetchedLikeCount < 0 || fetchedDislikeCount < 0) {
|
||||||
|
throw new JSONException("Unexpected JSON values: " + json);
|
||||||
|
}
|
||||||
|
likeCount = fetchedLikeCount;
|
||||||
|
dislikeCount = fetchedDislikeCount;
|
||||||
|
updatePercentages();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimated like count
|
* Estimated like count
|
||||||
*/
|
*/
|
||||||
public final long likeCount;
|
public long getLikeCount() {
|
||||||
|
return likeCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimated dislike count
|
* Estimated dislike count
|
||||||
*/
|
*/
|
||||||
public final long dislikeCount;
|
public long getDislikeCount() {
|
||||||
|
return dislikeCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimated percentage of likes for all votes. Value has range of [0, 1]
|
* Estimated percentage of likes for all votes. Value has range of [0, 1]
|
||||||
*
|
*
|
||||||
* A video with 400 positive votes, and 100 negative votes, has a likePercentage of 0.8
|
* A video with 400 positive votes, and 100 negative votes, has a likePercentage of 0.8
|
||||||
*/
|
*/
|
||||||
public final float likePercentage;
|
public float getLikePercentage() {
|
||||||
|
return likePercentage;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimated percentage of dislikes for all votes. Value has range of [0, 1]
|
* Estimated percentage of dislikes for all votes. Value has range of [0, 1]
|
||||||
*
|
*
|
||||||
* A video with 400 positive votes, and 100 negative votes, has a dislikePercentage of 0.2
|
* A video with 400 positive votes, and 100 negative votes, has a dislikePercentage of 0.2
|
||||||
*/
|
*/
|
||||||
public final float dislikePercentage;
|
public float getDislikePercentage() {
|
||||||
|
return dislikePercentage;
|
||||||
/**
|
|
||||||
* @throws JSONException if JSON parse error occurs, or if the values make no sense (ie: negative values)
|
|
||||||
*/
|
|
||||||
public RYDVoteData(JSONObject json) throws JSONException {
|
|
||||||
Objects.requireNonNull(json);
|
|
||||||
videoId = json.getString("id");
|
|
||||||
viewCount = json.getLong("viewCount");
|
|
||||||
likeCount = json.getLong("likes");
|
|
||||||
dislikeCount = json.getLong("dislikes");
|
|
||||||
if (likeCount < 0 || dislikeCount < 0 || viewCount < 0) {
|
|
||||||
throw new JSONException("Unexpected JSON values: " + json);
|
|
||||||
}
|
|
||||||
likePercentage = (likeCount == 0 ? 0 : (float)likeCount / (likeCount + dislikeCount));
|
|
||||||
dislikePercentage = (dislikeCount == 0 ? 0 : (float)dislikeCount / (likeCount + dislikeCount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateUsingVote(Vote vote) {
|
||||||
|
if (vote == Vote.LIKE) {
|
||||||
|
LogHelper.printDebug(() -> "Increasing like count");
|
||||||
|
likeCount = fetchedLikeCount + 1;
|
||||||
|
dislikeCount = fetchedDislikeCount;
|
||||||
|
} else if (vote == Vote.DISLIKE) {
|
||||||
|
LogHelper.printDebug(() -> "Increasing dislike count");
|
||||||
|
likeCount = fetchedLikeCount;
|
||||||
|
dislikeCount = fetchedDislikeCount + 1;
|
||||||
|
} else if (vote == Vote.LIKE_REMOVE) {
|
||||||
|
LogHelper.printDebug(() -> "Resetting like/dislike to fetched values");
|
||||||
|
likeCount = fetchedLikeCount;
|
||||||
|
dislikeCount = fetchedDislikeCount;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
updatePercentages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePercentages() {
|
||||||
|
likePercentage = (likeCount == 0 ? 0 : (float) likeCount / (likeCount + dislikeCount));
|
||||||
|
dislikePercentage = (dislikeCount == 0 ? 0 : (float) dislikeCount / (likeCount + dislikeCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RYDVoteData{"
|
return "RYDVoteData{"
|
||||||
@ -73,4 +118,5 @@ public final class RYDVoteData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// equals and hashcode is not implemented (currently not needed)
|
// equals and hashcode is not implemented (currently not needed)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,27 @@
|
|||||||
package app.revanced.integrations.returnyoutubedislike.requests;
|
package app.revanced.integrations.returnyoutubedislike.requests;
|
||||||
|
|
||||||
import static app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute;
|
|
||||||
import static app.revanced.integrations.sponsorblock.StringRef.str;
|
|
||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.requests.Requester;
|
||||||
|
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.integrations.requests.Requester;
|
import static app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute;
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
import static app.revanced.integrations.sponsorblock.StringRef.str;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
public class ReturnYouTubeDislikeApi {
|
public class ReturnYouTubeDislikeApi {
|
||||||
/**
|
/**
|
||||||
@ -48,7 +46,13 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
/**
|
/**
|
||||||
* Response code of a successful API call
|
* Response code of a successful API call
|
||||||
*/
|
*/
|
||||||
private static final int SUCCESS_HTTP_STATUS_CODE = 200;
|
private static final int HTTP_STATUS_CODE_SUCCESS = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response code indicating the video id is not for a video that can be voted for.
|
||||||
|
* (it's not a Short or a regular video, and it's likely a YouTube Story)
|
||||||
|
*/
|
||||||
|
private static final int HTTP_STATUS_CODE_NOT_FOUND = 404;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates a client rate limit has been reached
|
* Indicates a client rate limit has been reached
|
||||||
@ -57,9 +61,9 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* How long to wait until API calls are resumed, if a rate limit is hit.
|
* How long to wait until API calls are resumed, if a rate limit is hit.
|
||||||
* No clear guideline of how long to backoff. Using 60 seconds for now.
|
* No clear guideline of how long to backoff. Using 2 minutes for now.
|
||||||
*/
|
*/
|
||||||
private static final int RATE_LIMIT_BACKOFF_SECONDS = 60;
|
private static final int RATE_LIMIT_BACKOFF_SECONDS = 120;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last time a {@link #RATE_LIMIT_HTTP_STATUS_CODE} was reached.
|
* Last time a {@link #RATE_LIMIT_HTTP_STATUS_CODE} was reached.
|
||||||
@ -133,6 +137,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
*
|
*
|
||||||
* @param maximumTimeToWait maximum time to wait
|
* @param maximumTimeToWait maximum time to wait
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
private static long randomlyWaitIfLocallyDebugging(long maximumTimeToWait) {
|
private static long randomlyWaitIfLocallyDebugging(long maximumTimeToWait) {
|
||||||
final boolean DEBUG_RANDOMLY_DELAY_NETWORK_CALLS = false; // set true to debug UI
|
final boolean DEBUG_RANDOMLY_DELAY_NETWORK_CALLS = false; // set true to debug UI
|
||||||
if (DEBUG_RANDOMLY_DELAY_NETWORK_CALLS) {
|
if (DEBUG_RANDOMLY_DELAY_NETWORK_CALLS) {
|
||||||
@ -183,6 +188,8 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
|
|
||||||
if (httpResponseCode == RATE_LIMIT_HTTP_STATUS_CODE) {
|
if (httpResponseCode == RATE_LIMIT_HTTP_STATUS_CODE) {
|
||||||
lastTimeRateLimitWasHit = System.currentTimeMillis();
|
lastTimeRateLimitWasHit = System.currentTimeMillis();
|
||||||
|
//noinspection NonAtomicOperationOnVolatileField // don't care, field is used only as an estimate
|
||||||
|
numberOfRateLimitRequestsEncountered++;
|
||||||
LogHelper.printDebug(() -> "API rate limit was hit. Stopping API calls for the next "
|
LogHelper.printDebug(() -> "API rate limit was hit. Stopping API calls for the next "
|
||||||
+ RATE_LIMIT_BACKOFF_SECONDS + " seconds");
|
+ RATE_LIMIT_BACKOFF_SECONDS + " seconds");
|
||||||
ReVancedUtils.runOnMainThread(() -> { // must show toasts on main thread
|
ReVancedUtils.runOnMainThread(() -> { // must show toasts on main thread
|
||||||
@ -208,7 +215,6 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
fetchCallNumberOfFailures++;
|
fetchCallNumberOfFailures++;
|
||||||
} else if (rateLimitHit) {
|
} else if (rateLimitHit) {
|
||||||
fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT;
|
fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT;
|
||||||
numberOfRateLimitRequestsEncountered++;
|
|
||||||
} else {
|
} else {
|
||||||
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
||||||
}
|
}
|
||||||
@ -228,7 +234,6 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
LogHelper.printDebug(() -> "Fetching votes for: " + videoId);
|
LogHelper.printDebug(() -> "Fetching votes for: " + videoId);
|
||||||
final long timeNetworkCallStarted = System.currentTimeMillis();
|
final long timeNetworkCallStarted = System.currentTimeMillis();
|
||||||
|
|
||||||
String connectionErrorMessageStringKey = "revanced_ryd_failure_connection_timeout";
|
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_DISLIKES, videoId);
|
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_DISLIKES, videoId);
|
||||||
// request headers, as per https://returnyoutubedislike.com/docs/fetching
|
// request headers, as per https://returnyoutubedislike.com/docs/fetching
|
||||||
@ -250,7 +255,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
final long timeNetworkCallEnded = System.currentTimeMillis(); // record end time before parsing
|
final long timeNetworkCallEnded = System.currentTimeMillis(); // record end time before parsing
|
||||||
// do not disconnect, the same server connection will likely be used again soon
|
// do not disconnect, the same server connection will likely be used again soon
|
||||||
JSONObject json = Requester.parseJSONObject(connection);
|
JSONObject json = Requester.parseJSONObject(connection);
|
||||||
@ -263,13 +268,20 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
LogHelper.printException(() -> "Failed to parse video: " + videoId + " json: " + json, ex);
|
LogHelper.printException(() -> "Failed to parse video: " + videoId + " json: " + json, ex);
|
||||||
// fall thru to update statistics
|
// fall thru to update statistics
|
||||||
}
|
}
|
||||||
|
} else if (responseCode == HTTP_STATUS_CODE_NOT_FOUND) {
|
||||||
|
// normal response when viewing YouTube Stories (cannot vote for these)
|
||||||
|
LogHelper.printDebug(() -> "Video has no like/dislikes (video is a YouTube Story?): " + videoId);
|
||||||
|
return null; // do not updated connection statistics
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printException(() -> "Failed to fetch votes for video: " + videoId
|
LogHelper.printException(() -> "Failed to fetch votes for video: " + videoId + " response code was: " + responseCode,
|
||||||
+ " response code was: " + responseCode, null, str(connectionErrorMessageStringKey));
|
null, str("revanced_ryd_failure_connection_status_code", responseCode));
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
connection.disconnect(); // something went wrong, might as well disconnect
|
||||||
}
|
}
|
||||||
} catch (Exception ex) { // connection timed out, response timeout, or some other network error
|
} catch (SocketTimeoutException ex) { // connection timed out, response timeout, or some other network error
|
||||||
LogHelper.printException(() -> "Failed to fetch votes", ex, str(connectionErrorMessageStringKey));
|
LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_connection_timeout"));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// should never happen
|
||||||
|
LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatistics(timeNetworkCallStarted, System.currentTimeMillis(), true, false);
|
updateStatistics(timeNetworkCallStarted, System.currentTimeMillis(), true, false);
|
||||||
@ -299,7 +311,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
JSONObject json = Requester.parseJSONObject(connection);
|
JSONObject json = Requester.parseJSONObject(connection);
|
||||||
String challenge = json.getString("challenge");
|
String challenge = json.getString("challenge");
|
||||||
int difficulty = json.getInt("difficulty");
|
int difficulty = json.getInt("difficulty");
|
||||||
@ -340,7 +352,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
String result = Requester.parseJson(connection);
|
String result = Requester.parseJson(connection);
|
||||||
if (result.equalsIgnoreCase("true")) {
|
if (result.equalsIgnoreCase("true")) {
|
||||||
LogHelper.printDebug(() -> "Registration confirmation successful for user: " + userId);
|
LogHelper.printDebug(() -> "Registration confirmation successful for user: " + userId);
|
||||||
@ -387,7 +399,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
JSONObject json = Requester.parseJSONObject(connection);
|
JSONObject json = Requester.parseJSONObject(connection);
|
||||||
String challenge = json.getString("challenge");
|
String challenge = json.getString("challenge");
|
||||||
int difficulty = json.getInt("difficulty");
|
int difficulty = json.getInt("difficulty");
|
||||||
@ -431,7 +443,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseCode == SUCCESS_HTTP_STATUS_CODE) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
String result = Requester.parseJson(connection);
|
String result = Requester.parseJson(connection);
|
||||||
if (result.equalsIgnoreCase("true")) {
|
if (result.equalsIgnoreCase("true")) {
|
||||||
LogHelper.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
LogHelper.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
||||||
@ -469,9 +481,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
byte[] decodedChallenge = Base64.decode(challenge, Base64.NO_WRAP);
|
byte[] decodedChallenge = Base64.decode(challenge, Base64.NO_WRAP);
|
||||||
|
|
||||||
byte[] buffer = new byte[20];
|
byte[] buffer = new byte[20];
|
||||||
for (int i = 4; i < 20; i++) { // FIXME replace with System.arrayCopy
|
System.arraycopy(decodedChallenge, 0, buffer, 4, 16);
|
||||||
buffer[i] = decodedChallenge[i - 4];
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDigest md;
|
MessageDigest md;
|
||||||
try {
|
try {
|
||||||
@ -513,9 +523,9 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
|
|
||||||
private static int countLeadingZeroes(byte[] uInt8View) {
|
private static int countLeadingZeroes(byte[] uInt8View) {
|
||||||
int zeroes = 0;
|
int zeroes = 0;
|
||||||
int value = 0;
|
int value;
|
||||||
for (int i = 0; i < uInt8View.length; i++) {
|
for (byte b : uInt8View) {
|
||||||
value = uInt8View[i] & 0xFF;
|
value = b & 0xFF;
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
zeroes += 8;
|
zeroes += 8;
|
||||||
} else {
|
} else {
|
||||||
|
@ -56,6 +56,8 @@ public enum SettingsEnum {
|
|||||||
ADREMOVER_WEB_SEARCH_RESULTS("revanced_adremover_web_search_result", true, ReturnType.BOOLEAN),
|
ADREMOVER_WEB_SEARCH_RESULTS("revanced_adremover_web_search_result", true, ReturnType.BOOLEAN),
|
||||||
ADREMOVER_HORIZONTAL_VIDEO_SHELF("revanced_horizontal_video_shelf", true, ReturnType.BOOLEAN),
|
ADREMOVER_HORIZONTAL_VIDEO_SHELF("revanced_horizontal_video_shelf", true, ReturnType.BOOLEAN),
|
||||||
ADREMOVER_CHANNEL_BAR("revanced_hide_channel_bar", false, ReturnType.BOOLEAN),
|
ADREMOVER_CHANNEL_BAR("revanced_hide_channel_bar", false, ReturnType.BOOLEAN),
|
||||||
|
ADREMOVER_QUICK_ACTIONS("revanced_hide_quick_actions", false, ReturnType.BOOLEAN),
|
||||||
|
ADREMOVER_RELATED_VIDEOS("revanced_hide_related_videos", false, ReturnType.BOOLEAN),
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
HIDE_LIKE_BUTTON("revanced_hide_like_button", false, ReturnType.BOOLEAN, false),
|
HIDE_LIKE_BUTTON("revanced_hide_like_button", false, ReturnType.BOOLEAN, false),
|
||||||
|
@ -7,8 +7,8 @@ import app.revanced.integrations.utils.Event
|
|||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
enum class PlayerType {
|
enum class PlayerType {
|
||||||
NONE, // this also includes when shorts are playing
|
NONE, // includes Shorts playback
|
||||||
HIDDEN,
|
HIDDEN, // also includes YouTube Shorts and Stories, if a regular video is minimized and a Short/Story is then opened
|
||||||
WATCH_WHILE_MINIMIZED,
|
WATCH_WHILE_MINIMIZED,
|
||||||
WATCH_WHILE_MAXIMIZED,
|
WATCH_WHILE_MAXIMIZED,
|
||||||
WATCH_WHILE_FULLSCREEN,
|
WATCH_WHILE_FULLSCREEN,
|
||||||
@ -42,6 +42,7 @@ enum class PlayerType {
|
|||||||
currentPlayerType = value
|
currentPlayerType = value
|
||||||
onChange(currentPlayerType)
|
onChange(currentPlayerType)
|
||||||
}
|
}
|
||||||
|
@Volatile // value is read/write from different threads
|
||||||
private var currentPlayerType = NONE
|
private var currentPlayerType = NONE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,7 +52,9 @@ enum class PlayerType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Weather Shorts are being played.
|
* Check if the current player type is [NONE] or [HIDDEN]
|
||||||
|
*
|
||||||
|
* @return True, if nothing, a Short, or a Story is playing.
|
||||||
*/
|
*/
|
||||||
fun isNoneOrHidden(): Boolean {
|
fun isNoneOrHidden(): Boolean {
|
||||||
return this == NONE || this == HIDDEN
|
return this == NONE || this == HIDDEN
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
org.gradle.jvmargs = -Xmx2048m
|
org.gradle.jvmargs = -Xmx2048m
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
version = 0.99.0-dev.1
|
version = 0.99.0-dev.4
|
||||||
|
Loading…
Reference in New Issue
Block a user