mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-06-12 05:07:45 +02:00
chore: Separate extensions by app (#3905)
This commit is contained in:
8
extensions/twitch/build.gradle.kts
Normal file
8
extensions/twitch/build.gradle.kts
Normal file
@ -0,0 +1,8 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:twitch:stub"))
|
||||
compileOnly(libs.okhttp)
|
||||
compileOnly(libs.retrofit)
|
||||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.appcompat)
|
||||
}
|
1
extensions/twitch/src/main/AndroidManifest.xml
Normal file
1
extensions/twitch/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest/>
|
@ -0,0 +1,14 @@
|
||||
package app.revanced.extension.twitch;
|
||||
|
||||
public class Utils {
|
||||
|
||||
/* Called from SettingsPatch smali */
|
||||
public static int getStringId(String name) {
|
||||
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "string");
|
||||
}
|
||||
|
||||
/* Called from SettingsPatch smali */
|
||||
public static int getDrawableId(String name) {
|
||||
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "drawable");
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.extension.twitch.adblock;
|
||||
|
||||
import okhttp3.Request;
|
||||
|
||||
public interface IAdblockService {
|
||||
static boolean isVod(Request request) {
|
||||
return request.url().pathSegments().contains("vod");
|
||||
}
|
||||
|
||||
static String channelName(Request request) {
|
||||
for (String pathSegment : request.url().pathSegments()) {
|
||||
if (pathSegment.endsWith(".m3u8")) {
|
||||
return pathSegment.replace(".m3u8", "");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String friendlyName();
|
||||
|
||||
Integer maxAttempts();
|
||||
|
||||
Boolean isAvailable();
|
||||
|
||||
Request rewriteHlsRequest(Request originalRequest);
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
package app.revanced.extension.twitch.adblock;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
public class LuminousService implements IAdblockService {
|
||||
@Override
|
||||
public String friendlyName() {
|
||||
return str("revanced_proxy_luminous");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer maxAttempts() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request rewriteHlsRequest(Request originalRequest) {
|
||||
var type = IAdblockService.isVod(originalRequest) ? "vod" : "playlist";
|
||||
var url = HttpUrl.parse("https://eu.luminous.dev/" +
|
||||
type +
|
||||
"/" +
|
||||
IAdblockService.channelName(originalRequest) +
|
||||
".m3u8" +
|
||||
"%3Fallow_source%3Dtrue%26allow_audio_only%3Dtrue%26fast_bread%3Dtrue"
|
||||
);
|
||||
|
||||
if (url == null) {
|
||||
Logger.printException(() -> "Failed to parse rewritten URL");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Overwrite old request
|
||||
return new Request.Builder()
|
||||
.get()
|
||||
.url(url)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package app.revanced.extension.twitch.adblock;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.twitch.api.RetrofitClient;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
public class PurpleAdblockService implements IAdblockService {
|
||||
private final Map<String, Boolean> tunnels = new HashMap<>() {{
|
||||
put("https://eu1.jupter.ga", false);
|
||||
put("https://eu2.jupter.ga", false);
|
||||
}};
|
||||
|
||||
@Override
|
||||
public String friendlyName() {
|
||||
return str("revanced_proxy_purpleadblock");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer maxAttempts() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isAvailable() {
|
||||
for (String tunnel : tunnels.keySet()) {
|
||||
var success = true;
|
||||
|
||||
try {
|
||||
var response = RetrofitClient.getInstance().getPurpleAdblockApi().ping(tunnel).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
Logger.printException(() ->
|
||||
"PurpleAdBlock tunnel $tunnel returned an error: HTTP code " + response.code()
|
||||
);
|
||||
Logger.printDebug(response::message);
|
||||
|
||||
try (var errorBody = response.errorBody()) {
|
||||
if (errorBody != null) {
|
||||
Logger.printDebug(() -> {
|
||||
try {
|
||||
return errorBody.string();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
success = false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "PurpleAdBlock tunnel $tunnel is unavailable", ex);
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Cache availability data
|
||||
tunnels.put(tunnel, success);
|
||||
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request rewriteHlsRequest(Request originalRequest) {
|
||||
for (Map.Entry<String, Boolean> entry : tunnels.entrySet()) {
|
||||
if (!entry.getValue()) continue;
|
||||
|
||||
var server = entry.getKey();
|
||||
|
||||
// Compose new URL
|
||||
var url = HttpUrl.parse(server + "/channel/" + IAdblockService.channelName(originalRequest));
|
||||
if (url == null) {
|
||||
Logger.printException(() -> "Failed to parse rewritten URL");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Overwrite old request
|
||||
return new Request.Builder()
|
||||
.get()
|
||||
.url(url)
|
||||
.build();
|
||||
}
|
||||
|
||||
Logger.printException(() -> "No tunnels are available");
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package app.revanced.extension.twitch.api;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Url;
|
||||
|
||||
/* only used for service pings */
|
||||
public interface PurpleAdblockApi {
|
||||
@GET /* root */
|
||||
Call<ResponseBody> ping(@Url String baseUrl);
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package app.revanced.extension.twitch.api;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.twitch.adblock.IAdblockService;
|
||||
import app.revanced.extension.twitch.adblock.LuminousService;
|
||||
import app.revanced.extension.twitch.adblock.PurpleAdblockService;
|
||||
import app.revanced.extension.twitch.settings.Settings;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
public class RequestInterceptor implements Interceptor {
|
||||
private IAdblockService activeService = null;
|
||||
|
||||
private static final String PROXY_DISABLED = str("revanced_block_embedded_ads_entry_1");
|
||||
private static final String LUMINOUS_SERVICE = str("revanced_block_embedded_ads_entry_2");
|
||||
private static final String PURPLE_ADBLOCK_SERVICE = str("revanced_block_embedded_ads_entry_3");
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Response intercept(@NonNull Chain chain) throws IOException {
|
||||
var originalRequest = chain.request();
|
||||
|
||||
if (Settings.BLOCK_EMBEDDED_ADS.get().equals(PROXY_DISABLED)) {
|
||||
return chain.proceed(originalRequest);
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Intercepted request to URL:" + originalRequest.url());
|
||||
|
||||
// Skip if not HLS manifest request
|
||||
if (!originalRequest.url().host().contains("usher.ttvnw.net")) {
|
||||
return chain.proceed(originalRequest);
|
||||
}
|
||||
|
||||
final String isVod;
|
||||
if (IAdblockService.isVod(originalRequest)) isVod = "yes";
|
||||
else isVod = "no";
|
||||
|
||||
Logger.printDebug(() -> "Found HLS manifest request. Is VOD? " +
|
||||
isVod +
|
||||
"; Channel: " +
|
||||
IAdblockService.channelName(originalRequest)
|
||||
);
|
||||
|
||||
// None of the services support VODs currently
|
||||
if (IAdblockService.isVod(originalRequest)) return chain.proceed(originalRequest);
|
||||
|
||||
updateActiveService();
|
||||
|
||||
if (activeService != null) {
|
||||
var available = activeService.isAvailable();
|
||||
var rewritten = activeService.rewriteHlsRequest(originalRequest);
|
||||
|
||||
|
||||
if (!available || rewritten == null) {
|
||||
Utils.showToastShort(String.format(
|
||||
str("revanced_embedded_ads_service_unavailable"), activeService.friendlyName()
|
||||
));
|
||||
return chain.proceed(originalRequest);
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Rewritten HLS stream URL: " + rewritten.url());
|
||||
|
||||
var maxAttempts = activeService.maxAttempts();
|
||||
|
||||
for (var i = 1; i <= maxAttempts; i++) {
|
||||
// Execute rewritten request and close body to allow multiple proceed() calls
|
||||
var response = chain.proceed(rewritten);
|
||||
response.close();
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
int attempt = i;
|
||||
Logger.printException(() -> "Request failed (attempt " +
|
||||
attempt +
|
||||
"/" + maxAttempts + "): HTTP error " +
|
||||
response.code() +
|
||||
" (" + response.message() + ")"
|
||||
);
|
||||
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
Logger.printException(() -> "Failed to sleep", e);
|
||||
}
|
||||
} else {
|
||||
// Accept response from ad blocker
|
||||
Logger.printDebug(() -> "Ad-blocker used");
|
||||
return chain.proceed(rewritten);
|
||||
}
|
||||
}
|
||||
|
||||
// maxAttempts exceeded; giving up on using the ad blocker
|
||||
Utils.showToastLong(String.format(
|
||||
str("revanced_embedded_ads_service_failed"),
|
||||
activeService.friendlyName())
|
||||
);
|
||||
}
|
||||
|
||||
// Adblock disabled
|
||||
return chain.proceed(originalRequest);
|
||||
|
||||
}
|
||||
|
||||
private void updateActiveService() {
|
||||
var current = Settings.BLOCK_EMBEDDED_ADS.get();
|
||||
|
||||
if (current.equals(LUMINOUS_SERVICE) && !(activeService instanceof LuminousService))
|
||||
activeService = new LuminousService();
|
||||
else if (current.equals(PURPLE_ADBLOCK_SERVICE) && !(activeService instanceof PurpleAdblockService))
|
||||
activeService = new PurpleAdblockService();
|
||||
else if (current.equals(PROXY_DISABLED))
|
||||
activeService = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.extension.twitch.api;
|
||||
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class RetrofitClient {
|
||||
|
||||
private static RetrofitClient instance = null;
|
||||
private final PurpleAdblockApi purpleAdblockApi;
|
||||
|
||||
private RetrofitClient() {
|
||||
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://localhost" /* dummy */).build();
|
||||
purpleAdblockApi = retrofit.create(PurpleAdblockApi.class);
|
||||
}
|
||||
|
||||
public static synchronized RetrofitClient getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new RetrofitClient();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public PurpleAdblockApi getPurpleAdblockApi() {
|
||||
return purpleAdblockApi;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package app.revanced.extension.twitch.patches;
|
||||
|
||||
import app.revanced.extension.twitch.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AudioAdsPatch {
|
||||
public static boolean shouldBlockAudioAds() {
|
||||
return Settings.BLOCK_AUDIO_ADS.get();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package app.revanced.extension.twitch.patches;
|
||||
|
||||
import app.revanced.extension.twitch.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AutoClaimChannelPointsPatch {
|
||||
public static boolean shouldAutoClaim() {
|
||||
return Settings.AUTO_CLAIM_CHANNEL_POINTS.get();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package app.revanced.extension.twitch.patches;
|
||||
|
||||
import app.revanced.extension.twitch.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class DebugModePatch {
|
||||
public static boolean isDebugModeEnabled() {
|
||||
return Settings.TWITCH_DEBUG_MODE.get();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package app.revanced.extension.twitch.patches;
|
||||
|
||||
import app.revanced.extension.twitch.api.RequestInterceptor;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class EmbeddedAdsPatch {
|
||||
public static RequestInterceptor createRequestInterceptor() {
|
||||
return new RequestInterceptor();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package app.revanced.extension.twitch.patches;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.SpannedString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.twitch.settings.Settings;
|
||||
import tv.twitch.android.shared.chat.util.ClickableUsernameSpan;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ShowDeletedMessagesPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean shouldUseSpoiler() {
|
||||
return "spoiler".equals(Settings.SHOW_DELETED_MESSAGES.get());
|
||||
}
|
||||
|
||||
public static boolean shouldCrossOut() {
|
||||
return "cross-out".equals(Settings.SHOW_DELETED_MESSAGES.get());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Spanned reformatDeletedMessage(Spanned original) {
|
||||
if (!shouldCrossOut())
|
||||
return null;
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder(original);
|
||||
ssb.setSpan(new StrikethroughSpan(), 0, original.length(), 0);
|
||||
ssb.append(" (").append(str("revanced_deleted_msg")).append(")");
|
||||
ssb.setSpan(new StyleSpan(Typeface.ITALIC), original.length(), ssb.length(), 0);
|
||||
|
||||
// Gray-out username
|
||||
ClickableUsernameSpan[] usernameSpans = original.getSpans(0, original.length(), ClickableUsernameSpan.class);
|
||||
if (usernameSpans.length > 0) {
|
||||
ssb.setSpan(new ForegroundColorSpan(Color.parseColor("#ADADB8")), 0, original.getSpanEnd(usernameSpans[0]), 0);
|
||||
}
|
||||
|
||||
return new SpannedString(ssb);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package app.revanced.extension.twitch.patches;
|
||||
|
||||
import app.revanced.extension.twitch.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class VideoAdsPatch {
|
||||
public static boolean shouldBlockVideoAds() {
|
||||
return Settings.BLOCK_VIDEO_ADS.get();
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package app.revanced.extension.twitch.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.twitch.settings.preference.ReVancedPreferenceFragment;
|
||||
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
|
||||
import tv.twitch.android.settings.SettingsActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hooks AppCompatActivity.
|
||||
* <p>
|
||||
* This class is responsible for injecting our own fragment by replacing the AppCompatActivity.
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class AppCompatActivityHook {
|
||||
private static final int REVANCED_SETTINGS_MENU_ITEM_ID = 0x7;
|
||||
private static final String EXTRA_REVANCED_SETTINGS = "app.revanced.twitch.settings";
|
||||
|
||||
/**
|
||||
* Launches SettingsActivity and show ReVanced settings
|
||||
*/
|
||||
public static void startSettingsActivity() {
|
||||
Logger.printDebug(() -> "Launching ReVanced settings");
|
||||
|
||||
final var context = Utils.getContext();
|
||||
|
||||
if (context != null) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(EXTRA_REVANCED_SETTINGS, true);
|
||||
intent.putExtras(bundle);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for easy access in smali
|
||||
* @return Returns string resource id
|
||||
*/
|
||||
public static int getReVancedSettingsString() {
|
||||
return app.revanced.extension.twitch.Utils.getStringId("revanced_settings");
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts settings menu group list creation in SettingsMenuPresenter$Event.MenuGroupsUpdated
|
||||
* @return Returns a modified list of menu groups
|
||||
*/
|
||||
public static List<SettingsMenuGroup> handleSettingMenuCreation(List<SettingsMenuGroup> settingGroups, Object revancedEntry) {
|
||||
List<SettingsMenuGroup> groups = new ArrayList<>(settingGroups);
|
||||
|
||||
if (groups.isEmpty()) {
|
||||
// Create new menu group if none exist yet
|
||||
List<Object> items = new ArrayList<>();
|
||||
items.add(revancedEntry);
|
||||
groups.add(new SettingsMenuGroup(items));
|
||||
} else {
|
||||
// Add to last menu group
|
||||
int groupIdx = groups.size() - 1;
|
||||
List<Object> items = new ArrayList<>(groups.remove(groupIdx).getSettingsMenuItems());
|
||||
items.add(revancedEntry);
|
||||
groups.add(new SettingsMenuGroup(items));
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> settingGroups.size() + " menu groups in list");
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts settings menu group onclick events
|
||||
* @return Returns true if handled, otherwise false
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static boolean handleSettingMenuOnClick(Enum item) {
|
||||
Logger.printDebug(() -> "item " + item.ordinal() + " clicked");
|
||||
if (item.ordinal() != REVANCED_SETTINGS_MENU_ITEM_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
startSettingsActivity();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts fragment loading in SettingsActivity.onCreate
|
||||
* @return Returns true if the revanced settings have been requested by the user, otherwise false
|
||||
*/
|
||||
public static boolean handleSettingsCreation(androidx.appcompat.app.AppCompatActivity base) {
|
||||
if (!base.getIntent().getBooleanExtra(EXTRA_REVANCED_SETTINGS, false)) {
|
||||
Logger.printDebug(() -> "Revanced settings not requested");
|
||||
return false; // User wants to enter another settings fragment
|
||||
}
|
||||
Logger.printDebug(() -> "ReVanced settings requested");
|
||||
|
||||
ReVancedPreferenceFragment fragment = new ReVancedPreferenceFragment();
|
||||
ActionBar supportActionBar = base.getSupportActionBar();
|
||||
if (supportActionBar != null)
|
||||
supportActionBar.setTitle(app.revanced.extension.twitch.Utils.getStringId("revanced_settings"));
|
||||
|
||||
base.getFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(Utils.getResourceIdentifier("fragment_container", "id"), fragment)
|
||||
.commit();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.extension.twitch.settings;
|
||||
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
public class Settings extends BaseSettings {
|
||||
/* Ads */
|
||||
public static final BooleanSetting BLOCK_VIDEO_ADS = new BooleanSetting("revanced_block_video_ads", TRUE);
|
||||
public static final BooleanSetting BLOCK_AUDIO_ADS = new BooleanSetting("revanced_block_audio_ads", TRUE);
|
||||
public static final StringSetting BLOCK_EMBEDDED_ADS = new StringSetting("revanced_block_embedded_ads", "luminous");
|
||||
|
||||
/* Chat */
|
||||
public static final StringSetting SHOW_DELETED_MESSAGES = new StringSetting("revanced_show_deleted_messages", "cross-out");
|
||||
public static final BooleanSetting AUTO_CLAIM_CHANNEL_POINTS = new BooleanSetting("revanced_auto_claim_channel_points", TRUE);
|
||||
|
||||
/* Misc */
|
||||
/**
|
||||
* Not to be confused with {@link BaseSettings#DEBUG}.
|
||||
*/
|
||||
public static final BooleanSetting TWITCH_DEBUG_MODE = new BooleanSetting("revanced_twitch_debug_mode", FALSE, true);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.extension.twitch.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class CustomPreferenceCategory extends PreferenceCategory {
|
||||
public CustomPreferenceCategory(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View rootView) {
|
||||
super.onBindView(rootView);
|
||||
|
||||
if(rootView instanceof TextView) {
|
||||
((TextView) rootView).setTextColor(Color.parseColor("#8161b3"));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.extension.twitch.settings.preference;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
|
||||
import app.revanced.extension.twitch.settings.Settings;
|
||||
|
||||
/**
|
||||
* Preference fragment for ReVanced settings
|
||||
*/
|
||||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
super.initialize();
|
||||
|
||||
// Do anything that forces this apps Settings bundle to load.
|
||||
if (Settings.BLOCK_VIDEO_ADS.get()) {
|
||||
Logger.printDebug(() -> "Block video ads enabled"); // Any statement that references the app settings.
|
||||
}
|
||||
}
|
||||
}
|
17
extensions/twitch/stub/build.gradle.kts
Normal file
17
extensions/twitch/stub/build.gradle.kts
Normal file
@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id(libs.plugins.android.library.get().pluginId)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 33
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
1
extensions/twitch/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/twitch/stub/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest/>
|
@ -0,0 +1,14 @@
|
||||
package tv.twitch.android.feature.settings.menu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// Dummy
|
||||
public final class SettingsMenuGroup {
|
||||
public SettingsMenuGroup(List<Object> settingsMenuItems) {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
|
||||
public List<Object> getSettingsMenuItems() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package tv.twitch.android.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
public class SettingsActivity extends Activity {}
|
@ -0,0 +1,9 @@
|
||||
package tv.twitch.android.shared.chat.util;
|
||||
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.View;
|
||||
|
||||
public final class ClickableUsernameSpan extends ClickableSpan {
|
||||
@Override
|
||||
public void onClick(View widget) {}
|
||||
}
|
Reference in New Issue
Block a user