mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-12-11 21:17:47 +01:00
chore: merge branch dev
to main
(#466)
This commit is contained in:
commit
6801ed8d28
@ -1,3 +1,10 @@
|
|||||||
|
## [0.116.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.116.0...v0.116.1-dev.1) (2023-08-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Revert previous release ([a178a22](https://github.com/ReVanced/revanced-integrations/commit/a178a223c283abe420e197d10863d7fe64534f32))
|
||||||
|
|
||||||
# [0.116.0](https://github.com/ReVanced/revanced-integrations/compare/v0.115.1...v0.116.0) (2023-08-26)
|
# [0.116.0](https://github.com/ReVanced/revanced-integrations/compare/v0.115.1...v0.116.0) (2023-08-26)
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = 33
|
compileSdk = 33
|
||||||
|
buildToolsVersion = "33.0.1"
|
||||||
namespace = "app.revanced.integrations"
|
namespace = "app.revanced.integrations"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
@ -43,7 +44,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(mapOf("path" to ":dummy")))
|
compileOnly(project(mapOf("path" to ":dummy")))
|
||||||
compileOnly("androidx.annotation:annotation:1.6.0")
|
compileOnly("androidx.annotation:annotation:1.6.0")
|
||||||
compileOnly("androidx.appcompat:appcompat:1.7.0-alpha03")
|
compileOnly("androidx.appcompat:appcompat:1.7.0-alpha02")
|
||||||
compileOnly("com.squareup.okhttp3:okhttp:5.0.0-alpha.11")
|
compileOnly("com.squareup.okhttp3:okhttp:5.0.0-alpha.11")
|
||||||
compileOnly("com.squareup.retrofit2:retrofit:2.9.0")
|
compileOnly("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
}
|
}
|
||||||
|
@ -1,409 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.chromium.net.UrlResponseInfo;
|
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alternative YouTube thumbnails, showing the beginning/middle/end of the video.
|
|
||||||
* (ie: sd1.jpg, sd2.jpg, sd3.jpg).
|
|
||||||
*
|
|
||||||
* Has an additional option to use 'fast' thumbnails,
|
|
||||||
* where it forces sd thumbnail quality and skips verifying if the alt thumbnail image exists.
|
|
||||||
* The UI loading time will be the same or better than using the the original thumbnails,
|
|
||||||
* but thumbnails will initially fail to load for all live streams, unreleased, and occasionally very old videos.
|
|
||||||
* If a failed thumbnail load is reloaded (ie: scroll off, then on screen), then the original thumbnail
|
|
||||||
* is reloaded instead. Fast thumbnails requires using SD or lower thumbnail resolution,
|
|
||||||
* because a noticeable number of videos do not have hq720 and too many fail to load.
|
|
||||||
*
|
|
||||||
* Ideas for improvements:
|
|
||||||
* - Selectively allow using original thumbnails in some situations,
|
|
||||||
* such as videos subscription feed, watch history, or in search results.
|
|
||||||
* - Save to a temporary file the video id's verified to have alt thumbnails.
|
|
||||||
* This would speed up loading the watch history and users saved playlists.
|
|
||||||
*/
|
|
||||||
public final class AlternativeThumbnailsPatch {
|
|
||||||
|
|
||||||
private enum ThumbnailQuality {
|
|
||||||
// In order of lowest to highest resolution.
|
|
||||||
DEFAULT("default", ""), // effective alt name is 1.jpg, 2.jpg, 3.jpg
|
|
||||||
MQDEFAULT("mqdefault", "mq"),
|
|
||||||
HQDEFAULT("hqdefault", "hq"),
|
|
||||||
SDDEFAULT("sddefault", "sd"),
|
|
||||||
HQ720("hq720", "hq720_"),
|
|
||||||
MAXRESDEFAULT("maxresdefault", "maxres");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup map of original name to enum.
|
|
||||||
*/
|
|
||||||
private static final Map<String, ThumbnailQuality> originalNameToEnum = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup map of alt name to enum. ie: "hq720_1" to {@link #HQ720}.
|
|
||||||
*/
|
|
||||||
private static final Map<String, ThumbnailQuality> altNameToEnum = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (ThumbnailQuality quality : values()) {
|
|
||||||
originalNameToEnum.put(quality.originalName, quality);
|
|
||||||
|
|
||||||
for (int i = 1; i <= 3; i++) {
|
|
||||||
altNameToEnum.put(quality.altImageName + i, quality);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert an alt image name to enum.
|
|
||||||
* ie: "hq720_2" returns {@link #HQ720}.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
static ThumbnailQuality altImageNameToQuality(@NonNull String altImageName) {
|
|
||||||
return altNameToEnum.get(altImageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Original quality to effective alt quality to use.
|
|
||||||
* ie: If fast alt image is enabled, then "hq720" returns {@link #SDDEFAULT}.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
static ThumbnailQuality getQualityToUse(@NonNull String originalSize) {
|
|
||||||
ThumbnailQuality quality = originalNameToEnum.get(originalSize);
|
|
||||||
if (quality == null) {
|
|
||||||
return null; // Not a thumbnail for a regular video.
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean useFastQuality = SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean();
|
|
||||||
switch (quality) {
|
|
||||||
case SDDEFAULT:
|
|
||||||
// SD alt images have somewhat worse quality with washed out color and poor contrast.
|
|
||||||
// But the 720 images look much better and don't suffer from these issues.
|
|
||||||
// For unknown reasons, the 720 thumbnails are used only for the home feed,
|
|
||||||
// while SD is used for the search and subscription feed
|
|
||||||
// (even though search and subscriptions use the exact same layout as the home feed).
|
|
||||||
// Of note, this image quality issue only appears with the alt thumbnail images,
|
|
||||||
// and the regular thumbnails have identical color/contrast quality for all sizes.
|
|
||||||
// Fix this by falling thru and upgrading SD to 720.
|
|
||||||
case HQ720:
|
|
||||||
if (useFastQuality) {
|
|
||||||
return SDDEFAULT; // SD is max resolution for fast alt images.
|
|
||||||
}
|
|
||||||
return HQ720;
|
|
||||||
case MAXRESDEFAULT:
|
|
||||||
if (useFastQuality) {
|
|
||||||
return SDDEFAULT;
|
|
||||||
}
|
|
||||||
return MAXRESDEFAULT;
|
|
||||||
default:
|
|
||||||
return quality;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final String originalName;
|
|
||||||
final String altImageName;
|
|
||||||
|
|
||||||
ThumbnailQuality(String originalName, String altImageName) {
|
|
||||||
this.originalName = originalName;
|
|
||||||
this.altImageName = altImageName;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getAltImageNameToUse() {
|
|
||||||
return altImageName + SettingsEnum.ALT_THUMBNAIL_TYPE.getInt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses HTTP HEAD requests to verify and keep track of which thumbnail sizes
|
|
||||||
* are available and not available.
|
|
||||||
*/
|
|
||||||
private static class VerifiedQualities {
|
|
||||||
/**
|
|
||||||
* After a quality is verified as not available, how long until the quality is re-verified again.
|
|
||||||
* Used only if fast mode is not enabled. Intended for live streams and unreleased videos
|
|
||||||
* that are now finished and available (and thus, the alt thumbnails are also now available).
|
|
||||||
*/
|
|
||||||
private static final long NOT_AVAILABLE_TIMEOUT_MILLISECONDS = 10 * 60 * 1000; // 10 minutes.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache used to verify if an alternative thumbnails exists for a given video id.
|
|
||||||
*/
|
|
||||||
@GuardedBy("itself")
|
|
||||||
private static final Map<String, VerifiedQualities> altVideoIdLookup = new LinkedHashMap<>(100) {
|
|
||||||
private static final int CACHE_LIMIT = 1000;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
|
||||||
return size() > CACHE_LIMIT; // Evict oldest entry if over the cache limit.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static VerifiedQualities getVerifiedQualities(@NonNull String videoId, boolean returnNullIfDoesNotExist) {
|
|
||||||
synchronized (altVideoIdLookup) {
|
|
||||||
VerifiedQualities verified = altVideoIdLookup.get(videoId);
|
|
||||||
if (verified == null) {
|
|
||||||
if (returnNullIfDoesNotExist) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
verified = new VerifiedQualities();
|
|
||||||
altVideoIdLookup.put(videoId, verified);
|
|
||||||
}
|
|
||||||
return verified;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean verifyAltThumbnailExist(@NonNull String videoId, @NonNull ThumbnailQuality quality,
|
|
||||||
@NonNull String imageUrl) {
|
|
||||||
VerifiedQualities verified = getVerifiedQualities(videoId, SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean());
|
|
||||||
if (verified == null) return true; // Fast alt thumbnails is enabled.
|
|
||||||
return verified.verifyYouTubeThumbnailExists(videoId, quality, imageUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setAltThumbnailDoesNotExist(@NonNull String videoId, @NonNull ThumbnailQuality quality) {
|
|
||||||
VerifiedQualities verified = getVerifiedQualities(videoId, false);
|
|
||||||
verified.setQualityVerified(videoId, quality, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Highest quality verified as existing.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
ThumbnailQuality highestQualityVerified;
|
|
||||||
/**
|
|
||||||
* Lowest quality verified as not existing.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
ThumbnailQuality lowestQualityNotAvailable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* System time, of when to invalidate {@link #lowestQualityNotAvailable}.
|
|
||||||
* Used only if fast mode is not enabled.
|
|
||||||
*/
|
|
||||||
long timeToReVerifyLowestQuality;
|
|
||||||
|
|
||||||
synchronized void setQualityVerified(String videoId, ThumbnailQuality quality, boolean isVerified) {
|
|
||||||
if (isVerified) {
|
|
||||||
if (highestQualityVerified == null || highestQualityVerified.ordinal() < quality.ordinal()) {
|
|
||||||
highestQualityVerified = quality;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (lowestQualityNotAvailable == null || lowestQualityNotAvailable.ordinal() > quality.ordinal()) {
|
|
||||||
lowestQualityNotAvailable = quality;
|
|
||||||
timeToReVerifyLowestQuality = System.currentTimeMillis() + NOT_AVAILABLE_TIMEOUT_MILLISECONDS;
|
|
||||||
}
|
|
||||||
LogHelper.printDebug(() -> quality + " not available for video: " + videoId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify if a video alt thumbnail exists. Does so by making a minimal HEAD http request.
|
|
||||||
*/
|
|
||||||
synchronized boolean verifyYouTubeThumbnailExists(@NonNull String videoId, @NonNull ThumbnailQuality quality,
|
|
||||||
@NonNull String imageUrl) {
|
|
||||||
if (highestQualityVerified != null && highestQualityVerified.ordinal() >= quality.ordinal()) {
|
|
||||||
return true; // Previously verified as existing.
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean fastQuality = SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean();
|
|
||||||
if (lowestQualityNotAvailable != null && lowestQualityNotAvailable.ordinal() <= quality.ordinal()) {
|
|
||||||
if (fastQuality || System.currentTimeMillis() < timeToReVerifyLowestQuality) {
|
|
||||||
return false; // Previously verified as not existing.
|
|
||||||
}
|
|
||||||
// Enough time has passed, and should re-verify again.
|
|
||||||
LogHelper.printDebug(() -> "Resetting lowest verified quality for: " + videoId);
|
|
||||||
lowestQualityNotAvailable = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fastQuality) {
|
|
||||||
return true; // Unknown if it exists or not. Use the URL anyways and update afterwards if loading fails.
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean imageFileFound;
|
|
||||||
try {
|
|
||||||
LogHelper.printDebug(() -> "Verifying image: " + imageUrl);
|
|
||||||
// This hooked code is running on a low priority thread, and it's slightly faster
|
|
||||||
// to run the url connection thru the integrations thread pool which runs at the highest priority.
|
|
||||||
final long start = System.currentTimeMillis();
|
|
||||||
imageFileFound = ReVancedUtils.submitOnBackgroundThread(() -> {
|
|
||||||
final int connectionTimeoutMillis = 5000;
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection();
|
|
||||||
connection.setConnectTimeout(connectionTimeoutMillis);
|
|
||||||
connection.setReadTimeout(connectionTimeoutMillis);
|
|
||||||
connection.setRequestMethod("HEAD");
|
|
||||||
// Even with a HEAD request, the response is the same size as a full GET request.
|
|
||||||
// Using an empty range fixes this.
|
|
||||||
connection.setRequestProperty("Range", "bytes=0-0");
|
|
||||||
final int responseCode = connection.getResponseCode();
|
|
||||||
if (responseCode == HttpURLConnection.HTTP_PARTIAL) {
|
|
||||||
String contentType = connection.getContentType();
|
|
||||||
return (contentType != null && contentType.startsWith("image"));
|
|
||||||
}
|
|
||||||
if (responseCode != HttpURLConnection.HTTP_NOT_FOUND) {
|
|
||||||
LogHelper.printDebug(() -> "Unexpected response code: " + responseCode + " for url: " + imageUrl);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}).get();
|
|
||||||
LogHelper.printDebug(() -> "Alt verification took: " + (System.currentTimeMillis() - start) + "ms");
|
|
||||||
} catch (ExecutionException | InterruptedException ex) {
|
|
||||||
LogHelper.printInfo(() -> "Could not verify alt url: " + imageUrl, ex);
|
|
||||||
imageFileFound = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setQualityVerified(videoId, quality, imageFileFound);
|
|
||||||
return imageFileFound;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* YouTube video thumbnail url, decoded into it's relevant parts.
|
|
||||||
*/
|
|
||||||
private static class DecodedThumbnailUrl {
|
|
||||||
/**
|
|
||||||
* YouTube thumbnail URL prefix. Can be '/vi/' or '/vi_webp/'
|
|
||||||
*/
|
|
||||||
private static final String YOUTUBE_THUMBNAIL_PREFIX = "https://i.ytimg.com/vi";
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
static DecodedThumbnailUrl decodeImageUrl(String url) {
|
|
||||||
final int videoIdStartIndex = url.indexOf('/', YOUTUBE_THUMBNAIL_PREFIX.length()) + 1;
|
|
||||||
if (videoIdStartIndex <= 0) return null;
|
|
||||||
final int videoIdEndIndex = url.indexOf('/', videoIdStartIndex);
|
|
||||||
if (videoIdEndIndex < 0) return null;
|
|
||||||
final int imageSizeStartIndex = videoIdEndIndex + 1;
|
|
||||||
final int imageSizeEndIndex = url.indexOf('.', imageSizeStartIndex);
|
|
||||||
if (imageSizeEndIndex < 0) return null;
|
|
||||||
int imageExtensionEndIndex = url.indexOf('?', imageSizeEndIndex);
|
|
||||||
if (imageExtensionEndIndex < 0) imageExtensionEndIndex = url.length();
|
|
||||||
return new DecodedThumbnailUrl(url, videoIdStartIndex, videoIdEndIndex,
|
|
||||||
imageSizeStartIndex, imageSizeEndIndex, imageExtensionEndIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Full usable url, but stripped of any tracking information. */
|
|
||||||
final String sanitizedUrl;
|
|
||||||
/** Url up to the video id. */
|
|
||||||
final String urlPrefix;
|
|
||||||
final String videoId;
|
|
||||||
/** Quality, such as hq720 or sddefault. */
|
|
||||||
final String imageQuality;
|
|
||||||
/** jpg or webp */
|
|
||||||
final String imageExtension;
|
|
||||||
/** User view tracking parameters, only present on some images. */
|
|
||||||
final String urlTrackingParameters;
|
|
||||||
|
|
||||||
private DecodedThumbnailUrl(String fullUrl, int videoIdStartIndex, int videoIdEndIndex,
|
|
||||||
int imageSizeStartIndex, int imageSizeEndIndex, int imageExtensionEndIndex) {
|
|
||||||
sanitizedUrl = fullUrl.substring(0, imageExtensionEndIndex);
|
|
||||||
urlPrefix = fullUrl.substring(0, videoIdStartIndex);
|
|
||||||
videoId = fullUrl.substring(videoIdStartIndex, videoIdEndIndex);
|
|
||||||
imageQuality = fullUrl.substring(imageSizeStartIndex, imageSizeEndIndex);
|
|
||||||
imageExtension = fullUrl.substring(imageSizeEndIndex + 1, imageExtensionEndIndex);
|
|
||||||
urlTrackingParameters = (imageExtensionEndIndex == fullUrl.length())
|
|
||||||
? "" : fullUrl.substring(imageExtensionEndIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Fix any bad imported data.
|
|
||||||
final int altThumbnailType = SettingsEnum.ALT_THUMBNAIL_TYPE.getInt();
|
|
||||||
if (altThumbnailType < 1 || altThumbnailType > 3) {
|
|
||||||
LogHelper.printException(() -> "Invalid alt thumbnail type: " + altThumbnailType);
|
|
||||||
SettingsEnum.ALT_THUMBNAIL_TYPE.saveValue(SettingsEnum.ALT_THUMBNAIL_TYPE.defaultValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point. Called off the main thread and by multiple threads at the same time.
|
|
||||||
*
|
|
||||||
* @param originalUrl Image url for all url images loaded, including video thumbnails.
|
|
||||||
*/
|
|
||||||
public static String overrideImageURL(String originalUrl) {
|
|
||||||
try {
|
|
||||||
if (!SettingsEnum.ALT_THUMBNAIL.getBoolean()) {
|
|
||||||
return originalUrl;
|
|
||||||
}
|
|
||||||
DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(originalUrl);
|
|
||||||
if (decodedUrl == null) {
|
|
||||||
return originalUrl; // Not a thumbnail.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep any tracking parameters out of the logs, and log only the base url.
|
|
||||||
LogHelper.printDebug(() -> "Original url: " + decodedUrl.sanitizedUrl);
|
|
||||||
|
|
||||||
ThumbnailQuality qualityToUse = ThumbnailQuality.getQualityToUse(decodedUrl.imageQuality);
|
|
||||||
if (qualityToUse == null) return originalUrl; // Video is a short.
|
|
||||||
|
|
||||||
// Images could be upgraded to webp if they are not already, but this fails quite often,
|
|
||||||
// especially for new videos uploaded in the last hour.
|
|
||||||
// And even if alt webp images do exist, sometimes they can load much slower than the original jpg alt images.
|
|
||||||
// (as much as 4x slower has been observed, despite the alt webp image being a smaller file).
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder(originalUrl.length() + 2);
|
|
||||||
builder.append(decodedUrl.urlPrefix);
|
|
||||||
builder.append(decodedUrl.videoId).append('/');
|
|
||||||
builder.append(qualityToUse.getAltImageNameToUse());
|
|
||||||
builder.append('.').append(decodedUrl.imageExtension);
|
|
||||||
|
|
||||||
String sanitizedReplacement = builder.toString();
|
|
||||||
if (!VerifiedQualities.verifyAltThumbnailExist(decodedUrl.videoId, qualityToUse, sanitizedReplacement)) {
|
|
||||||
return originalUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Replaced url: " + sanitizedReplacement);
|
|
||||||
|
|
||||||
// URL tracking parameters. Presumably they are to determine if a user has viewed a thumbnail.
|
|
||||||
// This likely is used for recommendations, so they are retained if present.
|
|
||||||
builder.append(decodedUrl.urlTrackingParameters);
|
|
||||||
return builder.toString();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LogHelper.printException(() -> "Alt thumbnails failure", ex);
|
|
||||||
return originalUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*
|
|
||||||
* Cronet considers all completed connections as a success, even if the response is 404 or 5xx.
|
|
||||||
*/
|
|
||||||
public static void handleCronetSuccess(@NonNull UrlResponseInfo responseInfo) {
|
|
||||||
try {
|
|
||||||
if (responseInfo.getHttpStatusCode() == 404 && SettingsEnum.ALT_THUMBNAIL.getBoolean()) {
|
|
||||||
// Fast alt thumbnails is enabled and the thumbnail is not available.
|
|
||||||
// The video is:
|
|
||||||
// - live stream
|
|
||||||
// - upcoming unreleased video
|
|
||||||
// - very old
|
|
||||||
// - very low view count
|
|
||||||
// Take note of this, so if the image reloads the original thumbnail will be used.
|
|
||||||
DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(responseInfo.getUrl());
|
|
||||||
if (decodedUrl == null) {
|
|
||||||
return; // Not a thumbnail.
|
|
||||||
}
|
|
||||||
|
|
||||||
ThumbnailQuality quality = ThumbnailQuality.altImageNameToQuality(decodedUrl.imageQuality);
|
|
||||||
if (quality == null) {
|
|
||||||
// Video is a short or unknown quality, but the url returned 404. Should never happen.
|
|
||||||
LogHelper.printDebug(() -> "Failed to load unknown url: " + decodedUrl.sanitizedUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
VerifiedQualities.setAltThumbnailDoesNotExist(decodedUrl.videoId, quality);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LogHelper.printException(() -> "Alt thumbnails callback failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
public class CustomPlayerOverlayOpacityPatch {
|
|
||||||
private static final int DEFAULT_OPACITY = (int) SettingsEnum.PLAYER_OVERLAY_OPACITY.defaultValue;
|
|
||||||
|
|
||||||
public static void changeOpacity(ImageView imageView) {
|
|
||||||
int opacity = SettingsEnum.PLAYER_OVERLAY_OPACITY.getInt();
|
|
||||||
|
|
||||||
if (opacity < 0 || opacity > 100) {
|
|
||||||
ReVancedUtils.showToastLong("Player overlay opacity must be between 0-100");
|
|
||||||
SettingsEnum.PLAYER_OVERLAY_OPACITY.saveValue(DEFAULT_OPACITY);
|
|
||||||
opacity = DEFAULT_OPACITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
imageView.setImageAlpha((opacity * 255) / 100);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public final class EnableTabletLayoutPatch {
|
|
||||||
public static boolean enableTabletLayout() {
|
|
||||||
return SettingsEnum.TABLET_LAYOUT.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,14 +4,16 @@ import app.revanced.integrations.settings.SettingsEnum;
|
|||||||
|
|
||||||
public class OpenLinksExternallyPatch {
|
public class OpenLinksExternallyPatch {
|
||||||
/**
|
/**
|
||||||
* Return the intent to open links with. If empty, the link will be opened with the default browser.
|
* Override 'android.support.customtabs.action.CustomTabsService',
|
||||||
|
* in order to open links in the default browser. This is done by returning an empty string,
|
||||||
|
* for the service that handles custom tabs in the Android support library
|
||||||
|
* which opens links in the default service instead.
|
||||||
*
|
*
|
||||||
* @param originalIntent The original intent to open links with.
|
* @param original The original custom tabs service.
|
||||||
* @return The intent to open links with. Empty means the link will be opened with the default browser.
|
* @return The new, default service to open links with or the original service.
|
||||||
*/
|
*/
|
||||||
public static String getIntent(String originalIntent) {
|
public static String enableExternalBrowser(String original) {
|
||||||
if (SettingsEnum.EXTERNAL_BROWSER.getBoolean()) return "";
|
if (SettingsEnum.EXTERNAL_BROWSER.getBoolean()) original = "";
|
||||||
|
return original;
|
||||||
return originalIntent;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ public final class AdsFilter extends Filter {
|
|||||||
"cta_shelf_card"
|
"cta_shelf_card"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pathFilterGroupList.addAll(
|
this.pathFilterGroups.addAll(
|
||||||
generalAds,
|
generalAds,
|
||||||
buttonedAd,
|
buttonedAd,
|
||||||
merchandise,
|
merchandise,
|
||||||
@ -95,16 +95,16 @@ public final class AdsFilter extends Filter {
|
|||||||
webLinkPanel,
|
webLinkPanel,
|
||||||
movieAds
|
movieAds
|
||||||
);
|
);
|
||||||
this.identifierFilterGroupList.addAll(carouselAd);
|
this.identifierFilterGroups.addAll(carouselAd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
if (exceptions.matches(path))
|
if (exceptions.matches(path))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,114 +1,59 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
final class ButtonsFilter extends Filter {
|
final class ButtonsFilter extends Filter {
|
||||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
private final StringFilterGroup actionBarRule;
|
||||||
|
|
||||||
private final StringFilterGroup actionBarGroup;
|
|
||||||
private final StringFilterGroup bufferFilterPathGroup;
|
|
||||||
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
|
||||||
|
|
||||||
public ButtonsFilter() {
|
public ButtonsFilter() {
|
||||||
actionBarGroup = new StringFilterGroup(
|
actionBarRule = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
VIDEO_ACTION_BAR_PATH
|
"video_action_bar"
|
||||||
);
|
);
|
||||||
identifierFilterGroupList.addAll(actionBarGroup);
|
|
||||||
|
|
||||||
|
pathFilterGroups.addAll(
|
||||||
bufferFilterPathGroup = new StringFilterGroup(
|
|
||||||
null,
|
|
||||||
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
|
||||||
);
|
|
||||||
pathFilterGroupList.addAll(
|
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON,
|
SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON,
|
||||||
"|segmented_like_dislike_button"
|
"|like_button",
|
||||||
|
"dislike_button"
|
||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_DOWNLOAD_BUTTON,
|
SettingsEnum.HIDE_DOWNLOAD_BUTTON,
|
||||||
"|download_button.eml|"
|
"download_button"
|
||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_PLAYLIST_BUTTON,
|
SettingsEnum.HIDE_PLAYLIST_BUTTON,
|
||||||
"|save_to_playlist_button"
|
"save_to_playlist_button"
|
||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_CLIP_BUTTON,
|
SettingsEnum.HIDE_CLIP_BUTTON,
|
||||||
"|clip_button.eml|"
|
"|clip_button.eml|"
|
||||||
),
|
),
|
||||||
bufferFilterPathGroup
|
new StringFilterGroup(
|
||||||
);
|
SettingsEnum.HIDE_ACTION_BUTTONS,
|
||||||
|
"ContainerType|video_action_button",
|
||||||
bufferButtonsGroupList.addAll(
|
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
||||||
new ByteArrayAsStringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_LIVE_CHAT_BUTTON,
|
|
||||||
"yt_outline_message_bubble_overlap"
|
|
||||||
),
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
actionBarRule
|
||||||
SettingsEnum.HIDE_REPORT_BUTTON,
|
|
||||||
"yt_outline_flag"
|
|
||||||
),
|
|
||||||
new ByteArrayAsStringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHARE_BUTTON,
|
|
||||||
"yt_outline_share"
|
|
||||||
),
|
|
||||||
new ByteArrayAsStringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_REMIX_BUTTON,
|
|
||||||
"yt_outline_youtube_shorts_plus"
|
|
||||||
),
|
|
||||||
// Check for clip button both here and using a path filter,
|
|
||||||
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
|
||||||
new ByteArrayAsStringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_CLIP_BUTTON,
|
|
||||||
"yt_outline_scissors"
|
|
||||||
),
|
|
||||||
new ByteArrayAsStringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHOP_BUTTON,
|
|
||||||
"yt_outline_bag"
|
|
||||||
),
|
|
||||||
new ByteArrayAsStringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_THANKS_BUTTON,
|
|
||||||
"yt_outline_dollar_sign_heart"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEveryFilterGroupEnabled() {
|
private boolean isEveryFilterGroupEnabled() {
|
||||||
for (var group : pathFilterGroupList)
|
for (StringFilterGroup rule : pathFilterGroups)
|
||||||
if (!group.isEnabled()) return false;
|
if (!rule.isEnabled()) return false;
|
||||||
|
|
||||||
for (var group : bufferButtonsGroupList)
|
|
||||||
if (!group.isEnabled()) return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
// If the current matched group is the action bar group,
|
if (matchedGroup == actionBarRule) {
|
||||||
// in case every filter group is enabled, hide the action bar.
|
return isEveryFilterGroupEnabled();
|
||||||
if (matchedGroup == actionBarGroup) {
|
|
||||||
if (!isEveryFilterGroupEnabled()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (matchedGroup == bufferFilterPathGroup) {
|
|
||||||
// Make sure the current path is the right one
|
|
||||||
// to avoid false positives.
|
|
||||||
if (!path.startsWith(VIDEO_ACTION_BAR_PATH)) return false;
|
|
||||||
|
|
||||||
// In case the group list has no match, return false.
|
|
||||||
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ final class CommentsFilter extends Filter {
|
|||||||
"comments_entry_point_simplebox"
|
"comments_entry_point_simplebox"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pathFilterGroupList.addAll(
|
this.pathFilterGroups.addAll(
|
||||||
comments,
|
comments,
|
||||||
previewComment
|
previewComment
|
||||||
);
|
);
|
||||||
|
@ -142,7 +142,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"chips_shelf"
|
"chips_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.pathFilterGroupList.addAll(
|
this.pathFilterGroups.addAll(
|
||||||
channelBar,
|
channelBar,
|
||||||
communityPosts,
|
communityPosts,
|
||||||
paidContent,
|
paidContent,
|
||||||
@ -165,19 +165,19 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
custom
|
custom
|
||||||
);
|
);
|
||||||
|
|
||||||
this.identifierFilterGroupList.addAll(
|
this.identifierFilterGroups.addAll(
|
||||||
graySeparator,
|
graySeparator,
|
||||||
chipsShelf
|
chipsShelf
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
if (matchedGroup != custom && exceptions.matches(path))
|
if (matchedGroup != custom && exceptions.matches(path))
|
||||||
return false; // Exceptions are not filtered.
|
return false; // Exceptions are not filtered.
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,7 +81,8 @@ class StringFilterGroup extends FilterGroup<String> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FilterGroupResult check(final String string) {
|
public FilterGroupResult check(final String string) {
|
||||||
return new FilterGroupResult(setting, isEnabled() && ReVancedUtils.containsAny(string, filters));
|
return new FilterGroupResult(setting,
|
||||||
|
(setting == null || setting.getBoolean()) && ReVancedUtils.containsAny(string, filters));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,14 +274,25 @@ abstract class Filter {
|
|||||||
* will never be called for any matches.
|
* will never be called for any matches.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
protected final StringFilterGroupList pathFilterGroupList = new StringFilterGroupList();
|
protected final StringFilterGroupList pathFilterGroups = new StringFilterGroupList();
|
||||||
protected final StringFilterGroupList identifierFilterGroupList = new StringFilterGroupList();
|
protected final StringFilterGroupList identifierFilterGroups = new StringFilterGroupList();
|
||||||
|
/**
|
||||||
|
* A collection of {@link ByteArrayFilterGroup} that are always searched for (no matter what).
|
||||||
|
*
|
||||||
|
* If possible, avoid adding values to this list and instead use a path or identifier filter
|
||||||
|
* for the item you are looking for. Then inside
|
||||||
|
* {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)},
|
||||||
|
* the buffer can then be searched using using a different
|
||||||
|
* {@link ByteArrayFilterGroupList} or a {@link ByteArrayFilterGroup}.
|
||||||
|
* This way, the expensive buffer searching only occurs if the cheap and fast path/identifier is already found.
|
||||||
|
*/
|
||||||
|
protected final ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after an enabled filter has been matched.
|
* Called after an enabled filter has been matched.
|
||||||
* Default implementation is to always filter the matched item.
|
* Default implementation is to always filter the matched item.
|
||||||
* Subclasses can perform additional or different checks if needed.
|
* Subclasses can perform additional or different checks if needed.
|
||||||
* <p>
|
*
|
||||||
* Method is called off the main thread.
|
* Method is called off the main thread.
|
||||||
*
|
*
|
||||||
* @param matchedList The list the group filter belongs to.
|
* @param matchedList The list the group filter belongs to.
|
||||||
@ -289,13 +301,15 @@ abstract class Filter {
|
|||||||
* @return True if the litho item should be filtered out.
|
* @return True if the litho item should be filtered out.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||||
if (pathFilterGroupList == matchedList) {
|
if (pathFilterGroups == matchedList) {
|
||||||
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path);
|
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path);
|
||||||
} else if (identifierFilterGroupList == matchedList) {
|
} else if (identifierFilterGroups == matchedList) {
|
||||||
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier);
|
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier);
|
||||||
|
} else if (protobufBufferFilterGroups == matchedList) {
|
||||||
|
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered from protobuf-buffer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -309,14 +323,13 @@ public final class LithoFilterPatch {
|
|||||||
* Simple wrapper to pass the litho parameters through the prefix search.
|
* Simple wrapper to pass the litho parameters through the prefix search.
|
||||||
*/
|
*/
|
||||||
private static final class LithoFilterParameters {
|
private static final class LithoFilterParameters {
|
||||||
@Nullable
|
|
||||||
final String identifier;
|
|
||||||
final String path;
|
final String path;
|
||||||
|
final String identifier;
|
||||||
final byte[] protoBuffer;
|
final byte[] protoBuffer;
|
||||||
|
|
||||||
LithoFilterParameters(@Nullable String lithoIdentifier, StringBuilder lithoPath, ByteBuffer protoBuffer) {
|
LithoFilterParameters(StringBuilder lithoPath, String lithoIdentifier, ByteBuffer protoBuffer) {
|
||||||
this.identifier = lithoIdentifier;
|
|
||||||
this.path = lithoPath.toString();
|
this.path = lithoPath.toString();
|
||||||
|
this.identifier = lithoIdentifier;
|
||||||
this.protoBuffer = protoBuffer.array();
|
this.protoBuffer = protoBuffer.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,10 +342,9 @@ public final class LithoFilterPatch {
|
|||||||
builder.append(identifier);
|
builder.append(identifier);
|
||||||
builder.append(" Path: ");
|
builder.append(" Path: ");
|
||||||
builder.append(path);
|
builder.append(path);
|
||||||
if (SettingsEnum.DEBUG_PROTOBUFFER.getBoolean()) {
|
// TODO: allow turning on/off buffer logging with a debug setting?
|
||||||
builder.append(" BufferStrings: ");
|
builder.append(" BufferStrings: ");
|
||||||
findAsciiStrings(builder, protoBuffer);
|
findAsciiStrings(builder, protoBuffer);
|
||||||
}
|
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
@ -354,9 +366,7 @@ public final class LithoFilterPatch {
|
|||||||
int value = buffer[end];
|
int value = buffer[end];
|
||||||
if (value < minimumAscii || value > maximumAscii || end == length - 1) {
|
if (value < minimumAscii || value > maximumAscii || end == length - 1) {
|
||||||
if (end - start >= minimumAsciiStringLength) {
|
if (end - start >= minimumAsciiStringLength) {
|
||||||
for (int i = start; i < end; i++) {
|
builder.append(new String(buffer, start, end - start));
|
||||||
builder.append((char) buffer[i]);
|
|
||||||
}
|
|
||||||
builder.append(delimitingCharacter);
|
builder.append(delimitingCharacter);
|
||||||
}
|
}
|
||||||
start = end + 1;
|
start = end + 1;
|
||||||
@ -372,24 +382,22 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
||||||
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
||||||
|
private static final ByteTrieSearch protoSearchTree = new ByteTrieSearch();
|
||||||
/**
|
|
||||||
* Because litho filtering is multi-threaded and the buffer is passed in from a different injection point,
|
|
||||||
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<ByteBuffer> bufferThreadLocal = new ThreadLocal<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (Filter filter : filters) {
|
for (Filter filter : filters) {
|
||||||
filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList);
|
filterGroupLists(pathSearchTree, filter, filter.pathFilterGroups);
|
||||||
filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroupList);
|
filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroups);
|
||||||
|
filterGroupLists(protoSearchTree, filter, filter.protobufBufferFilterGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Using: "
|
LogHelper.printDebug(() -> "Using: "
|
||||||
+ pathSearchTree.numberOfPatterns() + " path filters"
|
+ pathSearchTree.numberOfPatterns() + " path filters"
|
||||||
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB), "
|
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB), "
|
||||||
+ identifierSearchTree.numberOfPatterns() + " identifier filters"
|
+ identifierSearchTree.numberOfPatterns() + " identifier filters"
|
||||||
+ " (" + identifierSearchTree.getEstimatedMemorySize() + " KB)");
|
+ " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), "
|
||||||
|
+ protoSearchTree.numberOfPatterns() + " buffer filters"
|
||||||
|
+ " (" + protoSearchTree.getEstimatedMemorySize() + " KB)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> void filterGroupLists(TrieSearch<T> pathSearchTree,
|
private static <T> void filterGroupLists(TrieSearch<T> pathSearchTree,
|
||||||
@ -402,7 +410,7 @@ public final class LithoFilterPatch {
|
|||||||
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> {
|
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> {
|
||||||
if (!group.isEnabled()) return false;
|
if (!group.isEnabled()) return false;
|
||||||
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||||
return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer,
|
return filter.isFiltered(parameters.path, parameters.identifier, parameters.protoBuffer,
|
||||||
list, group, matchedStartIndex);
|
list, group, matchedStartIndex);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -414,36 +422,21 @@ public final class LithoFilterPatch {
|
|||||||
* Injection point. Called off the main thread.
|
* Injection point. Called off the main thread.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void setProtoBuffer(@NonNull ByteBuffer protobufBuffer) {
|
public static boolean filter(@NonNull StringBuilder pathBuilder, @Nullable String lithoIdentifier,
|
||||||
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
@NonNull ByteBuffer protobufBuffer) {
|
||||||
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
|
|
||||||
// The buffer will be cleared from memory after a new buffer is set by the same thread,
|
|
||||||
// or when the calling thread eventually dies.
|
|
||||||
bufferThreadLocal.set(protobufBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point. Called off the main thread, and commonly called by multiple threads at the same time.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public static boolean filter(@Nullable String lithoIdentifier, @NonNull StringBuilder pathBuilder) {
|
|
||||||
try {
|
try {
|
||||||
// It is assumed that protobufBuffer is empty as well in this case.
|
// It is assumed that protobufBuffer is empty as well in this case.
|
||||||
if (pathBuilder.length() == 0)
|
if (pathBuilder.length() == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ByteBuffer protobufBuffer = bufferThreadLocal.get();
|
LithoFilterParameters parameter = new LithoFilterParameters(pathBuilder, lithoIdentifier, protobufBuffer);
|
||||||
if (protobufBuffer == null) {
|
|
||||||
LogHelper.printException(() -> "Proto buffer is null"); // Should never happen
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LithoFilterParameters parameter = new LithoFilterParameters(lithoIdentifier, pathBuilder, protobufBuffer);
|
|
||||||
LogHelper.printDebug(() -> "Searching " + parameter);
|
LogHelper.printDebug(() -> "Searching " + parameter);
|
||||||
|
|
||||||
|
if (pathSearchTree.matches(parameter.path, parameter)) return true;
|
||||||
if (parameter.identifier != null) {
|
if (parameter.identifier != null) {
|
||||||
if (identifierSearchTree.matches(parameter.identifier, parameter)) return true;
|
if (identifierSearchTree.matches(parameter.identifier, parameter)) return true;
|
||||||
}
|
}
|
||||||
if (pathSearchTree.matches(parameter.path, parameter)) return true;
|
if (protoSearchTree.matches(parameter.protoBuffer, parameter)) return true;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Litho filter failure", ex);
|
LogHelper.printException(() -> "Litho filter failure", ex);
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,14 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
|||||||
public static volatile boolean isPlaybackSpeedMenuVisible;
|
public static volatile boolean isPlaybackSpeedMenuVisible;
|
||||||
|
|
||||||
public PlaybackSpeedMenuFilterPatch() {
|
public PlaybackSpeedMenuFilterPatch() {
|
||||||
pathFilterGroupList.addAll(new StringFilterGroup(
|
pathFilterGroups.addAll(new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"playback_speed_sheet_content.eml-js"
|
"playback_speed_sheet_content.eml-js"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
isPlaybackSpeedMenuVisible = true;
|
isPlaybackSpeedMenuVisible = true;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public PlayerFlyoutMenuItemsFilter() {
|
public PlayerFlyoutMenuItemsFilter() {
|
||||||
identifierFilterGroupList.addAll(new StringFilterGroup(null, "overflow_menu_item.eml|"));
|
identifierFilterGroups.addAll(new StringFilterGroup(null, "overflow_menu_item.eml|"));
|
||||||
|
|
||||||
flyoutFilterGroupList.addAll(
|
flyoutFilterGroupList.addAll(
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayAsStringFilterGroup(
|
||||||
@ -62,12 +62,12 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
// Only 1 group is added to the parent class, so the matched group must be the overflow menu.
|
// Only 1 group is added to the parent class, so the matched group must be the overflow menu.
|
||||||
if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||||
// Super class handles logging.
|
// Super class handles logging.
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -12,21 +12,45 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
|||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
public final class ShortsFilter extends Filter {
|
public final class ShortsFilter extends Filter {
|
||||||
private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml";
|
private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar";
|
||||||
public static PivotBar pivotBar; // Set by patch.
|
public static PivotBar pivotBar; // Set by patch.
|
||||||
|
|
||||||
private final StringFilterGroup channelBar;
|
private final StringFilterGroup channelBar;
|
||||||
private final StringFilterGroup soundButton;
|
private final StringFilterGroup soundButton;
|
||||||
private final StringFilterGroup infoPanel;
|
private final StringFilterGroup infoPanel;
|
||||||
private final StringFilterGroup shortsShelfHeader;
|
|
||||||
|
|
||||||
public ShortsFilter() {
|
public ShortsFilter() {
|
||||||
// Home / subscription feed components.
|
channelBar = new StringFilterGroup(
|
||||||
var thanksButton = new StringFilterGroup(
|
SettingsEnum.HIDE_SHORTS_CHANNEL_BAR,
|
||||||
|
REEL_CHANNEL_BAR_PATH
|
||||||
|
);
|
||||||
|
|
||||||
|
soundButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_SOUND_BUTTON,
|
||||||
|
"reel_pivot_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
infoPanel = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_INFO_PANEL,
|
||||||
|
"shorts_info_panel_overview"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var thanksButton = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_SHORTS_THANKS_BUTTON,
|
SettingsEnum.HIDE_SHORTS_THANKS_BUTTON,
|
||||||
"suggested_action"
|
"suggested_action"
|
||||||
);
|
);
|
||||||
var shorts = new StringFilterGroup(
|
|
||||||
|
final var subscribeButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_SUBSCRIBE_BUTTON,
|
||||||
|
"subscribe_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var joinButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_JOIN_BUTTON,
|
||||||
|
"sponsor_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var shorts = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_SHORTS,
|
SettingsEnum.HIDE_SHORTS,
|
||||||
"shorts_shelf",
|
"shorts_shelf",
|
||||||
"inline_shorts",
|
"inline_shorts",
|
||||||
@ -34,58 +58,22 @@ public final class ShortsFilter extends Filter {
|
|||||||
"shorts_video_cell",
|
"shorts_video_cell",
|
||||||
"shorts_pivot_item"
|
"shorts_pivot_item"
|
||||||
);
|
);
|
||||||
// Use a different filter group for this pattern, as it requires an additional check after matching.
|
|
||||||
shortsShelfHeader = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS,
|
|
||||||
"shelf_header.eml"
|
|
||||||
);
|
|
||||||
identifierFilterGroupList.addAll(shorts, shortsShelfHeader, thanksButton);
|
|
||||||
|
|
||||||
|
pathFilterGroups.addAll(joinButton, subscribeButton, channelBar, soundButton, infoPanel);
|
||||||
// Shorts player components.
|
identifierFilterGroups.addAll(shorts, thanksButton);
|
||||||
var joinButton = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_JOIN_BUTTON,
|
|
||||||
"sponsor_button"
|
|
||||||
);
|
|
||||||
var subscribeButton = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_SUBSCRIBE_BUTTON,
|
|
||||||
"subscribe_button"
|
|
||||||
);
|
|
||||||
channelBar = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_CHANNEL_BAR,
|
|
||||||
REEL_CHANNEL_BAR_PATH
|
|
||||||
);
|
|
||||||
soundButton = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_SOUND_BUTTON,
|
|
||||||
"reel_pivot_button"
|
|
||||||
);
|
|
||||||
infoPanel = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_INFO_PANEL,
|
|
||||||
"shorts_info_panel_overview"
|
|
||||||
);
|
|
||||||
pathFilterGroupList.addAll(joinButton, subscribeButton, channelBar, soundButton, infoPanel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
if (matchedList == pathFilterGroupList) {
|
if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar) return true;
|
||||||
// Always filter if matched.
|
|
||||||
if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar)
|
|
||||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
|
||||||
|
|
||||||
// Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible
|
// Filter the path only when reelChannelBar is visible.
|
||||||
// to avoid false positives.
|
if (pathFilterGroups == matchedList) {
|
||||||
if (!path.startsWith(REEL_CHANNEL_BAR_PATH))
|
return path.contains(REEL_CHANNEL_BAR_PATH);
|
||||||
return false;
|
|
||||||
} else if (matchedGroup == shortsShelfHeader) {
|
|
||||||
// Because the header is used in watch history and possibly other places, check for the index,
|
|
||||||
// which is 0 when the shelf header is used for Shorts.
|
|
||||||
if (matchedIndex != 0) return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Super class handles logging.
|
return identifierFilterGroups == matchedList;
|
||||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideShortsShelf(final View shortsShelfView) {
|
public static void hideShortsShelf(final View shortsShelfView) {
|
||||||
|
@ -10,14 +10,14 @@ public final class VideoQualityMenuFilterPatch extends Filter {
|
|||||||
public static volatile boolean isVideoQualityMenuVisible;
|
public static volatile boolean isVideoQualityMenuVisible;
|
||||||
|
|
||||||
public VideoQualityMenuFilterPatch() {
|
public VideoQualityMenuFilterPatch() {
|
||||||
pathFilterGroupList.addAll(new StringFilterGroup(
|
pathFilterGroups.addAll(new StringFilterGroup(
|
||||||
SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU,
|
SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU,
|
||||||
"quick_quality_sheet_content.eml-js"
|
"quick_quality_sheet_content.eml-js"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
isVideoQualityMenuVisible = true;
|
isVideoQualityMenuVisible = true;
|
||||||
|
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
package app.revanced.integrations.settings;
|
package app.revanced.integrations.settings;
|
||||||
|
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.BOOLEAN;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.FLOAT;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.INTEGER;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.LONG;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.STRING;
|
||||||
|
import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE;
|
||||||
|
import static app.revanced.integrations.settings.SharedPrefCategory.SPONSOR_BLOCK;
|
||||||
|
import static app.revanced.integrations.utils.StringRef.str;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.sponsorblock.SponsorBlockSettings;
|
import app.revanced.integrations.sponsorblock.SponsorBlockSettings;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
import app.revanced.integrations.utils.StringRef;
|
import app.revanced.integrations.utils.StringRef;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
@ -15,13 +29,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.*;
|
|
||||||
import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE;
|
|
||||||
import static app.revanced.integrations.settings.SharedPrefCategory.SPONSOR_BLOCK;
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
|
||||||
import static java.lang.Boolean.FALSE;
|
|
||||||
import static java.lang.Boolean.TRUE;
|
|
||||||
|
|
||||||
|
|
||||||
public enum SettingsEnum {
|
public enum SettingsEnum {
|
||||||
// External downloader
|
// External downloader
|
||||||
@ -82,18 +89,12 @@ public enum SettingsEnum {
|
|||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE),
|
HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE),
|
||||||
HIDE_LIVE_CHAT_BUTTON("revanced_hide_live_chat_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHARE_BUTTON("revanced_hide_share_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_REPORT_BUTTON("revanced_hide_report_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_REMIX_BUTTON("revanced_hide_remix_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_DOWNLOAD_BUTTON("revanced_hide_download_button", BOOLEAN, FALSE),
|
HIDE_DOWNLOAD_BUTTON("revanced_hide_download_button", BOOLEAN, FALSE),
|
||||||
HIDE_THANKS_BUTTON("revanced_hide_thanks_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_PLAYLIST_BUTTON("revanced_hide_playlist_button", BOOLEAN, FALSE),
|
HIDE_PLAYLIST_BUTTON("revanced_hide_playlist_button", BOOLEAN, FALSE),
|
||||||
HIDE_SHOP_BUTTON("revanced_hide_shop_button", BOOLEAN, TRUE),
|
HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, FALSE, "revanced_hide_clip_button_user_dialog_message"),
|
||||||
|
HIDE_ACTION_BUTTONS("revanced_hide_action_buttons", BOOLEAN, FALSE),
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true),
|
|
||||||
DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),
|
DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),
|
||||||
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
|
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
|
||||||
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE),
|
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE),
|
||||||
@ -114,6 +115,7 @@ public enum SettingsEnum {
|
|||||||
HIDE_INFO_CARDS("revanced_hide_info_cards", BOOLEAN, TRUE),
|
HIDE_INFO_CARDS("revanced_hide_info_cards", BOOLEAN, TRUE),
|
||||||
HIDE_LOAD_MORE_BUTTON("revanced_hide_load_more_button", BOOLEAN, TRUE, true),
|
HIDE_LOAD_MORE_BUTTON("revanced_hide_load_more_button", BOOLEAN, TRUE, true),
|
||||||
HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE),
|
HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE),
|
||||||
|
HIDE_PLAYER_OVERLAY("revanced_hide_player_overlay", BOOLEAN, FALSE, true),
|
||||||
HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true),
|
HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true),
|
||||||
HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE),
|
HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE),
|
||||||
HIDE_SEEKBAR_THUMBNAIL("revanced_hide_seekbar_thumbnail", BOOLEAN, FALSE),
|
HIDE_SEEKBAR_THUMBNAIL("revanced_hide_seekbar_thumbnail", BOOLEAN, FALSE),
|
||||||
@ -127,8 +129,9 @@ public enum SettingsEnum {
|
|||||||
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
||||||
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.08.35", true, parents(SPOOF_APP_VERSION)),
|
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.08.35", true, parents(SPOOF_APP_VERSION)),
|
||||||
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
||||||
TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"),
|
|
||||||
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SEEKBAR_COLOR("revanced_seekbar_color", STRING, "#FF0000"), // TODO: delete this
|
||||||
SEEKBAR_CUSTOM_COLOR("revanced_seekbar_custom_color", BOOLEAN, TRUE, true),
|
SEEKBAR_CUSTOM_COLOR("revanced_seekbar_custom_color", BOOLEAN, TRUE, true),
|
||||||
SEEKBAR_CUSTOM_COLOR_VALUE("revanced_seekbar_custom_color_value", STRING, "#FF0000", true, parents(SEEKBAR_CUSTOM_COLOR)),
|
SEEKBAR_CUSTOM_COLOR_VALUE("revanced_seekbar_custom_color_value", STRING, "#FF0000", true, parents(SEEKBAR_CUSTOM_COLOR)),
|
||||||
HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true),
|
HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true),
|
||||||
@ -146,10 +149,6 @@ public enum SettingsEnum {
|
|||||||
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
|
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
|
||||||
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
|
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
|
||||||
|
|
||||||
ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE),
|
|
||||||
ALT_THUMBNAIL_TYPE("revanced_alt_thumbnail_type", INTEGER, 2, parents(ALT_THUMBNAIL)),
|
|
||||||
ALT_THUMBNAIL_FAST_QUALITY("revanced_alt_thumbnail_fast_quality", BOOLEAN, FALSE, parents(ALT_THUMBNAIL)),
|
|
||||||
|
|
||||||
//Player flyout menu items
|
//Player flyout menu items
|
||||||
HIDE_QUALITY_MENU("revanced_hide_player_flyout_quality", BOOLEAN, FALSE),
|
HIDE_QUALITY_MENU("revanced_hide_player_flyout_quality", BOOLEAN, FALSE),
|
||||||
HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE),
|
HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE),
|
||||||
@ -159,7 +158,7 @@ public enum SettingsEnum {
|
|||||||
HIDE_HELP_MENU("revanced_hide_player_flyout_help", BOOLEAN, TRUE),
|
HIDE_HELP_MENU("revanced_hide_player_flyout_help", BOOLEAN, TRUE),
|
||||||
HIDE_SPEED_MENU("revanced_hide_player_flyout_speed", BOOLEAN, FALSE),
|
HIDE_SPEED_MENU("revanced_hide_player_flyout_speed", BOOLEAN, FALSE),
|
||||||
HIDE_MORE_INFO_MENU("revanced_hide_player_flyout_more_info", BOOLEAN, TRUE),
|
HIDE_MORE_INFO_MENU("revanced_hide_player_flyout_more_info", BOOLEAN, TRUE),
|
||||||
HIDE_AUDIO_TRACK_MENU("revanced_hide_player_flyout_audio_track", BOOLEAN, FALSE),
|
HIDE_AUDIO_TRACK_MENU("revanced_hide_player_flyout_audio_track", BOOLEAN, TRUE),
|
||||||
HIDE_WATCH_IN_VR_MENU("revanced_hide_player_flyout_watch_in_vr", BOOLEAN, TRUE),
|
HIDE_WATCH_IN_VR_MENU("revanced_hide_player_flyout_watch_in_vr", BOOLEAN, TRUE),
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
@ -192,7 +191,6 @@ public enum SettingsEnum {
|
|||||||
// Debugging
|
// Debugging
|
||||||
DEBUG("revanced_debug", BOOLEAN, FALSE),
|
DEBUG("revanced_debug", BOOLEAN, FALSE),
|
||||||
DEBUG_STACKTRACE("revanced_debug_stacktrace", BOOLEAN, FALSE, parents(DEBUG)),
|
DEBUG_STACKTRACE("revanced_debug_stacktrace", BOOLEAN, FALSE, parents(DEBUG)),
|
||||||
DEBUG_PROTOBUFFER("revanced_debug_protobuffer", BOOLEAN, FALSE, parents(DEBUG)),
|
|
||||||
DEBUG_TOAST_ON_ERROR("revanced_debug_toast_on_error", BOOLEAN, TRUE, "revanced_debug_toast_on_error_user_dialog_message"),
|
DEBUG_TOAST_ON_ERROR("revanced_debug_toast_on_error", BOOLEAN, TRUE, "revanced_debug_toast_on_error_user_dialog_message"),
|
||||||
|
|
||||||
// ReturnYoutubeDislike
|
// ReturnYoutubeDislike
|
||||||
@ -376,6 +374,14 @@ public enum SettingsEnum {
|
|||||||
// TODO: delete DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU (When? anytime).
|
// TODO: delete DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU (When? anytime).
|
||||||
migrateOldSettingToNew(DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU, SHOW_OLD_VIDEO_QUALITY_MENU);
|
migrateOldSettingToNew(DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU, SHOW_OLD_VIDEO_QUALITY_MENU);
|
||||||
|
|
||||||
|
// TODO: delete this seekbar color migration code
|
||||||
|
String oldSeekbarColorValue = DEPRECATED_SEEKBAR_COLOR.getString();
|
||||||
|
if (!oldSeekbarColorValue.equalsIgnoreCase((String) DEPRECATED_SEEKBAR_COLOR.defaultValue)) {
|
||||||
|
SEEKBAR_CUSTOM_COLOR_VALUE.saveValue(oldSeekbarColorValue);
|
||||||
|
SEEKBAR_CUSTOM_COLOR.saveValue(true);
|
||||||
|
DEPRECATED_SEEKBAR_COLOR.saveValue(DEPRECATED_SEEKBAR_COLOR.defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,24 +80,12 @@ public enum SharedPrefCategory {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public String getString(@NonNull String key, @NonNull String _default) {
|
public String getString(@NonNull String key, @NonNull String _default) {
|
||||||
Objects.requireNonNull(_default);
|
Objects.requireNonNull(_default);
|
||||||
try {
|
return preferences.getString(key, _default);
|
||||||
return preferences.getString(key, _default);
|
|
||||||
} catch (ClassCastException ex) {
|
|
||||||
// Value stored is a completely different type (should never happen).
|
|
||||||
removeConflictingPreferenceKeyValue(key);
|
|
||||||
return _default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean getBoolean(@NonNull String key, boolean _default) {
|
public boolean getBoolean(@NonNull String key, boolean _default) {
|
||||||
try {
|
return preferences.getBoolean(key, _default);
|
||||||
return preferences.getBoolean(key, _default);
|
|
||||||
} catch (ClassCastException ex) {
|
|
||||||
// Value stored is a completely different type (should never happen).
|
|
||||||
removeConflictingPreferenceKeyValue(key);
|
|
||||||
return _default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -30,19 +30,9 @@ public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
|||||||
super.addPattern(pattern, pattern.length, Objects.requireNonNull(callback));
|
super.addPattern(pattern, pattern.length, Objects.requireNonNull(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(@NonNull byte[] textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
|
|
||||||
return super.matches(textToSearch, textToSearch.length, startIndex, endIndex, callbackParameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(@NonNull byte[] textToSearch, int startIndex) {
|
|
||||||
return matches(textToSearch, startIndex, textToSearch.length, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(@NonNull byte[] textToSearch, @Nullable Object callbackParameter) {
|
public boolean matches(@NonNull byte[] textToSearch, @Nullable Object callbackParameter) {
|
||||||
return matches(textToSearch,0, textToSearch.length, callbackParameter);
|
return super.matches(textToSearch, textToSearch.length, callbackParameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,18 +33,8 @@ public final class StringTrieSearch extends TrieSearch<String> {
|
|||||||
super.addPattern(pattern, pattern.length(), Objects.requireNonNull(callback));
|
super.addPattern(pattern, pattern.length(), Objects.requireNonNull(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(@NonNull String textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
|
|
||||||
return super.matches(textToSearch, textToSearch.length(), startIndex, endIndex, callbackParameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(@NonNull String textToSearch, @Nullable Object callbackParameter) {
|
public boolean matches(@NonNull String textToSearch, @Nullable Object callbackParameter) {
|
||||||
return matches(textToSearch, 0, textToSearch.length(), callbackParameter);
|
return super.matches(textToSearch, textToSearch.length(), callbackParameter);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(@NonNull String textToSearch, int startIndex) {
|
|
||||||
return matches(textToSearch, startIndex, textToSearch.length(), null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,17 +243,12 @@ public abstract class TrieSearch<T> {
|
|||||||
root.addPattern(pattern, patternLength, 0, callback);
|
root.addPattern(pattern, patternLength, 0, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean matches(@NonNull T textToSearch, int textToSearchLength, int startIndex, int endIndex,
|
boolean matches(@NonNull T textToSearch, int textToSearchLength, @Nullable Object callbackParameter) {
|
||||||
@Nullable Object callbackParameter) {
|
|
||||||
if (endIndex > textToSearchLength) {
|
|
||||||
throw new IllegalArgumentException("endIndex: " + endIndex
|
|
||||||
+ " is greater than texToSearchLength: " + textToSearchLength);
|
|
||||||
}
|
|
||||||
if (patterns.size() == 0) {
|
if (patterns.size() == 0) {
|
||||||
return false; // No patterns were added.
|
return false; // No patterns were added.
|
||||||
}
|
}
|
||||||
for (int i = startIndex; i < endIndex; i++) {
|
for (int i = 0; i < textToSearchLength; i++) {
|
||||||
if (root.matches(textToSearch, endIndex, i, 0, callbackParameter)) return true;
|
if (root.matches(textToSearch, textToSearchLength, i, 0, callbackParameter)) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -292,27 +287,19 @@ public abstract class TrieSearch<T> {
|
|||||||
*/
|
*/
|
||||||
public abstract void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback<T> callback);
|
public abstract void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback<T> callback);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches through text, looking for any substring that matches any pattern in this tree.
|
* Searches through text, looking for any substring that matches any pattern in this tree.
|
||||||
*
|
*
|
||||||
* @param textToSearch Text to search through.
|
* @param textToSearch Text to search through.
|
||||||
* @param startIndex Index to start searching, inclusive value.
|
|
||||||
* @param endIndex Index to stop matching, exclusive value.
|
|
||||||
* @param callbackParameter Optional parameter passed to the callbacks.
|
* @param callbackParameter Optional parameter passed to the callbacks.
|
||||||
* @return If any pattern matched, and it's callback halted searching.
|
* @return If any pattern matched, and it's callback halted searching.
|
||||||
*/
|
*/
|
||||||
public abstract boolean matches(@NonNull T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter);
|
|
||||||
|
|
||||||
public abstract boolean matches(@NonNull T textToSearch, int startIndex);
|
|
||||||
|
|
||||||
public abstract boolean matches(@NonNull T textToSearch, @Nullable Object callbackParameter);
|
public abstract boolean matches(@NonNull T textToSearch, @Nullable Object callbackParameter);
|
||||||
|
|
||||||
public final boolean matches(@NonNull T textToSearch, int startIndex, int endIndex) {
|
/**
|
||||||
return matches(textToSearch, startIndex, endIndex, null);
|
* Identical to {@link #matches(Object, Object)} but with a null callback parameter.
|
||||||
}
|
*/
|
||||||
|
|
||||||
public final boolean matches(@NonNull T textToSearch) {
|
public final boolean matches(@NonNull T textToSearch) {
|
||||||
return matches(textToSearch, 0);
|
return matches(textToSearch, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ internal object TwiFucker {
|
|||||||
|
|
||||||
private fun JSONObject.jsonCheckAndRemoveRecommendedUsers() {
|
private fun JSONObject.jsonCheckAndRemoveRecommendedUsers() {
|
||||||
if (jsonHasRecommendedUsers()) {
|
if (jsonHasRecommendedUsers()) {
|
||||||
Log.d("ReVanced", "Handle recommended users: $this")
|
Log.d("revanced", "Handle recommended users: $this")
|
||||||
jsonRemoveRecommendedUsers()
|
jsonRemoveRecommendedUsers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ internal object TwiFucker {
|
|||||||
|
|
||||||
private fun JSONObject.jsonCheckAndRemoveThreads() {
|
private fun JSONObject.jsonCheckAndRemoveThreads() {
|
||||||
if (jsonHasThreads()) {
|
if (jsonHasThreads()) {
|
||||||
Log.d("ReVanced", "Handle threads: $this")
|
Log.d("revabced", "Handle threads: $this")
|
||||||
jsonRemoveThreads()
|
jsonRemoveThreads()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ internal object TwiFucker {
|
|||||||
val trendRemoveIndex = mutableListOf<Int>()
|
val trendRemoveIndex = mutableListOf<Int>()
|
||||||
forEachIndexed { trendIndex, trend ->
|
forEachIndexed { trendIndex, trend ->
|
||||||
if (trend.trendHasPromotedMetadata()) {
|
if (trend.trendHasPromotedMetadata()) {
|
||||||
Log.d("ReVanced", "Handle trends ads $trendIndex $trend")
|
Log.d("revanced", "Handle trends ads $trendIndex $trend")
|
||||||
trendRemoveIndex.add(trendIndex)
|
trendRemoveIndex.add(trendIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ internal object TwiFucker {
|
|||||||
entry.entryGetTrends()?.trendRemoveAds()
|
entry.entryGetTrends()?.trendRemoveAds()
|
||||||
|
|
||||||
if (entry.entryHasPromotedMetadata()) {
|
if (entry.entryHasPromotedMetadata()) {
|
||||||
Log.d("ReVanced", "Handle timeline ads $entryIndex $entry")
|
Log.d("revanced", "Handle timeline ads $entryIndex $entry")
|
||||||
removeIndex.add(entryIndex)
|
removeIndex.add(entryIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ internal object TwiFucker {
|
|||||||
val contentItems = entry.entryGetContentItems()
|
val contentItems = entry.entryGetContentItems()
|
||||||
contentItems?.forEachIndexed inner@{ itemIndex, item ->
|
contentItems?.forEachIndexed inner@{ itemIndex, item ->
|
||||||
if (item.entryHasPromotedMetadata()) {
|
if (item.entryHasPromotedMetadata()) {
|
||||||
Log.d("ReVanced", "Handle timeline replies ads $entryIndex $entry")
|
Log.d("revanced", "Handle timeline replies ads $entryIndex $entry")
|
||||||
if (contentItems.length() == 1) {
|
if (contentItems.length() == 1) {
|
||||||
removeIndex.add(entryIndex)
|
removeIndex.add(entryIndex)
|
||||||
} else {
|
} else {
|
||||||
@ -150,7 +150,7 @@ internal object TwiFucker {
|
|||||||
forEachIndexed { entryIndex, entry ->
|
forEachIndexed { entryIndex, entry ->
|
||||||
|
|
||||||
if (entry.entryIsTweetDetailRelatedTweets()) {
|
if (entry.entryIsTweetDetailRelatedTweets()) {
|
||||||
Log.d("ReVanced", "Handle tweet detail related tweets $entryIndex $entry")
|
Log.d("revanced", "Handle tweet detail related tweets $entryIndex $entry")
|
||||||
removeIndex.add(entryIndex)
|
removeIndex.add(entryIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,7 +180,7 @@ internal object TwiFucker {
|
|||||||
forEachIndexed { entryIndex, entry ->
|
forEachIndexed { entryIndex, entry ->
|
||||||
if (!entry.entryIsWhoToFollow()) return@forEachIndexed
|
if (!entry.entryIsWhoToFollow()) return@forEachIndexed
|
||||||
|
|
||||||
Log.d("ReVanced", "Handle whoToFollow $entryIndex $entry")
|
Log.d("revanced", "Handle whoToFollow $entryIndex $entry")
|
||||||
entryRemoveIndex.add(entryIndex)
|
entryRemoveIndex.add(entryIndex)
|
||||||
|
|
||||||
val items = entry.entryGetContentItems()
|
val items = entry.entryGetContentItems()
|
||||||
@ -188,7 +188,7 @@ internal object TwiFucker {
|
|||||||
items?.forEachIndexed { index, item ->
|
items?.forEachIndexed { index, item ->
|
||||||
item.itemContainsPromotedUser().let {
|
item.itemContainsPromotedUser().let {
|
||||||
if (it) {
|
if (it) {
|
||||||
Log.d("ReVanced", "Handle whoToFollow promoted user $index $item")
|
Log.d("revanced", "Handle whoToFollow promoted user $index $item")
|
||||||
userRemoveIndex.add(index)
|
userRemoveIndex.add(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.0.2")
|
classpath("com.android.tools.build:gradle:7.4.2")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
version = 0.116.0
|
version = 0.116.1-dev.1
|
||||||
|
Loading…
Reference in New Issue
Block a user