mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-09-11 16:56:30 +02:00
Merge branch 'ryd-refactor' into ryd-integration
This commit is contained in:
commit
c7cc47baf0
@ -5,12 +5,19 @@ android {
|
||||
buildToolsVersion "30.0.2"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "pl.jakubweg"
|
||||
applicationId "vanced.integrations"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled false
|
||||
|
||||
Properties properties = new Properties()
|
||||
if (rootProject.file("local.properties").exists()) {
|
||||
properties.load(rootProject.file("local.properties").newDataInputStream())
|
||||
}
|
||||
|
||||
buildConfigField "String", "YT_API_KEY", "\"${properties.getProperty("youtubeAPIKey", "")}\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -1,4 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="pl.jakubweg">
|
||||
package="vanced.integrations">
|
||||
</manifest>
|
@ -1,237 +0,0 @@
|
||||
package fi.vanced.libraries.youtube.ads;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.channelName;
|
||||
import static fi.vanced.libraries.youtube.ui.SlimButtonContainer.adBlockButton;
|
||||
import static fi.vanced.utils.VancedUtils.getPreferences;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import fi.razerman.youtube.XGlobals;
|
||||
import fi.vanced.libraries.youtube.player.ChannelModel;
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.utils.ObjectSerializer;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
|
||||
public class VideoAds {
|
||||
public static final String TAG = "VI - VideoAds";
|
||||
public static final String PREFERENCES_NAME = "channel-whitelist";
|
||||
public static boolean isEnabled;
|
||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1";
|
||||
private static final String YT_API_KEY = "replaceMeWithTheYouTubeAPIKey";
|
||||
private static ArrayList<ChannelModel> whiteList;
|
||||
private static Thread fetchThread = null;
|
||||
|
||||
static {
|
||||
whiteList = parseWhitelist(YouTubeTikTokRoot_Application.getAppContext());
|
||||
isEnabled = SharedPrefUtils.getBoolean(YouTubeTikTokRoot_Application.getAppContext(), "youtube", "vanced_videoadwhitelisting_enabled", false);
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void setChannelName(String channelName) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "channel name set to " + channelName);
|
||||
}
|
||||
VideoInformation.channelName = channelName;
|
||||
|
||||
if (!isEnabled) return;
|
||||
|
||||
if (adBlockButton != null) {
|
||||
adBlockButton.changeEnabled(getShouldShowAds());
|
||||
}
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code (CURRENTLY NOT USED)
|
||||
public static void newVideoLoaded(String videoId) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "newVideoLoaded - " + videoId);
|
||||
}
|
||||
|
||||
try {
|
||||
if (fetchThread != null && fetchThread.getState() != Thread.State.TERMINATED) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Interrupting the thread. Current state " + fetchThread.getState());
|
||||
}
|
||||
fetchThread.interrupt();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error in the fetch thread", ex);
|
||||
}
|
||||
|
||||
fetchThread = new Thread(() -> {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching channelId for " + videoId);
|
||||
}
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(YT_API_URL + "/player?key=" + YT_API_KEY).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json; utf-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(2 * 1000);
|
||||
|
||||
// TODO: Actually fetch the version
|
||||
String jsonInputString = "{\"context\": {\"client\": { \"clientName\": \"Android\", \"clientVersion\": \"16.49.37\" } }, \"videoId\": \"" + videoId + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
JSONObject videoInfo = json.getJSONObject("videoDetails");
|
||||
ChannelModel channelModel = new ChannelModel(videoInfo.getString("author"), videoInfo.getString("channelId"));
|
||||
if (debug) {
|
||||
Log.d(TAG, "channelId " + channelModel.getChannelId() + " fetched for author " + channelModel.getAuthor());
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "player fetch response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to fetch channelId", ex);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
fetchThread.start();
|
||||
}
|
||||
|
||||
public static boolean getShouldShowAds() {
|
||||
if (!isEnabled) return false;
|
||||
|
||||
if (channelName == null || channelName.isEmpty() || channelName.trim().isEmpty()) {
|
||||
if (XGlobals.debug) {
|
||||
Log.d(TAG, "getShouldShowAds skipped because channelId was null");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ChannelModel channelModel: whiteList) {
|
||||
if (channelModel.getAuthor().equals(channelName)) {
|
||||
if (XGlobals.debug) {
|
||||
Log.d(TAG, "Video ad whitelist for " + channelName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean addToWhitelist(Context context, String channelName, String channelId) {
|
||||
try {
|
||||
// Check that the channel doesn't exist already (can happen if for example the channel changes the name)
|
||||
// If it exists, remove it
|
||||
Iterator<ChannelModel> iterator = whiteList.iterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
ChannelModel value = iterator.next();
|
||||
if (value.getChannelId().equals(channelId))
|
||||
{
|
||||
if (XGlobals.debug) {
|
||||
Log.d(TAG, String.format("Tried whitelisting an existing channel again. Old info (%1$s | %2$s) - New info (%3$s | %4$s)",
|
||||
value.getAuthor(), value.getChannelId(), channelName, channelId));
|
||||
}
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
whiteList.add(new ChannelModel(channelName, channelId));
|
||||
updateWhitelist(context);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.d(TAG, "Unable to add " + channelName + " with id " + channelId + " to whitelist");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean removeFromWhitelist(Context context, String channelName) {
|
||||
try {
|
||||
//whiteList.removeIf(x -> x.getAuthor().equals(channelName)); // Requires Android N
|
||||
|
||||
Iterator<ChannelModel> iterator = whiteList.iterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
ChannelModel value = iterator.next();
|
||||
if (value.getAuthor().equals(channelName))
|
||||
{
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateWhitelist(context);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.d(TAG, "Unable to remove " + channelName + " from whitelist");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void updateWhitelist(Context context) {
|
||||
if (context == null) return;
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
|
||||
try {
|
||||
editor.putString("channels", ObjectSerializer.serialize(whiteList));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private static ArrayList<ChannelModel> parseWhitelist(Context context) {
|
||||
if (context == null) return new ArrayList<>();
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
try {
|
||||
String channels = preferences.getString("channels", null);
|
||||
if (channels == null) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "channels string was null for ad whitelisting");
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ArrayList<ChannelModel> channelModels = (ArrayList<ChannelModel>) ObjectSerializer.deserialize(channels);
|
||||
if (debug) {
|
||||
Log.d(TAG, channels);
|
||||
for (ChannelModel channelModel: channelModels) {
|
||||
Log.d(TAG, "Ad whitelisted " + channelModel.getAuthor() + " with id of " + channelModel.getChannelId());
|
||||
}
|
||||
}
|
||||
|
||||
return channelModels;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package fi.vanced.libraries.youtube.dialog;
|
||||
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_ENABLED;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_HINT_SHOWN;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.LightingColorFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
import pl.jakubweg.SponsorBlockSettings;
|
||||
|
||||
public class Dialogs {
|
||||
// Inject call from YT to this
|
||||
public static void showDialogsAtStartup(Activity activity) {
|
||||
rydFirstRun(activity);
|
||||
sbFirstRun(activity);
|
||||
}
|
||||
|
||||
private static void rydFirstRun(Activity activity) {
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
boolean enabled = SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, false);
|
||||
boolean hintShown = SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, false);
|
||||
|
||||
// If RYD is enabled or hint has been shown, exit
|
||||
if (enabled || hintShown) {
|
||||
// If RYD is enabled but hint hasn't been shown, mark it as shown
|
||||
if (enabled && !hintShown) {
|
||||
SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
|
||||
} else {
|
||||
builder = new AlertDialog.Builder(activity);
|
||||
}
|
||||
builder.setTitle(str("vanced_ryd"));
|
||||
builder.setIcon(VancedUtils.getIdentifier("reel_dislike_icon", "drawable"));
|
||||
builder.setCancelable(false);
|
||||
builder.setMessage(str("vanced_ryd_firstrun"));
|
||||
builder.setPositiveButton(str("vanced_enable"),
|
||||
(dialog, id) -> {
|
||||
SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, true);
|
||||
SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, true);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
builder.setNegativeButton(str("vanced_disable"),
|
||||
(dialog, id) -> {
|
||||
SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, true);
|
||||
SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, false);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
builder.setNeutralButton(str("vanced_learnmore"), null);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
// Set black background
|
||||
dialog.getWindow().getDecorView().getBackground().setColorFilter(new LightingColorFilter(0xFF000000, VancedUtils.getIdentifier("ytBrandBackgroundSolid", "color")));
|
||||
|
||||
// Set learn more action (set here so clicking it doesn't dismiss the dialog)
|
||||
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
Uri uri = Uri.parse("https://www.returnyoutubedislike.com/");
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
activity.startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
||||
private static void sbFirstRun(Activity activity) {
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
boolean enabled = SharedPrefUtils.getBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, false);
|
||||
boolean hintShown = SharedPrefUtils.getBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, false);
|
||||
|
||||
// If SB is enabled or hint has been shown, exit
|
||||
if (enabled || hintShown) {
|
||||
// If SB is enabled but hint hasn't been shown, mark it as shown
|
||||
if (enabled && !hintShown) {
|
||||
SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
|
||||
} else {
|
||||
builder = new AlertDialog.Builder(activity);
|
||||
}
|
||||
builder.setTitle(str("vanced_sb"));
|
||||
builder.setIcon(VancedUtils.getIdentifier("ic_sb_logo", "drawable"));
|
||||
builder.setCancelable(false);
|
||||
builder.setMessage(str("vanced_sb_firstrun"));
|
||||
builder.setPositiveButton(str("vanced_enable"),
|
||||
(dialog, id) -> {
|
||||
SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, true);
|
||||
SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, true);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
builder.setNegativeButton(str("vanced_disable"),
|
||||
(dialog, id) -> {
|
||||
SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, true);
|
||||
SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, false);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
builder.setNeutralButton(str("vanced_learnmore"), null);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
|
||||
// Set black background
|
||||
dialog.getWindow().getDecorView().getBackground().setColorFilter(new LightingColorFilter(0xFF000000, VancedUtils.getIdentifier("ytBrandBackgroundSolid", "color")));
|
||||
|
||||
// Set learn more action (set here so clicking it doesn't dismiss the dialog)
|
||||
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
Uri uri = Uri.parse("https://sponsor.ajay.app/");
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
activity.startActivity(intent);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_ENABLED;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_HINT_SHOWN;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
@ -44,6 +46,18 @@ public class RYDFragment extends PreferenceFragment {
|
||||
});
|
||||
}
|
||||
|
||||
// Clear hint
|
||||
if (debug) {
|
||||
SwitchPreference preference = new SwitchPreference(context);
|
||||
preferenceScreen.addPreference(preference);
|
||||
preference.setKey(PREFERENCES_KEY_RYD_HINT_SHOWN);
|
||||
preference.setDefaultValue(false);
|
||||
preference.setChecked(SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN));
|
||||
preference.setTitle("Hint debug");
|
||||
preference.setSummary("Debug toggle for clearing the hint shown preference");
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> true);
|
||||
}
|
||||
|
||||
// About category
|
||||
addAboutCategory(context, preferenceScreen);
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ public class RYDSettings {
|
||||
public static final String PREFERENCES_NAME = "ryd";
|
||||
public static final String PREFERENCES_KEY_USERID = "userId";
|
||||
public static final String PREFERENCES_KEY_RYD_ENABLED = "ryd-enabled";
|
||||
public static final String PREFERENCES_KEY_RYD_HINT_SHOWN = "ryd_hint_shown";
|
||||
}
|
||||
|
@ -4,19 +4,13 @@ import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_USERID;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
|
||||
import static fi.vanced.utils.VancedUtils.getPreferences;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
import static fi.vanced.utils.VancedUtils.randomString;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
|
||||
|
||||
public class Registration {
|
||||
private static final String TAG = "VI - RYD - Registration";
|
||||
@ -50,7 +44,7 @@ public class Registration {
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
private void saveUserId(String userId) {
|
||||
public void saveUserId(String userId) {
|
||||
try {
|
||||
if (this.context == null) throw new Exception("Unable to save userId because context was null");
|
||||
|
||||
@ -64,87 +58,10 @@ public class Registration {
|
||||
}
|
||||
|
||||
private String register() {
|
||||
try {
|
||||
// Generate a new userId
|
||||
String userId = randomString(36);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to register the following userId: " + userId);
|
||||
}
|
||||
|
||||
// Get the registration challenge
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/puzzle/registration?userId=" + userId).openConnection();
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration challenge - " + challenge + " with difficulty of " + difficulty);
|
||||
}
|
||||
|
||||
// Solve the puzzle
|
||||
String solution = Utils.solvePuzzle(challenge, difficulty);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration confirmation solution is " + solution);
|
||||
}
|
||||
|
||||
return confirmRegistration(userId, solution);
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Registration response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to register userId", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String confirmRegistration(String userId, String solution) {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to confirm registration for the following userId: " + userId + " with solution: " + solution);
|
||||
}
|
||||
|
||||
// Confirm registration
|
||||
HttpURLConnection confirmationCon = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/puzzle/registration?userId=" + userId).openConnection();
|
||||
confirmationCon.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
confirmationCon.setRequestMethod("POST");
|
||||
confirmationCon.setRequestProperty("Content-Type", "application/json");
|
||||
confirmationCon.setRequestProperty("Accept", "application/json");
|
||||
confirmationCon.setDoOutput(true);
|
||||
confirmationCon.setConnectTimeout(5 * 1000);
|
||||
|
||||
String jsonInputString = "{\"solution\": \"" + solution + "\"}";
|
||||
try(OutputStream os = confirmationCon.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (confirmationCon.getResponseCode() == 200) {
|
||||
String result = parseJson(confirmationCon);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration confirmation result was " + result);
|
||||
}
|
||||
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
saveUserId(userId);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration was successful for user " + userId);
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Registration confirmation response was " + confirmationCon.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to confirm registration", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
return RYDRequester.register(userId, this);
|
||||
}
|
||||
}
|
||||
|
@ -2,37 +2,28 @@ package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.dislikeCount;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_ENABLED;
|
||||
import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
|
||||
import static fi.vanced.utils.VancedUtils.getIdentifier;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
|
||||
import android.content.Context;
|
||||
import android.icu.text.CompactDecimalFormat;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.dislikeCount;
|
||||
|
||||
import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
|
||||
public class ReturnYouTubeDislikes {
|
||||
public static final String RYD_API_URL = "https://returnyoutubedislikeapi.com";
|
||||
public static boolean isEnabled;
|
||||
private static final String TAG = "VI - RYD";
|
||||
public static final String TAG = "VI - RYD";
|
||||
private static View _dislikeView = null;
|
||||
private static Thread _dislikeFetchThread = null;
|
||||
private static Thread _votingThread = null;
|
||||
@ -96,39 +87,7 @@ public class ReturnYouTubeDislikes {
|
||||
Log.e(TAG, "Error in the dislike fetch thread", ex);
|
||||
}
|
||||
|
||||
_dislikeFetchThread = new Thread(() -> {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching dislikes for " + videoId);
|
||||
}
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(RYD_API_URL + "/votes?videoId=" + videoId).openConnection();
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
dislikeCount = json.getInt("dislikes");
|
||||
if (debug) {
|
||||
Log.d(TAG, "dislikes fetched - " + dislikeCount);
|
||||
}
|
||||
|
||||
// Set the dislikes
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable () {
|
||||
@Override
|
||||
public void run () {
|
||||
trySetDislikes(formatDislikes(dislikeCount));
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "dislikes fetch response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
dislikeCount = null;
|
||||
Log.e(TAG, "Failed to fetch dislikes", ex);
|
||||
return;
|
||||
}
|
||||
});
|
||||
_dislikeFetchThread = new Thread(() -> RYDRequester.fetchDislikes(videoId));
|
||||
_dislikeFetchThread.start();
|
||||
}
|
||||
|
||||
@ -211,7 +170,7 @@ public class ReturnYouTubeDislikes {
|
||||
return originalText;
|
||||
}
|
||||
|
||||
private static void trySetDislikes(String dislikeCount) {
|
||||
public static void trySetDislikes(String dislikeCount) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
try {
|
||||
@ -280,8 +239,6 @@ public class ReturnYouTubeDislikes {
|
||||
Log.d(TAG, "Like button " + likeActive + " | Dislike button " + dislikeActive);
|
||||
}
|
||||
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), "Voting value: " + votingValue, Toast.LENGTH_SHORT).show();
|
||||
|
||||
sendVote(votingValue);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@ -345,7 +302,7 @@ public class ReturnYouTubeDislikes {
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDislikes(int dislikes) {
|
||||
public static String formatDislikes(int dislikes) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && compactNumberFormatter != null) {
|
||||
final String formatted = compactNumberFormatter.format(dislikes);
|
||||
if (debug) {
|
||||
|
@ -1,13 +1,14 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = "VI - RYD - Utils";
|
||||
|
||||
static String solvePuzzle(String challenge, int difficulty) {
|
||||
public static String solvePuzzle(String challenge, int difficulty) {
|
||||
byte[] decodedChallenge = Base64.decode(challenge, Base64.NO_WRAP);
|
||||
|
||||
byte[] buffer = new byte[20];
|
||||
|
@ -1,17 +1,11 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
|
||||
|
||||
public class Voting {
|
||||
private static final String TAG = "VI - RYD - Voting";
|
||||
@ -25,96 +19,10 @@ public class Voting {
|
||||
}
|
||||
|
||||
public boolean sendVote(String videoId, int vote) {
|
||||
try {
|
||||
String userId = registration.getUserId();
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to vote the following video: " + videoId + " with vote " + vote + " and userId: " + userId);
|
||||
}
|
||||
|
||||
// Send the vote
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/interact/vote").openConnection();
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
String voteJsonString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = voteJsonString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote challenge - " + challenge + " with difficulty of " + difficulty);
|
||||
}
|
||||
|
||||
// Solve the puzzle
|
||||
String solution = Utils.solvePuzzle(challenge, difficulty);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote confirmation solution is " + solution);
|
||||
}
|
||||
|
||||
// Confirm vote
|
||||
return confirmVote(userId, videoId, solution);
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Vote response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to send vote", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean confirmVote(String userId, String videoId, String solution) {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to confirm vote for video: " + videoId + " with solution " + solution + " and userId: " + userId);
|
||||
}
|
||||
|
||||
// Confirm vote
|
||||
HttpURLConnection confirmationCon = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/interact/confirmVote").openConnection();
|
||||
confirmationCon.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
confirmationCon.setRequestMethod("POST");
|
||||
confirmationCon.setRequestProperty("Content-Type", "application/json");
|
||||
confirmationCon.setRequestProperty("Accept", "application/json");
|
||||
confirmationCon.setDoOutput(true);
|
||||
confirmationCon.setConnectTimeout(5 * 1000);
|
||||
|
||||
String jsonInputString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}";
|
||||
try(OutputStream os = confirmationCon.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (confirmationCon.getResponseCode() == 200) {
|
||||
String result = parseJson(confirmationCon);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote confirmation result was " + result);
|
||||
}
|
||||
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote was successful for user " + userId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Vote confirmation response was " + confirmationCon.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to send vote", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
return RYDRequester.sendVote(videoId, userId, vote);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,226 @@
|
||||
package fi.vanced.libraries.youtube.ryd.requests;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.dislikeCount;
|
||||
import static fi.vanced.libraries.youtube.ryd.ReturnYouTubeDislikes.TAG;
|
||||
import static fi.vanced.utils.requests.Requester.parseJson;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import fi.vanced.libraries.youtube.ryd.Registration;
|
||||
import fi.vanced.libraries.youtube.ryd.ReturnYouTubeDislikes;
|
||||
import fi.vanced.libraries.youtube.ryd.Utils;
|
||||
import fi.vanced.utils.requests.Requester;
|
||||
import fi.vanced.utils.requests.Route;
|
||||
|
||||
public class RYDRequester {
|
||||
private static final String RYD_API_URL = "https://returnyoutubedislikeapi.com/";
|
||||
|
||||
private RYDRequester() {}
|
||||
|
||||
public static void fetchDislikes(String videoId) {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching dislikes for " + videoId);
|
||||
}
|
||||
HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.GET_DISLIKES, videoId);
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = getJSONObject(connection);
|
||||
dislikeCount = json.getInt("dislikes");
|
||||
if (debug) {
|
||||
Log.d(TAG, "dislikes fetched - " + dislikeCount);
|
||||
}
|
||||
|
||||
// Set the dislikes
|
||||
new Handler(Looper.getMainLooper()).post(() -> ReturnYouTubeDislikes.trySetDislikes(ReturnYouTubeDislikes.formatDislikes(dislikeCount)));
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "dislikes fetch response was " + connection.getResponseCode());
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
dislikeCount = null;
|
||||
Log.e(TAG, "Failed to fetch dislikes", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static String register(String userId, Registration registration) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.GET_REGISTRATION, userId);
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = getJSONObject(connection);
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration challenge - " + challenge + " with difficulty of " + difficulty);
|
||||
}
|
||||
|
||||
// Solve the puzzle
|
||||
String solution = Utils.solvePuzzle(challenge, difficulty);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration confirmation solution is " + solution);
|
||||
}
|
||||
|
||||
return confirmRegistration(userId, solution, registration);
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Registration response was " + connection.getResponseCode());
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex){
|
||||
Log.e(TAG, "Failed to register userId", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String confirmRegistration(String userId, String solution, Registration registration) {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to confirm registration for the following userId: " + userId + " with solution: " + solution);
|
||||
}
|
||||
|
||||
HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.CONFIRM_REGISTRATION, userId);
|
||||
applyCommonRequestSettings(connection);
|
||||
|
||||
String jsonInputString = "{\"solution\": \"" + solution + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (connection.getResponseCode() == 200) {
|
||||
String result = parseJson(connection);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration confirmation result was " + result);
|
||||
}
|
||||
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
registration.saveUserId(userId);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration was successful for user " + userId);
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Registration confirmation response was " + connection.getResponseCode());
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to confirm registration", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean sendVote(String videoId, String userId, int vote) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.SEND_VOTE);
|
||||
applyCommonRequestSettings(connection);
|
||||
|
||||
String voteJsonString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = voteJsonString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = getJSONObject(connection);
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote challenge - " + challenge + " with difficulty of " + difficulty);
|
||||
}
|
||||
|
||||
// Solve the puzzle
|
||||
String solution = Utils.solvePuzzle(challenge, difficulty);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote confirmation solution is " + solution);
|
||||
}
|
||||
|
||||
// Confirm vote
|
||||
return confirmVote(videoId, userId, solution);
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Vote response was " + connection.getResponseCode());
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to send vote", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean confirmVote(String videoId, String userId, String solution) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.CONFIRM_VOTE);
|
||||
applyCommonRequestSettings(connection);
|
||||
|
||||
String jsonInputString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (connection.getResponseCode() == 200) {
|
||||
String result = parseJson(connection);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote confirmation result was " + result);
|
||||
}
|
||||
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote was successful for user " + userId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Vote confirmation response was " + connection.getResponseCode());
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to confirm vote", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// utils
|
||||
|
||||
private static void applyCommonRequestSettings(HttpURLConnection connection) throws Exception {
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(RYD_API_URL, route, params);
|
||||
}
|
||||
|
||||
private static JSONObject getJSONObject(HttpURLConnection connection) throws Exception {
|
||||
return Requester.getJSONObject(connection);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package fi.vanced.libraries.youtube.ryd.requests;
|
||||
|
||||
import static fi.vanced.utils.requests.Route.Method.GET;
|
||||
import static fi.vanced.utils.requests.Route.Method.POST;
|
||||
|
||||
import fi.vanced.utils.requests.Route;
|
||||
|
||||
public class RYDRoutes {
|
||||
public static final Route SEND_VOTE = new Route(POST,"interact/vote");
|
||||
public static final Route CONFIRM_VOTE = new Route(POST,"interact/confirmVote");
|
||||
public static final Route GET_DISLIKES = new Route(GET, "votes?videoId={video_id}");
|
||||
public static final Route GET_REGISTRATION = new Route(GET, "puzzle/registration?userId={user_id}");
|
||||
public static final Route CONFIRM_REGISTRATION = new Route(POST,"puzzle/registration?userId={user_id}");
|
||||
|
||||
private RYDRoutes() {}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.ads.VideoAds.getShouldShowAds;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import fi.vanced.libraries.youtube.ads.VideoAds;
|
||||
import fi.vanced.libraries.youtube.player.ChannelModel;
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class AdBlock extends SlimButton {
|
||||
private static final String TAG = "VI - AdBlock - Button";
|
||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1";
|
||||
private static final String YT_API_KEY = "replaceMeWithTheYouTubeAPIKey";
|
||||
|
||||
public AdBlock(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, SharedPrefUtils.getBoolean(context, "youtube", "vanced_videoadwhitelisting_enabled", false));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_ad_button", "drawable"));
|
||||
this.button_text.setText(str("action_ads"));
|
||||
changeEnabled(getShouldShowAds());
|
||||
}
|
||||
|
||||
public void changeEnabled(boolean enabled) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "changeEnabled " + enabled);
|
||||
}
|
||||
this.button_icon.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
this.view.setEnabled(false);
|
||||
if (this.button_icon.isEnabled()) {
|
||||
removeFromWhitelist();
|
||||
return;
|
||||
}
|
||||
//this.button_icon.setEnabled(!this.button_icon.isEnabled());
|
||||
|
||||
addToWhiteList(this.view, this.button_icon);
|
||||
}
|
||||
|
||||
private void removeFromWhitelist() {
|
||||
try {
|
||||
VideoAds.removeFromWhitelist(this.context, VideoInformation.channelName);
|
||||
this.button_icon.setEnabled(false);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to remove from whitelist", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
this.view.setEnabled(true);
|
||||
}
|
||||
|
||||
private void addToWhiteList(View view, ImageView buttonIcon) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching channelId for " + currentVideoId);
|
||||
}
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(YT_API_URL + "/player?key=" + YT_API_KEY).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json; utf-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(2 * 1000);
|
||||
|
||||
// TODO: Actually fetch the version
|
||||
String jsonInputString = "{\"context\": {\"client\": { \"clientName\": \"Android\", \"clientVersion\": \"16.49.37\" } }, \"videoId\": \"" + currentVideoId + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
JSONObject videoInfo = json.getJSONObject("videoDetails");
|
||||
ChannelModel channelModel = new ChannelModel(videoInfo.getString("author"), videoInfo.getString("channelId"));
|
||||
if (debug) {
|
||||
Log.d(TAG, "channelId " + channelModel.getChannelId() + " fetched for author " + channelModel.getAuthor());
|
||||
}
|
||||
|
||||
boolean success = VideoAds.addToWhitelist(this.context, channelModel.getAuthor(), channelModel.getChannelId());
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
if (success) {
|
||||
buttonIcon.setEnabled(true);
|
||||
Toast.makeText(context, "Channel " + channelModel.getAuthor() + " whitelisted", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
else {
|
||||
buttonIcon.setEnabled(false);
|
||||
Toast.makeText(context, "Channel " + channelModel.getAuthor() + " failed to whitelist", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
view.setEnabled(true);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (debug) {
|
||||
Log.d(TAG, "player fetch response was " + connection.getResponseCode());
|
||||
}
|
||||
|
||||
buttonIcon.setEnabled(false);
|
||||
this.view.setEnabled(true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to fetch channelId", ex);
|
||||
this.view.setEnabled(true);
|
||||
return;
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.libraries.youtube.whitelisting.Whitelist;
|
||||
import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
|
||||
import fi.vanced.libraries.youtube.whitelisting.requests.WhitelistRequester;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class AdButton extends SlimButton {
|
||||
public static final String TAG = "VI - AdButton - Button";
|
||||
|
||||
public AdButton(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID,
|
||||
SharedPrefUtils.getBoolean(context, WhitelistType.ADS.getSharedPreferencesName(), WhitelistType.ADS.getPreferenceEnabledName(), false));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_ad_button", "drawable"));
|
||||
this.button_text.setText(str("action_ads"));
|
||||
changeEnabled(Whitelist.shouldShowAds());
|
||||
}
|
||||
|
||||
public void changeEnabled(boolean enabled) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "changeEnabled " + enabled);
|
||||
}
|
||||
this.button_icon.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
this.view.setEnabled(false);
|
||||
if (this.button_icon.isEnabled()) {
|
||||
removeFromWhitelist();
|
||||
return;
|
||||
}
|
||||
//this.button_icon.setEnabled(!this.button_icon.isEnabled());
|
||||
|
||||
addToWhiteList(this.view, this.button_icon);
|
||||
}
|
||||
|
||||
private void removeFromWhitelist() {
|
||||
try {
|
||||
Whitelist.removeFromWhitelist(WhitelistType.ADS, this.context, VideoInformation.channelName);
|
||||
changeEnabled(false);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to remove from whitelist", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
this.view.setEnabled(true);
|
||||
}
|
||||
|
||||
private void addToWhiteList(View view, ImageView buttonIcon) {
|
||||
new Thread(() -> {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching channelId for " + currentVideoId);
|
||||
}
|
||||
WhitelistRequester.addChannelToWhitelist(WhitelistType.ADS, view, buttonIcon, this.context);
|
||||
}).start();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.libraries.youtube.whitelisting.Whitelist;
|
||||
import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
|
||||
import fi.vanced.libraries.youtube.whitelisting.requests.WhitelistRequester;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class SBWhitelistButton extends SlimButton {
|
||||
public static final String TAG = "VI - SBWhitelistButton";
|
||||
|
||||
public SBWhitelistButton(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID,
|
||||
SharedPrefUtils.getBoolean(context, WhitelistType.SPONSORBLOCK.getSharedPreferencesName(), WhitelistType.SPONSORBLOCK.getPreferenceEnabledName(), false));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_sb_button", "drawable"));
|
||||
this.button_text.setText(str("action_segments"));
|
||||
changeEnabled(Whitelist.isChannelSBWhitelisted());
|
||||
}
|
||||
|
||||
public void changeEnabled(boolean enabled) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "changeEnabled " + enabled);
|
||||
}
|
||||
this.button_icon.setEnabled(!enabled); // enabled == true -> strikethrough (no segments), enabled == false -> clear (segments)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
this.view.setEnabled(false);
|
||||
if (Whitelist.isChannelSBWhitelisted()) {
|
||||
removeFromWhitelist();
|
||||
return;
|
||||
}
|
||||
//this.button_icon.setEnabled(!this.button_icon.isEnabled());
|
||||
|
||||
addToWhiteList(this.view, this.button_icon);
|
||||
}
|
||||
|
||||
private void removeFromWhitelist() {
|
||||
try {
|
||||
Whitelist.removeFromWhitelist(WhitelistType.SPONSORBLOCK, this.context, VideoInformation.channelName);
|
||||
changeEnabled(false);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to remove from whitelist", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
this.view.setEnabled(true);
|
||||
}
|
||||
|
||||
private void addToWhiteList(View view, ImageView buttonIcon) {
|
||||
new Thread(() -> {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching channelId for " + currentVideoId);
|
||||
}
|
||||
WhitelistRequester.addChannelToWhitelist(WhitelistType.SPONSORBLOCK, view, buttonIcon, this.context);
|
||||
}).start();
|
||||
}
|
||||
}
|
@ -10,7 +10,8 @@ import android.view.ViewGroup;
|
||||
|
||||
import com.google.android.apps.youtube.app.ui.SlimMetadataScrollableButtonContainerLayout;
|
||||
|
||||
import fi.vanced.libraries.youtube.ads.VideoAds;
|
||||
import fi.vanced.libraries.youtube.whitelisting.Whitelist;
|
||||
import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
@ -19,7 +20,8 @@ public class SlimButtonContainer extends SlimMetadataScrollableButtonContainerLa
|
||||
private ViewGroup container;
|
||||
private CopyButton copyButton;
|
||||
private CopyWithTimestamp copyWithTimestampButton;
|
||||
public static AdBlock adBlockButton;
|
||||
public static AdButton adBlockButton;
|
||||
public static SBWhitelistButton sbWhitelistButton;
|
||||
private final Context context;
|
||||
SharedPreferences.OnSharedPreferenceChangeListener listener;
|
||||
|
||||
@ -48,8 +50,8 @@ public class SlimButtonContainer extends SlimMetadataScrollableButtonContainerLa
|
||||
|
||||
copyButton = new CopyButton(context, this);
|
||||
copyWithTimestampButton = new CopyWithTimestamp(context, this);
|
||||
adBlockButton = new AdBlock(context, this);
|
||||
new SponsorBlock(context, this);
|
||||
adBlockButton = new AdButton(context, this);
|
||||
sbWhitelistButton = new SBWhitelistButton(context, this);
|
||||
new SponsorBlockVoting(context, this);
|
||||
|
||||
addSharedPrefsChangeListener();
|
||||
@ -73,9 +75,20 @@ public class SlimButtonContainer extends SlimMetadataScrollableButtonContainerLa
|
||||
copyWithTimestampButton.setVisible(ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_timestamp_button_list"));
|
||||
return;
|
||||
}
|
||||
if ("vanced_videoadwhitelisting_enabled".equals(key) && adBlockButton != null) {
|
||||
VideoAds.isEnabled = SharedPrefUtils.getBoolean(context, "youtube", "vanced_videoadwhitelisting_enabled", false);
|
||||
adBlockButton.setVisible(VideoAds.isEnabled);
|
||||
WhitelistType whitelistAds = WhitelistType.ADS;
|
||||
String adsEnabledPreferenceName = whitelistAds.getPreferenceEnabledName();
|
||||
if (adsEnabledPreferenceName.equals(key) && adBlockButton != null) {
|
||||
boolean enabled = SharedPrefUtils.getBoolean(context, whitelistAds.getSharedPreferencesName(), adsEnabledPreferenceName, false);
|
||||
Whitelist.setEnabled(whitelistAds, enabled);
|
||||
adBlockButton.setVisible(enabled);
|
||||
return;
|
||||
}
|
||||
WhitelistType whitelistSB = WhitelistType.SPONSORBLOCK;
|
||||
String sbEnabledPreferenceName = whitelistSB.getPreferenceEnabledName();
|
||||
if (sbEnabledPreferenceName.equals(key) && sbWhitelistButton != null) {
|
||||
boolean enabled = SharedPrefUtils.getBoolean(context, whitelistSB.getSharedPreferencesName(), sbEnabledPreferenceName, false);
|
||||
Whitelist.setEnabled(whitelistSB, enabled);
|
||||
sbWhitelistButton.setVisible(enabled);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -84,7 +97,9 @@ public class SlimButtonContainer extends SlimMetadataScrollableButtonContainerLa
|
||||
}
|
||||
};
|
||||
|
||||
context.getSharedPreferences("youtube", Context.MODE_PRIVATE)
|
||||
context.getSharedPreferences(WhitelistType.ADS.getSharedPreferencesName(), Context.MODE_PRIVATE)
|
||||
.registerOnSharedPreferenceChangeListener(listener);
|
||||
context.getSharedPreferences(WhitelistType.SPONSORBLOCK.getSharedPreferencesName(), Context.MODE_PRIVATE)
|
||||
.registerOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoHelpers;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class SponsorBlock extends SlimButton {
|
||||
public SponsorBlock(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, false);
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_sb_logo", "drawable"));
|
||||
this.button_text.setText("SB");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), "Nothing atm", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package fi.vanced.libraries.youtube.whitelisting;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.channelName;
|
||||
import static fi.vanced.libraries.youtube.ui.SlimButtonContainer.adBlockButton;
|
||||
import static fi.vanced.libraries.youtube.ui.SlimButtonContainer.sbWhitelistButton;
|
||||
import static fi.vanced.utils.VancedUtils.getPreferences;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.ChannelModel;
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.utils.ObjectSerializer;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class Whitelist {
|
||||
private static final String TAG = "VI - Whitelisting";
|
||||
private static final Map<WhitelistType, ArrayList<ChannelModel>> whitelistMap = parseWhitelist(YouTubeTikTokRoot_Application.getAppContext());
|
||||
private static final Map<WhitelistType, Boolean> enabledMap = parseEnabledMap(YouTubeTikTokRoot_Application.getAppContext());
|
||||
|
||||
private Whitelist() {}
|
||||
|
||||
// injected calls
|
||||
|
||||
public static boolean shouldShowAds() {
|
||||
return isWhitelisted(WhitelistType.ADS);
|
||||
}
|
||||
|
||||
public static void setChannelName(String channelName) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "channel name set to " + channelName);
|
||||
}
|
||||
VideoInformation.channelName = channelName;
|
||||
|
||||
if (enabledMap.get(WhitelistType.ADS) && adBlockButton != null) {
|
||||
adBlockButton.changeEnabled(shouldShowAds());
|
||||
}
|
||||
if (enabledMap.get(WhitelistType.SPONSORBLOCK) && sbWhitelistButton != null) {
|
||||
sbWhitelistButton.changeEnabled(isChannelSBWhitelisted());
|
||||
}
|
||||
}
|
||||
|
||||
// the rest
|
||||
|
||||
public static boolean isChannelSBWhitelisted() {
|
||||
return isWhitelisted(WhitelistType.SPONSORBLOCK);
|
||||
}
|
||||
|
||||
private static Map<WhitelistType, ArrayList<ChannelModel>> parseWhitelist(Context context) {
|
||||
if (context == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
WhitelistType[] whitelistTypes = WhitelistType.values();
|
||||
Map<WhitelistType, ArrayList<ChannelModel>> whitelistMap = new EnumMap<>(WhitelistType.class);
|
||||
|
||||
for (WhitelistType whitelistType : whitelistTypes) {
|
||||
SharedPreferences preferences = VancedUtils.getPreferences(context, whitelistType.getPreferencesName());
|
||||
String serializedChannels = preferences.getString("channels", null);
|
||||
if (serializedChannels == null) {
|
||||
if (debug) {
|
||||
Log.d(TAG, String.format("channels string was null for %s whitelisting", whitelistType));
|
||||
}
|
||||
whitelistMap.put(whitelistType, new ArrayList<>());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ArrayList<ChannelModel> deserializedChannels = (ArrayList<ChannelModel>) ObjectSerializer.deserialize(serializedChannels);
|
||||
if (debug) {
|
||||
Log.d(TAG, serializedChannels);
|
||||
for (ChannelModel channel : deserializedChannels) {
|
||||
Log.d(TAG, String.format("Whitelisted channel %s (%s) for type %s", channel.getAuthor(), channel.getChannelId(), whitelistType));
|
||||
}
|
||||
}
|
||||
whitelistMap.put(whitelistType, deserializedChannels);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
return whitelistMap;
|
||||
}
|
||||
|
||||
private static Map<WhitelistType, Boolean> parseEnabledMap(Context context) {
|
||||
Map<WhitelistType, Boolean> enabledMap = new EnumMap<>(WhitelistType.class);
|
||||
for (WhitelistType whitelistType : WhitelistType.values()) {
|
||||
enabledMap.put(whitelistType, SharedPrefUtils.getBoolean(context, whitelistType.getSharedPreferencesName(), whitelistType.getPreferenceEnabledName()));
|
||||
}
|
||||
return enabledMap;
|
||||
}
|
||||
|
||||
private static boolean isWhitelisted(WhitelistType whitelistType) {
|
||||
boolean isEnabled = enabledMap.get(whitelistType);
|
||||
if (!isEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (channelName == null || channelName.trim().isEmpty()) {
|
||||
if (debug) {
|
||||
Log.d(TAG, String.format("Can't check whitelist status for %s because channel name was missing", whitelistType));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
List<ChannelModel> whitelistedChannels = whitelistMap.get(whitelistType);
|
||||
for (ChannelModel channel : whitelistedChannels) {
|
||||
if (channel.getAuthor().equals(channelName)) {
|
||||
if (debug) {
|
||||
Log.d(TAG, String.format("Whitelist for channel %s for type %s", channelName, whitelistType));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean addToWhitelist(WhitelistType whitelistType, Context context, ChannelModel channel) {
|
||||
ArrayList<ChannelModel> whitelisted = whitelistMap.get(whitelistType);
|
||||
for (ChannelModel whitelistedChannel : whitelisted) {
|
||||
String channelId = channel.getChannelId();
|
||||
if (whitelistedChannel.getChannelId().equals(channelId)) {
|
||||
if (debug) {
|
||||
Log.d(TAG, String.format("Tried whitelisting an existing channel again. Old info (%1$s | %2$s) - New info (%3$s | %4$s)",
|
||||
whitelistedChannel.getAuthor(), channelId, channelName, channelId));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
whitelisted.add(channel);
|
||||
return updateWhitelist(whitelistType, whitelisted, context);
|
||||
}
|
||||
|
||||
public static void removeFromWhitelist(WhitelistType whitelistType, Context context, String channelName) {
|
||||
ArrayList<ChannelModel> channels = whitelistMap.get(whitelistType);
|
||||
Iterator<ChannelModel> iterator = channels.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ChannelModel channel = iterator.next();
|
||||
if (channel.getAuthor().equals(channelName)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
boolean success = updateWhitelist(whitelistType, channels, context);
|
||||
String friendlyName = whitelistType.getFriendlyName();
|
||||
if (success) {
|
||||
Toast.makeText(context, str("vanced_whitelisting_removed", channelName, friendlyName), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
else {
|
||||
Toast.makeText(context, str("vanced_whitelisting_remove_failed", channelName, friendlyName), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean updateWhitelist(WhitelistType whitelistType, ArrayList<ChannelModel> channels, Context context) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
SharedPreferences preferences = getPreferences(context, whitelistType.getPreferencesName());
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
|
||||
try {
|
||||
editor.putString("channels", ObjectSerializer.serialize(channels));
|
||||
editor.apply();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setEnabled(WhitelistType whitelistType, boolean enabled) {
|
||||
enabledMap.put(whitelistType, enabled);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package fi.vanced.libraries.youtube.whitelisting;
|
||||
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import pl.jakubweg.SponsorBlockSettings;
|
||||
|
||||
public enum WhitelistType {
|
||||
ADS("youtube", "vanced_whitelist_ads_enabled"),
|
||||
SPONSORBLOCK(SponsorBlockSettings.PREFERENCES_NAME, "vanced_whitelist_sb_enabled");
|
||||
|
||||
private final String friendlyName;
|
||||
private final String preferencesName;
|
||||
private final String sharedPreferencesName;
|
||||
private final String preferenceEnabledName;
|
||||
|
||||
WhitelistType(String sharedPreferencesName, String preferenceEnabledName) {
|
||||
this.friendlyName = str("vanced_whitelisting_" + name().toLowerCase());
|
||||
this.sharedPreferencesName = sharedPreferencesName;
|
||||
this.preferencesName = "whitelist_" + name();
|
||||
this.preferenceEnabledName = preferenceEnabledName;
|
||||
}
|
||||
|
||||
public String getFriendlyName() {
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
public String getSharedPreferencesName() {
|
||||
return sharedPreferencesName;
|
||||
}
|
||||
|
||||
public String getPreferencesName() {
|
||||
return preferencesName;
|
||||
}
|
||||
|
||||
public String getPreferenceEnabledName() {
|
||||
return preferenceEnabledName;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package fi.vanced.libraries.youtube.whitelisting.requests;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
|
||||
import static fi.vanced.libraries.youtube.ui.AdButton.TAG;
|
||||
import static fi.vanced.utils.VancedUtils.runOnMainThread;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.ChannelModel;
|
||||
import fi.vanced.libraries.youtube.whitelisting.Whitelist;
|
||||
import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
import fi.vanced.utils.requests.Requester;
|
||||
import fi.vanced.utils.requests.Route;
|
||||
import vanced.integrations.BuildConfig;
|
||||
|
||||
public class WhitelistRequester {
|
||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";
|
||||
|
||||
private WhitelistRequester() {}
|
||||
|
||||
public static void addChannelToWhitelist(WhitelistType whitelistType, View view, ImageView buttonIcon, Context context) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(WhitelistRoutes.GET_CHANNEL_DETAILS, BuildConfig.YT_API_KEY);
|
||||
connection.setRequestProperty("Content-Type", "application/json; utf-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(2 * 1000);
|
||||
|
||||
String versionName = VancedUtils.getVersionName(context);
|
||||
String jsonInputString = "{\"context\": {\"client\": { \"clientName\": \"Android\", \"clientVersion\": \"" + versionName + "\" } }, \"videoId\": \"" + currentVideoId + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
JSONObject json = getJSONObject(connection);
|
||||
JSONObject videoInfo = json.getJSONObject("videoDetails");
|
||||
ChannelModel channelModel = new ChannelModel(videoInfo.getString("author"), videoInfo.getString("channelId"));
|
||||
String author = channelModel.getAuthor();
|
||||
if (debug) {
|
||||
Log.d(TAG, "channelId " + channelModel.getChannelId() + " fetched for author " + author);
|
||||
}
|
||||
|
||||
boolean success = Whitelist.addToWhitelist(whitelistType, context, channelModel);
|
||||
String whitelistTypeName = whitelistType.getFriendlyName();
|
||||
runOnMainThread(() -> {
|
||||
if (success) {
|
||||
buttonIcon.setEnabled(whitelistType != WhitelistType.SPONSORBLOCK);
|
||||
Toast.makeText(context, str("vanced_whitelisting_added", author, whitelistTypeName), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
else {
|
||||
buttonIcon.setEnabled(whitelistType == WhitelistType.SPONSORBLOCK);
|
||||
Toast.makeText(context, str("vanced_whitelisting_add_failed", author, whitelistTypeName), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
view.setEnabled(true);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (debug) {
|
||||
Log.d(TAG, "player fetch response was " + responseCode);
|
||||
}
|
||||
runOnMainThread(() -> {
|
||||
Toast.makeText(context, str("vanced_whitelisting_fetch_failed", responseCode), Toast.LENGTH_SHORT).show();
|
||||
buttonIcon.setEnabled(true);
|
||||
view.setEnabled(true);
|
||||
});
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to fetch channelId", ex);
|
||||
runOnMainThread(() -> view.setEnabled(true));
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(YT_API_URL, route, params);
|
||||
}
|
||||
|
||||
private static JSONObject getJSONObject(HttpURLConnection connection) throws Exception {
|
||||
return Requester.getJSONObject(connection);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package fi.vanced.libraries.youtube.whitelisting.requests;
|
||||
|
||||
import static fi.vanced.utils.requests.Route.Method.POST;
|
||||
|
||||
import fi.vanced.utils.requests.Route;
|
||||
|
||||
public class WhitelistRoutes {
|
||||
public static final Route GET_CHANNEL_DETAILS = new Route(POST, "player?key={api_key}");
|
||||
|
||||
private WhitelistRoutes() {}
|
||||
}
|
@ -2,35 +2,24 @@ package fi.vanced.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class VancedUtils {
|
||||
|
||||
private VancedUtils() {}
|
||||
|
||||
public static SharedPreferences getPreferences(Context context, String preferencesName) {
|
||||
if (context == null) return null;
|
||||
return context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static String parseJson(HttpURLConnection connection) throws IOException {
|
||||
StringBuilder jsonBuilder = new StringBuilder();
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
jsonBuilder.append(line);
|
||||
}
|
||||
inputStream.close();
|
||||
return jsonBuilder.toString();
|
||||
}
|
||||
|
||||
public static int getIdentifier(String name, String defType) {
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
return context.getResources().getIdentifier(name, defType, context.getPackageName());
|
||||
@ -46,4 +35,29 @@ public class VancedUtils {
|
||||
sb.append(AB.charAt(rnd.nextInt(AB.length())));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static int countMatches(CharSequence seq, char c) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < seq.length(); i++) {
|
||||
if (seq.charAt(i) == c)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static String getVersionName(Context context) {
|
||||
try {
|
||||
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
String version = pInfo.versionName;
|
||||
return (version);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return ("17.03.35");
|
||||
}
|
||||
|
||||
public static void runOnMainThread(Runnable runnable) {
|
||||
new Handler(Looper.getMainLooper()).post(runnable);
|
||||
}
|
||||
}
|
51
app/src/main/java/fi/vanced/utils/requests/Requester.java
Normal file
51
app/src/main/java/fi/vanced/utils/requests/Requester.java
Normal file
@ -0,0 +1,51 @@
|
||||
package fi.vanced.utils.requests;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class Requester {
|
||||
private Requester() {}
|
||||
|
||||
public static HttpURLConnection getConnectionFromRoute(String apiUrl, Route route, String... params) throws IOException {
|
||||
String url = apiUrl + route.compile(params).getCompiledRoute();
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setRequestMethod(route.getMethod().name());
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static String parseJson(HttpURLConnection connection) throws IOException {
|
||||
return parseJson(connection.getInputStream());
|
||||
}
|
||||
|
||||
public static String parseJson(InputStream inputStream) throws IOException {
|
||||
StringBuilder jsonBuilder = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
jsonBuilder.append(line).append("\n");
|
||||
}
|
||||
inputStream.close();
|
||||
return jsonBuilder.toString();
|
||||
}
|
||||
|
||||
public static JSONObject getJSONObject(HttpURLConnection connection) throws Exception {
|
||||
return new JSONObject(parseJsonAndDisconnect(connection));
|
||||
}
|
||||
|
||||
public static JSONArray getJSONArray(HttpURLConnection connection) throws Exception {
|
||||
return new JSONArray(parseJsonAndDisconnect(connection));
|
||||
}
|
||||
|
||||
private static String parseJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String json = parseJson(connection);
|
||||
connection.disconnect();
|
||||
return json;
|
||||
}
|
||||
}
|
59
app/src/main/java/fi/vanced/utils/requests/Route.java
Normal file
59
app/src/main/java/fi/vanced/utils/requests/Route.java
Normal file
@ -0,0 +1,59 @@
|
||||
package fi.vanced.utils.requests;
|
||||
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class Route {
|
||||
private final String route;
|
||||
private final Route.Method method;
|
||||
private final int paramCount;
|
||||
|
||||
public Route(Route.Method method, String route) {
|
||||
this.method = method;
|
||||
this.route = route;
|
||||
this.paramCount = VancedUtils.countMatches(route, '{');
|
||||
|
||||
if (paramCount != VancedUtils.countMatches(route, '}'))
|
||||
throw new IllegalArgumentException("Not enough parameters");
|
||||
}
|
||||
|
||||
public Route.Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Route.CompiledRoute compile(String... params) {
|
||||
if (params.length != paramCount)
|
||||
throw new IllegalArgumentException("Error compiling route [" + route + "], incorrect amount of parameters provided. " +
|
||||
"Expected: " + paramCount + ", provided: " + params.length);
|
||||
|
||||
StringBuilder compiledRoute = new StringBuilder(route);
|
||||
for (int i = 0; i < paramCount; i++) {
|
||||
int paramStart = compiledRoute.indexOf("{");
|
||||
int paramEnd = compiledRoute.indexOf("}");
|
||||
compiledRoute.replace(paramStart, paramEnd + 1, params[i]);
|
||||
}
|
||||
return new Route.CompiledRoute(this, compiledRoute.toString());
|
||||
}
|
||||
|
||||
public static class CompiledRoute {
|
||||
private final Route baseRoute;
|
||||
private final String compiledRoute;
|
||||
|
||||
private CompiledRoute(Route baseRoute, String compiledRoute) {
|
||||
this.baseRoute = baseRoute;
|
||||
this.compiledRoute = compiledRoute;
|
||||
}
|
||||
|
||||
public String getCompiledRoute() {
|
||||
return compiledRoute;
|
||||
}
|
||||
|
||||
public Route.Method getMethod() {
|
||||
return baseRoute.method;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Method {
|
||||
GET,
|
||||
POST
|
||||
}
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
package pl.jakubweg;
|
||||
|
||||
import static pl.jakubweg.SponsorBlockSettings.skippedSegments;
|
||||
import static pl.jakubweg.SponsorBlockSettings.skippedTime;
|
||||
import static pl.jakubweg.SponsorBlockUtils.timeWithoutSegments;
|
||||
import static pl.jakubweg.SponsorBlockUtils.videoHasSegments;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
@ -22,11 +27,9 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.libraries.youtube.whitelisting.Whitelist;
|
||||
import pl.jakubweg.objects.SponsorSegment;
|
||||
import pl.jakubweg.requests.Requester;
|
||||
|
||||
import static pl.jakubweg.SponsorBlockSettings.skippedSegments;
|
||||
import static pl.jakubweg.SponsorBlockSettings.skippedTime;
|
||||
import pl.jakubweg.requests.SBRequester;
|
||||
|
||||
@SuppressLint({"LongLogTag"})
|
||||
public class PlayerController {
|
||||
@ -123,7 +126,11 @@ public class PlayerController {
|
||||
}
|
||||
|
||||
public static void executeDownloadSegments(String videoId) {
|
||||
SponsorSegment[] segments = Requester.getSegments(videoId);
|
||||
videoHasSegments = false;
|
||||
timeWithoutSegments = "";
|
||||
if (Whitelist.isChannelSBWhitelisted())
|
||||
return;
|
||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||
Arrays.sort(segments);
|
||||
|
||||
if (VERBOSE)
|
||||
@ -281,7 +288,7 @@ public class PlayerController {
|
||||
segment.category != SponsorBlockSettings.SegmentInfo.UNSUBMITTED &&
|
||||
millis - segment.start < 2000) {
|
||||
// Only skips from the start should count as a view
|
||||
Requester.sendViewCountRequest(segment);
|
||||
SBRequester.sendViewCountRequest(segment);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
@ -1,17 +1,20 @@
|
||||
package pl.jakubweg;
|
||||
|
||||
import static pl.jakubweg.SponsorBlockSettings.DefaultBehaviour;
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_COUNT_SKIPS;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_MIN_DURATION;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_NEW_SEGMENT_ENABLED;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_UUID;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_VOTING_ENABLED;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_NAME;
|
||||
import static pl.jakubweg.SponsorBlockSettings.adjustNewSegmentMillis;
|
||||
import static pl.jakubweg.SponsorBlockSettings.countSkips;
|
||||
import static pl.jakubweg.SponsorBlockSettings.minDuration;
|
||||
import static pl.jakubweg.SponsorBlockSettings.setSeenGuidelines;
|
||||
import static pl.jakubweg.SponsorBlockSettings.showTimeWithoutSegments;
|
||||
import static pl.jakubweg.SponsorBlockSettings.showToastWhenSkippedAutomatically;
|
||||
@ -26,7 +29,6 @@ import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
@ -38,7 +40,10 @@ import android.widget.Toast;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import pl.jakubweg.requests.Requester;
|
||||
import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import pl.jakubweg.objects.EditTextListPreference;
|
||||
import pl.jakubweg.requests.SBRequester;
|
||||
|
||||
@SuppressWarnings({"unused", "deprecation"}) // injected
|
||||
public class SponsorBlockPreferenceFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@ -75,6 +80,18 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
});
|
||||
}
|
||||
|
||||
// Clear hint
|
||||
if (debug) {
|
||||
SwitchPreference preference = new SwitchPreference(context);
|
||||
preferenceScreen.addPreference(preference);
|
||||
preference.setKey(PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN);
|
||||
preference.setDefaultValue(false);
|
||||
preference.setChecked(SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN));
|
||||
preference.setTitle("Hint debug");
|
||||
preference.setSummary("Debug toggle for clearing the hint shown preference");
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> true);
|
||||
}
|
||||
|
||||
{
|
||||
SwitchPreference preference = new SwitchPreference(context);
|
||||
preferenceScreen.addPreference(preference);
|
||||
@ -143,7 +160,6 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
preferencesToDisableWhenSBDisabled.add(category);
|
||||
category.setTitle(str("diff_segments"));
|
||||
|
||||
String defaultValue = DefaultBehaviour.key;
|
||||
SponsorBlockSettings.SegmentBehaviour[] segmentBehaviours = SponsorBlockSettings.SegmentBehaviour.values();
|
||||
String[] entries = new String[segmentBehaviours.length];
|
||||
String[] entryValues = new String[segmentBehaviours.length];
|
||||
@ -156,33 +172,21 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
SponsorBlockSettings.SegmentInfo[] categories = SponsorBlockSettings.SegmentInfo.valuesWithoutUnsubmitted();
|
||||
|
||||
for (SponsorBlockSettings.SegmentInfo segmentInfo : categories) {
|
||||
ListPreference preference = new ListPreference(context);
|
||||
EditTextListPreference preference = new EditTextListPreference(context);
|
||||
preference.setTitle(segmentInfo.getTitleWithDot());
|
||||
preference.setSummary(segmentInfo.description.toString());
|
||||
preference.setKey(segmentInfo.key);
|
||||
preference.setDefaultValue(defaultValue);
|
||||
preference.setDefaultValue(segmentInfo.behaviour.key);
|
||||
preference.setEntries(entries);
|
||||
preference.setEntryValues(entryValues);
|
||||
|
||||
category.addPreference(preference);
|
||||
}
|
||||
|
||||
Preference colorPreference = new Preference(context);
|
||||
Preference colorPreference = new Preference(context); // TODO remove this after the next major update
|
||||
screen.addPreference(colorPreference);
|
||||
colorPreference.setTitle(str("color_change"));
|
||||
|
||||
colorPreference.setOnPreferenceClickListener(preference1 -> {
|
||||
CharSequence[] items = new CharSequence[categories.length];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
items[i] = categories[i].getTitleWithDot();
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(str("color_choose_category"))
|
||||
.setItems(items, SponsorBlockUtils.categoryColorChangeClickListener)
|
||||
.show();
|
||||
return true;
|
||||
});
|
||||
colorPreference.setSummary(str("color_change_sum"));
|
||||
preferencesToDisableWhenSBDisabled.add(colorPreference);
|
||||
}
|
||||
|
||||
@ -197,7 +201,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
category.addPreference(preference);
|
||||
preference.setTitle(str("stats_loading"));
|
||||
|
||||
Requester.retrieveUserStats(category, preference);
|
||||
SBRequester.retrieveUserStats(category, preference);
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,9 +282,18 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
screen.addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
Preference preference = new SwitchPreference(context);
|
||||
preference.setTitle(str("general_whitelisting"));
|
||||
preference.setSummary(str("general_whitelisting_sum"));
|
||||
preference.setKey(WhitelistType.SPONSORBLOCK.getPreferenceEnabledName());
|
||||
preferencesToDisableWhenSBDisabled.add(preference);
|
||||
screen.addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
EditTextPreference preference = new EditTextPreference(context);
|
||||
preference.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
|
||||
preference.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
preference.setTitle(str("general_adjusting"));
|
||||
preference.setSummary(str("general_adjusting_sum"));
|
||||
preference.setKey(PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP);
|
||||
@ -289,6 +302,17 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
preferencesToDisableWhenSBDisabled.add(preference);
|
||||
}
|
||||
|
||||
{
|
||||
EditTextPreference preference = new EditTextPreference(context);
|
||||
preference.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
preference.setTitle(str("general_min_duration"));
|
||||
preference.setSummary(str("general_min_duration_sum"));
|
||||
preference.setKey(PREFERENCES_KEY_MIN_DURATION);
|
||||
preference.setDefaultValue(String.valueOf(minDuration));
|
||||
screen.addPreference(preference);
|
||||
preferencesToDisableWhenSBDisabled.add(preference);
|
||||
}
|
||||
|
||||
{
|
||||
Preference preference = new EditTextPreference(context);
|
||||
preference.setTitle(str("general_uuid"));
|
||||
|
@ -22,7 +22,9 @@ public class SponsorBlockSettings {
|
||||
public static final String PREFERENCES_KEY_COUNT_SKIPS = "count-skips";
|
||||
public static final String PREFERENCES_KEY_UUID = "uuid";
|
||||
public static final String PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP = "new-segment-step-accuracy";
|
||||
public static final String PREFERENCES_KEY_MIN_DURATION = "sb-min-duration";
|
||||
public static final String PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED = "sb-enabled";
|
||||
public static final String PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN = "sb_hint_shown";
|
||||
public static final String PREFERENCES_KEY_SEEN_GUIDELINES = "sb-seen-gl";
|
||||
public static final String PREFERENCES_KEY_NEW_SEGMENT_ENABLED = "sb-new-segment-enabled";
|
||||
public static final String PREFERENCES_KEY_VOTING_ENABLED = "sb-voting-enabled";
|
||||
@ -41,6 +43,7 @@ public class SponsorBlockSettings {
|
||||
public static boolean countSkips = true;
|
||||
public static boolean showTimeWithoutSegments = true;
|
||||
public static int adjustNewSegmentMillis = 150;
|
||||
public static float minDuration = 0f;
|
||||
public static String uuid = "<invalid>";
|
||||
public static String sponsorBlockUrlCategories = "[]";
|
||||
public static int skippedSegments;
|
||||
@ -100,9 +103,7 @@ public class SponsorBlockSettings {
|
||||
|
||||
SegmentBehaviour behaviour = null;
|
||||
String value = preferences.getString(segment.key, null);
|
||||
if (value == null)
|
||||
behaviour = DefaultBehaviour;
|
||||
else {
|
||||
if (value != null) {
|
||||
for (SegmentBehaviour possibleBehaviour : possibleBehaviours) {
|
||||
if (possibleBehaviour.key.equals(value)) {
|
||||
behaviour = possibleBehaviour;
|
||||
@ -110,10 +111,13 @@ public class SponsorBlockSettings {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (behaviour == null)
|
||||
behaviour = DefaultBehaviour;
|
||||
|
||||
if (behaviour != null) {
|
||||
segment.behaviour = behaviour;
|
||||
}
|
||||
else {
|
||||
behaviour = segment.behaviour;
|
||||
}
|
||||
|
||||
if (behaviour.showOnTimeBar && segment != SegmentInfo.UNSUBMITTED)
|
||||
enabledCategories.add(segment.key);
|
||||
}
|
||||
@ -132,6 +136,10 @@ public class SponsorBlockSettings {
|
||||
if (tmp1 != null)
|
||||
adjustNewSegmentMillis = Integer.parseInt(tmp1);
|
||||
|
||||
String minTmp = preferences.getString(PREFERENCES_KEY_MIN_DURATION, null);
|
||||
if (minTmp != null)
|
||||
minDuration = Float.parseFloat(minTmp);
|
||||
|
||||
countSkips = preferences.getBoolean(PREFERENCES_KEY_COUNT_SKIPS, countSkips);
|
||||
showTimeWithoutSegments = preferences.getBoolean(PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS, showTimeWithoutSegments);
|
||||
|
||||
@ -179,13 +187,14 @@ public class SponsorBlockSettings {
|
||||
}
|
||||
|
||||
public enum SegmentInfo {
|
||||
SPONSOR("sponsor", sf("segments_sponsor"), sf("skipped_sponsor"), sf("segments_sponsor_sum"), null, 0xFF00d400),
|
||||
INTRO("intro", sf("segments_intermission"), sf("skipped_intermission"), sf("segments_intermission_sum"), null, 0xFF00ffff),
|
||||
OUTRO("outro", sf("segments_endcards"), sf("skipped_endcard"), sf("segments_endcards_sum"), null, 0xFF0202ed),
|
||||
INTERACTION("interaction", sf("segments_subscribe"), sf("skipped_subscribe"), sf("segments_subscribe_sum"), null, 0xFFcc00ff),
|
||||
SELF_PROMO("selfpromo", sf("segments_selfpromo"), sf("skipped_selfpromo"), sf("segments_selfpromo_sum"), null, 0xFFffff00),
|
||||
MUSIC_OFFTOPIC("music_offtopic", sf("segments_nomusic"), sf("skipped_nomusic"), sf("segments_nomusic_sum"), null, 0xFFff9900),
|
||||
PREVIEW("preview", sf("segments_preview"), sf("skipped_preview"), sf("segments_preview_sum"), null, 0xFF008fd6),
|
||||
SPONSOR("sponsor", sf("segments_sponsor"), sf("skipped_sponsor"), sf("segments_sponsor_sum"), DefaultBehaviour, 0xFF00d400),
|
||||
INTRO("intro", sf("segments_intermission"), sf("skipped_intermission"), sf("segments_intermission_sum"), DefaultBehaviour, 0xFF00ffff),
|
||||
OUTRO("outro", sf("segments_endcards"), sf("skipped_endcard"), sf("segments_endcards_sum"), DefaultBehaviour, 0xFF0202ed),
|
||||
INTERACTION("interaction", sf("segments_subscribe"), sf("skipped_subscribe"), sf("segments_subscribe_sum"), DefaultBehaviour, 0xFFcc00ff),
|
||||
SELF_PROMO("selfpromo", sf("segments_selfpromo"), sf("skipped_selfpromo"), sf("segments_selfpromo_sum"), DefaultBehaviour, 0xFFffff00),
|
||||
MUSIC_OFFTOPIC("music_offtopic", sf("segments_nomusic"), sf("skipped_nomusic"), sf("segments_nomusic_sum"), DefaultBehaviour, 0xFFff9900),
|
||||
PREVIEW("preview", sf("segments_preview"), sf("skipped_preview"), sf("segments_preview_sum"), DefaultBehaviour, 0xFF008fd6),
|
||||
FILLER("filler", sf("segments_filler"), sf("skipped_filler"), sf("segments_filler_sum"), SegmentBehaviour.IGNORE, 0xFF7300FF),
|
||||
UNSUBMITTED("unsubmitted", StringRef.empty, sf("skipped_unsubmitted"), StringRef.empty, SegmentBehaviour.SKIP_AUTOMATICALLY, 0xFFFFFFFF);
|
||||
|
||||
private static final SegmentInfo[] mValuesWithoutUnsubmitted = new SegmentInfo[]{
|
||||
@ -195,7 +204,8 @@ public class SponsorBlockSettings {
|
||||
INTERACTION,
|
||||
SELF_PROMO,
|
||||
MUSIC_OFFTOPIC,
|
||||
PREVIEW
|
||||
PREVIEW,
|
||||
FILLER
|
||||
};
|
||||
private static final Map<String, SegmentInfo> mValuesMap = new HashMap<>(values().length);
|
||||
|
||||
|
@ -12,19 +12,21 @@ import static pl.jakubweg.SponsorBlockPreferenceFragment.FORMATTER;
|
||||
import static pl.jakubweg.SponsorBlockPreferenceFragment.SAVED_TEMPLATE;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_COUNT_SKIPS;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_MIN_DURATION;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_UUID;
|
||||
import static pl.jakubweg.SponsorBlockSettings.countSkips;
|
||||
import static pl.jakubweg.SponsorBlockSettings.getPreferences;
|
||||
import static pl.jakubweg.SponsorBlockSettings.isSponsorBlockEnabled;
|
||||
import static pl.jakubweg.SponsorBlockSettings.minDuration;
|
||||
import static pl.jakubweg.SponsorBlockSettings.showTimeWithoutSegments;
|
||||
import static pl.jakubweg.SponsorBlockSettings.showToastWhenSkippedAutomatically;
|
||||
import static pl.jakubweg.SponsorBlockSettings.skippedSegments;
|
||||
import static pl.jakubweg.SponsorBlockSettings.skippedTime;
|
||||
import static pl.jakubweg.SponsorBlockSettings.uuid;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
import static pl.jakubweg.requests.Requester.voteForSegment;
|
||||
import static pl.jakubweg.requests.SBRequester.voteForSegment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
@ -32,13 +34,11 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.text.Html;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
@ -61,7 +61,7 @@ import java.util.TimeZone;
|
||||
|
||||
import pl.jakubweg.objects.SponsorSegment;
|
||||
import pl.jakubweg.objects.UserStats;
|
||||
import pl.jakubweg.requests.Requester;
|
||||
import pl.jakubweg.requests.SBRequester;
|
||||
|
||||
@SuppressWarnings({"LongLogTag"})
|
||||
public abstract class SponsorBlockUtils {
|
||||
@ -223,10 +223,10 @@ public abstract class SponsorBlockUtils {
|
||||
appContext = new WeakReference<>(context.getApplicationContext());
|
||||
switch (voteOptions[which1]) {
|
||||
case UPVOTE:
|
||||
voteForSegment(segment, VoteOption.UPVOTE, appContext.get(), toastRunnable);
|
||||
voteForSegment(segment, VoteOption.UPVOTE, appContext.get());
|
||||
break;
|
||||
case DOWNVOTE:
|
||||
voteForSegment(segment, VoteOption.DOWNVOTE, appContext.get(), toastRunnable);
|
||||
voteForSegment(segment, VoteOption.DOWNVOTE, appContext.get());
|
||||
break;
|
||||
case CATEGORY_CHANGE:
|
||||
onNewCategorySelect(segment, context);
|
||||
@ -235,40 +235,6 @@ public abstract class SponsorBlockUtils {
|
||||
})
|
||||
.show();
|
||||
};
|
||||
public static final DialogInterface.OnClickListener categoryColorChangeClickListener = (dialog, which) -> {
|
||||
SponsorBlockSettings.SegmentInfo segmentInfo = SponsorBlockSettings.SegmentInfo.valuesWithoutUnsubmitted()[which];
|
||||
String key = segmentInfo.key + PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX;
|
||||
|
||||
Context context = ((AlertDialog) dialog).getContext();
|
||||
EditText editText = new EditText(context);
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
editText.setText(formatColorString(segmentInfo.color));
|
||||
|
||||
Context applicationContext = context.getApplicationContext();
|
||||
SharedPreferences preferences = SponsorBlockSettings.getPreferences(context);
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setView(editText)
|
||||
.setPositiveButton(str("change"), (dialog1, which1) -> {
|
||||
try {
|
||||
int color = Color.parseColor(editText.getText().toString());
|
||||
segmentInfo.setColor(color);
|
||||
Toast.makeText(applicationContext, str("color_changed"), Toast.LENGTH_SHORT).show();
|
||||
preferences.edit().putString(key, formatColorString(color)).apply();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Toast.makeText(applicationContext, str("color_invalid"), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setNeutralButton(str("reset"), (dialog1, which1) -> {
|
||||
int defaultColor = segmentInfo.defaultColor;
|
||||
segmentInfo.setColor(defaultColor);
|
||||
Toast.makeText(applicationContext, str("color_reset"), Toast.LENGTH_SHORT).show();
|
||||
preferences.edit().putString(key, formatColorString(defaultColor)).apply();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
};
|
||||
private static final Runnable submitRunnable = () -> {
|
||||
messageToToast = null;
|
||||
final String uuid = SponsorBlockSettings.uuid;
|
||||
@ -281,7 +247,7 @@ public abstract class SponsorBlockUtils {
|
||||
Log.e(TAG, "Unable to submit times, invalid parameters");
|
||||
return;
|
||||
}
|
||||
Requester.submitSegments(videoId, uuid, ((float) start) / 1000f, ((float) end) / 1000f, segmentType.key, toastRunnable);
|
||||
SBRequester.submitSegments(videoId, uuid, ((float) start) / 1000f, ((float) end) / 1000f, segmentType.key, toastRunnable);
|
||||
newSponsorSegmentEndMillis = newSponsorSegmentStartMillis = -1;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to submit segment", e);
|
||||
@ -401,7 +367,7 @@ public abstract class SponsorBlockUtils {
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(str("new_segment_choose_category"))
|
||||
.setItems(titles, (dialog, which) -> voteForSegment(segment, VoteOption.CATEGORY_CHANGE, appContext.get(), toastRunnable, values[which].key))
|
||||
.setItems(titles, (dialog, which) -> voteForSegment(segment, VoteOption.CATEGORY_CHANGE, appContext.get(), values[which].key))
|
||||
.show();
|
||||
}
|
||||
|
||||
@ -485,15 +451,6 @@ public abstract class SponsorBlockUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static int countMatches(CharSequence seq, char c) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < seq.length(); i++) {
|
||||
if (seq.charAt(i) == c)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static String formatColorString(int color) {
|
||||
return String.format("#%06X", color);
|
||||
}
|
||||
@ -514,7 +471,7 @@ public abstract class SponsorBlockUtils {
|
||||
preference.setText(userName);
|
||||
preference.setOnPreferenceChangeListener((preference1, newUsername) -> {
|
||||
appContext = new WeakReference<>(context.getApplicationContext());
|
||||
Requester.setUsername((String) newUsername, toastRunnable);
|
||||
SBRequester.setUsername((String) newUsername, preference, toastRunnable);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@ -597,6 +554,7 @@ public abstract class SponsorBlockUtils {
|
||||
editor.putBoolean(PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP, !settingsJson.getBoolean("dontShowNotice"));
|
||||
editor.putBoolean(PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS, settingsJson.getBoolean("showTimeWithSkips"));
|
||||
editor.putBoolean(PREFERENCES_KEY_COUNT_SKIPS, settingsJson.getBoolean("trackViewCount"));
|
||||
editor.putString(PREFERENCES_KEY_MIN_DURATION, settingsJson.getString("minDuration"));
|
||||
editor.putString(PREFERENCES_KEY_UUID, settingsJson.getString("userID"));
|
||||
editor.apply();
|
||||
|
||||
@ -633,6 +591,7 @@ public abstract class SponsorBlockUtils {
|
||||
json.put("dontShowNotice", !showToastWhenSkippedAutomatically);
|
||||
json.put("barTypes", barTypesObject);
|
||||
json.put("showTimeWithSkips", showTimeWithoutSegments);
|
||||
json.put("minDuration", minDuration);
|
||||
json.put("trackViewCount", countSkips);
|
||||
json.put("categorySelections", categorySelectionsArray);
|
||||
json.put("userID", uuid);
|
||||
|
@ -0,0 +1,97 @@
|
||||
package pl.jakubweg.objects;
|
||||
|
||||
import static pl.jakubweg.SponsorBlockUtils.formatColorString;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Color;
|
||||
import android.preference.ListPreference;
|
||||
import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import pl.jakubweg.SponsorBlockSettings;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class EditTextListPreference extends ListPreference {
|
||||
|
||||
private EditText mEditText;
|
||||
private int mClickedDialogEntryIndex;
|
||||
|
||||
public EditTextListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public EditTextListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public EditTextListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public EditTextListPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
SponsorBlockSettings.SegmentInfo category = getCategoryBySelf();
|
||||
|
||||
mEditText = new EditText(builder.getContext());
|
||||
mEditText.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
mEditText.setText(formatColorString(category.color));
|
||||
builder.setView(mEditText);
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
EditTextListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
});
|
||||
builder.setNeutralButton(str("reset"), (dialog, which) -> {
|
||||
//EditTextListPreference.this.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
|
||||
int defaultColor = category.defaultColor;
|
||||
category.setColor(defaultColor);
|
||||
Toast.makeText(getContext().getApplicationContext(), str("color_reset"), Toast.LENGTH_SHORT).show();
|
||||
getSharedPreferences().edit().putString(getColorPreferenceKey(), formatColorString(defaultColor)).apply();
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
mClickedDialogEntryIndex = findIndexOfValue(getValue());
|
||||
builder.setSingleChoiceItems(getEntries(), mClickedDialogEntryIndex, (dialog, which) -> mClickedDialogEntryIndex = which);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) {
|
||||
String value = getEntryValues()[mClickedDialogEntryIndex].toString();
|
||||
if (callChangeListener(value)) {
|
||||
setValue(value);
|
||||
}
|
||||
String colorString = mEditText.getText().toString();
|
||||
SponsorBlockSettings.SegmentInfo category = getCategoryBySelf();
|
||||
if (colorString.equals(formatColorString(category.color))) {
|
||||
return;
|
||||
}
|
||||
Context applicationContext = getContext().getApplicationContext();
|
||||
try {
|
||||
int color = Color.parseColor(colorString);
|
||||
category.setColor(color);
|
||||
Toast.makeText(applicationContext, str("color_changed"), Toast.LENGTH_SHORT).show();
|
||||
getSharedPreferences().edit().putString(getColorPreferenceKey(), formatColorString(color)).apply();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Toast.makeText(applicationContext, str("color_invalid"), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SponsorBlockSettings.SegmentInfo getCategoryBySelf() {
|
||||
return SponsorBlockSettings.SegmentInfo.byCategoryKey(getKey());
|
||||
}
|
||||
|
||||
private String getColorPreferenceKey() {
|
||||
return getKey() + SponsorBlockSettings.PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX;
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package pl.jakubweg.requests;
|
||||
|
||||
import static pl.jakubweg.requests.Route.Method.GET;
|
||||
import static pl.jakubweg.requests.Route.Method.POST;
|
||||
|
||||
import pl.jakubweg.SponsorBlockUtils;
|
||||
|
||||
public class Route {
|
||||
public static final Route GET_SEGMENTS = new Route(GET, "skipSegments?videoID={video_id}&categories={categories}");
|
||||
public static final Route VIEWED_SEGMENT = new Route(POST, "viewedVideoSponsorTime?UUID={segment_id}");
|
||||
public static final Route GET_USER_STATS = new Route(GET, "userInfo?userID={user_id}&values=[\"userName\", \"minutesSaved\", \"segmentCount\", \"viewCount\"]");
|
||||
public static final Route CHANGE_USERNAME = new Route(POST, "setUsername?userID={user_id}&username={username}");
|
||||
public static final Route SUBMIT_SEGMENTS = new Route(POST, "skipSegments?videoID={video_id}&userID={user_id}&startTime={start_time}&endTime={end_time}&category={category}");
|
||||
public static final Route VOTE_ON_SEGMENT_QUALITY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&type={type}");
|
||||
public static final Route VOTE_ON_SEGMENT_CATEGORY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&category={category}");
|
||||
|
||||
private final String route;
|
||||
private final Method method;
|
||||
private final int paramCount;
|
||||
|
||||
private Route(Method method, String route) {
|
||||
this.method = method;
|
||||
this.route = route;
|
||||
this.paramCount = SponsorBlockUtils.countMatches(route, '{');
|
||||
|
||||
if (paramCount != SponsorBlockUtils.countMatches(route, '}'))
|
||||
throw new IllegalArgumentException("Not enough parameters");
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public CompiledRoute compile(String... params) {
|
||||
if (params.length != paramCount)
|
||||
throw new IllegalArgumentException("Error compiling route [" + route + "], incorrect amount of parameters provided. " +
|
||||
"Expected: " + paramCount + ", provided: " + params.length);
|
||||
|
||||
StringBuilder compiledRoute = new StringBuilder(route);
|
||||
for (int i = 0; i < paramCount; i++) {
|
||||
int paramStart = compiledRoute.indexOf("{");
|
||||
int paramEnd = compiledRoute.indexOf("}");
|
||||
compiledRoute.replace(paramStart, paramEnd + 1, params[i]);
|
||||
}
|
||||
return new CompiledRoute(this, compiledRoute.toString());
|
||||
}
|
||||
|
||||
public static class CompiledRoute {
|
||||
private final Route baseRoute;
|
||||
private final String compiledRoute;
|
||||
|
||||
private CompiledRoute(Route baseRoute, String compiledRoute) {
|
||||
this.baseRoute = baseRoute;
|
||||
this.compiledRoute = compiledRoute;
|
||||
}
|
||||
|
||||
public String getCompiledRoute() {
|
||||
return compiledRoute;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return baseRoute.method;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Method {
|
||||
GET,
|
||||
POST
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
package pl.jakubweg.requests;
|
||||
|
||||
import static android.text.Html.fromHtml;
|
||||
import static fi.vanced.utils.VancedUtils.runOnMainThread;
|
||||
import static pl.jakubweg.SponsorBlockUtils.timeWithoutSegments;
|
||||
import static pl.jakubweg.SponsorBlockUtils.videoHasSegments;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.widget.Toast;
|
||||
@ -14,44 +15,45 @@ import android.widget.Toast;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import fi.vanced.utils.requests.Requester;
|
||||
import fi.vanced.utils.requests.Route;
|
||||
import pl.jakubweg.SponsorBlockSettings;
|
||||
import pl.jakubweg.SponsorBlockUtils;
|
||||
import pl.jakubweg.SponsorBlockUtils.VoteOption;
|
||||
import pl.jakubweg.objects.SponsorSegment;
|
||||
import pl.jakubweg.objects.UserStats;
|
||||
|
||||
public class Requester {
|
||||
public class SBRequester {
|
||||
private static final String SPONSORBLOCK_API_URL = "https://sponsor.ajay.app/api/";
|
||||
private static final String TIME_TEMPLATE = "%.3f";
|
||||
|
||||
private Requester() {}
|
||||
private SBRequester() {}
|
||||
|
||||
public static synchronized SponsorSegment[] getSegments(String videoId) {
|
||||
List<SponsorSegment> segments = new ArrayList<>();
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories);
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories);
|
||||
int responseCode = connection.getResponseCode();
|
||||
videoHasSegments = false;
|
||||
timeWithoutSegments = "";
|
||||
|
||||
if (responseCode == 200) {
|
||||
JSONArray responseArray = new JSONArray(parseJson(connection));
|
||||
JSONArray responseArray = Requester.getJSONArray(connection);
|
||||
int length = responseArray.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
JSONObject obj = ((JSONObject) responseArray.get(i));
|
||||
JSONObject obj = (JSONObject) responseArray.get(i);
|
||||
JSONArray segment = obj.getJSONArray("segment");
|
||||
long start = (long) (segment.getDouble(0) * 1000);
|
||||
long end = (long) (segment.getDouble(1) * 1000);
|
||||
|
||||
long minDuration = (long) (SponsorBlockSettings.minDuration * 1000);
|
||||
if ((end - start) < minDuration)
|
||||
continue;
|
||||
|
||||
String category = obj.getString("category");
|
||||
String uuid = obj.getString("UUID");
|
||||
|
||||
@ -61,9 +63,11 @@ public class Requester {
|
||||
segments.add(sponsorSegment);
|
||||
}
|
||||
}
|
||||
if (!segments.isEmpty()) {
|
||||
videoHasSegments = true;
|
||||
timeWithoutSegments = SponsorBlockUtils.getTimeWithoutSegments(segments.toArray(new SponsorSegment[0]));
|
||||
}
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@ -76,7 +80,7 @@ public class Requester {
|
||||
try {
|
||||
String start = String.format(Locale.US, TIME_TEMPLATE, startTime);
|
||||
String end = String.format(Locale.US, TIME_TEMPLATE, endTime);
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.SUBMIT_SEGMENTS, videoId, uuid, start, end, category);
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.SUBMIT_SEGMENTS, videoId, uuid, start, end, category);
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
switch (responseCode) {
|
||||
@ -87,7 +91,7 @@ public class Requester {
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_duplicate");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_forbidden");
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_forbidden", Requester.parseJson(connection.getErrorStream()));
|
||||
break;
|
||||
case 429:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_rate_limit");
|
||||
@ -96,7 +100,7 @@ public class Requester {
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
break;
|
||||
}
|
||||
new Handler(Looper.getMainLooper()).post(toastRunnable);
|
||||
runOnMainThread(toastRunnable);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@ -106,7 +110,7 @@ public class Requester {
|
||||
|
||||
public static void sendViewCountRequest(SponsorSegment segment) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.VIEWED_SEGMENT, segment.UUID);
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.VIEWED_SEGMENT, segment.UUID);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@ -114,17 +118,18 @@ public class Requester {
|
||||
}
|
||||
}
|
||||
|
||||
public static void voteForSegment(SponsorSegment segment, VoteOption voteOption, Context context, Runnable toastRunnable, String... args) {
|
||||
public static void voteForSegment(SponsorSegment segment, VoteOption voteOption, Context context, String... args) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String segmentUuid = segment.UUID;
|
||||
String uuid = SponsorBlockSettings.uuid;
|
||||
String vote = Integer.toString(voteOption == VoteOption.UPVOTE ? 1 : 0);
|
||||
|
||||
Toast.makeText(context, str("vote_started"), Toast.LENGTH_SHORT).show();
|
||||
runOnMainThread(() -> Toast.makeText(context, str("vote_started"), Toast.LENGTH_SHORT).show());
|
||||
|
||||
HttpURLConnection connection = voteOption == VoteOption.CATEGORY_CHANGE
|
||||
? getConnectionFromRoute(Route.VOTE_ON_SEGMENT_CATEGORY, segmentUuid, uuid, args[0])
|
||||
: getConnectionFromRoute(Route.VOTE_ON_SEGMENT_QUALITY, segmentUuid, uuid, vote);
|
||||
? getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_CATEGORY, segmentUuid, uuid, args[0])
|
||||
: getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_QUALITY, segmentUuid, uuid, vote);
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
switch (responseCode) {
|
||||
@ -132,18 +137,19 @@ public class Requester {
|
||||
SponsorBlockUtils.messageToToast = str("vote_succeeded");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_forbidden");
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_forbidden", Requester.parseJson(connection.getErrorStream()));
|
||||
break;
|
||||
default:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
break;
|
||||
}
|
||||
new Handler(Looper.getMainLooper()).post(toastRunnable);
|
||||
runOnMainThread(() -> Toast.makeText(context, SponsorBlockUtils.messageToToast, Toast.LENGTH_LONG).show());
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void retrieveUserStats(PreferenceCategory category, Preference loadingPreference) {
|
||||
@ -154,9 +160,7 @@ public class Requester {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.GET_USER_STATS, SponsorBlockSettings.uuid);
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
connection.disconnect();
|
||||
JSONObject json = getJSONObject(SBRoutes.GET_USER_STATS, SponsorBlockSettings.uuid);
|
||||
UserStats stats = new UserStats(json.getString("userName"), json.getDouble("minutesSaved"), json.getInt("segmentCount"),
|
||||
json.getInt("viewCount"));
|
||||
SponsorBlockUtils.addUserStats(category, loadingPreference, stats);
|
||||
@ -167,41 +171,38 @@ public class Requester {
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void setUsername(String username, Runnable toastRunnable) {
|
||||
public static void setUsername(String username, EditTextPreference preference, Runnable toastRunnable) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.CHANGE_USERNAME, SponsorBlockSettings.uuid, username);
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.uuid, username);
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
if (responseCode == 200) {
|
||||
SponsorBlockUtils.messageToToast = str("stats_username_changed");
|
||||
runOnMainThread(() -> {
|
||||
preference.setTitle(fromHtml(str("stats_username", username)));
|
||||
preference.setText(username);
|
||||
});
|
||||
}
|
||||
else {
|
||||
SponsorBlockUtils.messageToToast = str("stats_username_change_unknown_error", responseCode, connection.getResponseMessage());
|
||||
}
|
||||
new Handler(Looper.getMainLooper()).post(toastRunnable);
|
||||
runOnMainThread(toastRunnable);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
String url = SPONSORBLOCK_API_URL + route.compile(params).getCompiledRoute();
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setRequestMethod(route.getMethod().name());
|
||||
return connection;
|
||||
return Requester.getConnectionFromRoute(SPONSORBLOCK_API_URL, route, params);
|
||||
}
|
||||
|
||||
private static String parseJson(HttpURLConnection connection) throws IOException {
|
||||
StringBuilder jsonBuilder = new StringBuilder();
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
jsonBuilder.append(line);
|
||||
}
|
||||
inputStream.close();
|
||||
return jsonBuilder.toString();
|
||||
private static JSONObject getJSONObject(Route route, String... params) throws Exception {
|
||||
return Requester.getJSONObject(getConnectionFromRoute(route, params));
|
||||
}
|
||||
}
|
18
app/src/main/java/pl/jakubweg/requests/SBRoutes.java
Normal file
18
app/src/main/java/pl/jakubweg/requests/SBRoutes.java
Normal file
@ -0,0 +1,18 @@
|
||||
package pl.jakubweg.requests;
|
||||
|
||||
import static fi.vanced.utils.requests.Route.Method.GET;
|
||||
import static fi.vanced.utils.requests.Route.Method.POST;
|
||||
|
||||
import fi.vanced.utils.requests.Route;
|
||||
|
||||
public class SBRoutes {
|
||||
public static final Route GET_SEGMENTS = new Route(GET, "skipSegments?videoID={video_id}&categories={categories}");
|
||||
public static final Route VIEWED_SEGMENT = new Route(POST, "viewedVideoSponsorTime?UUID={segment_id}");
|
||||
public static final Route GET_USER_STATS = new Route(GET, "userInfo?userID={user_id}&values=[\"userName\", \"minutesSaved\", \"segmentCount\", \"viewCount\"]");
|
||||
public static final Route CHANGE_USERNAME = new Route(POST, "setUsername?userID={user_id}&username={username}");
|
||||
public static final Route SUBMIT_SEGMENTS = new Route(POST, "skipSegments?videoID={video_id}&userID={user_id}&startTime={start_time}&endTime={end_time}&category={category}");
|
||||
public static final Route VOTE_ON_SEGMENT_QUALITY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&type={type}");
|
||||
public static final Route VOTE_ON_SEGMENT_CATEGORY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&category={category}");
|
||||
|
||||
private SBRoutes() {}
|
||||
}
|
@ -151,6 +151,8 @@
|
||||
<string name="general_skipcount_sum">This lets SponsorBlock leaderboard system know how much time people have saved. The extension sends a message to the server each time you skip a segment</string>
|
||||
<string name="general_adjusting">Adjusting new segment step</string>
|
||||
<string name="general_adjusting_sum">This is the number of milliseconds you can move when you use the time adjustment buttons while adding new segment</string>
|
||||
<string name="general_min_duration">Minimum segment duration</string>
|
||||
<string name="general_min_duration_sum">Segments shorter than the set value (in seconds) will not be skipped or show in the player</string>
|
||||
<string name="general_uuid">Your unique user id</string>
|
||||
<string name="general_uuid_sum">This should be kept private. This is like a password and should not be shared with anyone. If someone has this, they can impersonate you</string>
|
||||
<string name="settings_ie">Import/Export settings</string>
|
||||
@ -173,6 +175,8 @@
|
||||
<string name="segments_selfpromo_sum">Similar to "sponsor" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with</string>
|
||||
<string name="segments_nomusic">Music: Non-Music Section</string>
|
||||
<string name="segments_nomusic_sum">Only for use in music videos. This includes introductions or outros in music videos</string>
|
||||
<string name="segments_filler">Filler/Tangent</string>
|
||||
<string name="segments_filler_sum">Tangential scenes added only for filler or humor that are not required to understand the main content of the video. This should not include segments providing context or background details</string>
|
||||
<string name="skipped_segment">Skipped a sponsor segment</string>
|
||||
<string name="skipped_sponsor">Skipped sponsor</string>
|
||||
<string name="skipped_intermission">Skipped intro</string>
|
||||
@ -181,6 +185,7 @@
|
||||
<string name="skipped_selfpromo">Skipped self promotion</string>
|
||||
<string name="skipped_nomusic">Skipped silence</string>
|
||||
<string name="skipped_preview">Skipped preview</string>
|
||||
<string name="skipped_filler">Skipped filler</string>
|
||||
<string name="skipped_unsubmitted">Skipped unsubmitted segment</string>
|
||||
<string name="skip_automatically">Skip automatically</string>
|
||||
<string name="skip_showbutton">Show a skip button</string>
|
||||
@ -193,14 +198,14 @@
|
||||
|
||||
<string name="submit_failed_unknown_error" formatted="false">Unable to submit segments: Status: %d %s</string>
|
||||
<string name="submit_failed_rate_limit">Can\'t submit the segment.\nRate Limited (Too many from the same user or IP)</string>
|
||||
<string name="submit_failed_forbidden">Can\'t submit the segment.\nRejected by auto moderator</string>
|
||||
<string name="submit_failed_forbidden" formatted="false">Can\'t submit the segment.\n\n%s</string>
|
||||
<string name="submit_failed_duplicate">Can\'t submit the segment.\nAlready exists</string>
|
||||
<string name="submit_succeeded">Segment submitted successfully</string>
|
||||
<string name="submit_started">Submitting segment…</string>
|
||||
|
||||
<string name="vote_failed_unknown_error" formatted="false">Unable to vote for segment: Status: %d %s</string>
|
||||
<string name="vote_failed_rate_limit">Can\'t vote for segment.\nRate Limited (Too many from the same user or IP)</string>
|
||||
<string name="vote_failed_forbidden">Can\'t vote for segment.\nA moderator has decided that this segment is correct</string>
|
||||
<string name="vote_failed_forbidden" formatted="false">Can\'t vote for segment.\n\n%s</string>
|
||||
<string name="vote_succeeded">Voted successfully</string>
|
||||
<string name="vote_started">Voting for segment…</string>
|
||||
<string name="vote_upvote">Upvote</string>
|
||||
@ -311,14 +316,16 @@
|
||||
|
||||
<string name="general_time_without_sb">Show time without segments</string>
|
||||
<string name="general_time_without_sb_sum">This time appears in brackets next to the current time. This shows the total video duration minus any segments.</string>
|
||||
<string name="general_whitelisting">Channel whitelisting</string>
|
||||
<string name="general_whitelisting_sum">Use the Segments button under the player to whitelist a channel</string>
|
||||
<string name="segments_preview">Preview/Recap</string>
|
||||
<string name="segments_preview_sum">Quick recap of previous episodes, or a preview of what\'s coming up later in the current video. Meant for edited together clips, not for spoken summaries.</string>
|
||||
<string name="stats">Stats</string>
|
||||
<string name="stats_loading">Loading..</string>
|
||||
<string name="stats_sb_disabled">SponsorBlock is disabled</string>
|
||||
<string name="stats_username">Your username: <b>%s</b></string>
|
||||
<string name="stats_username" formatted="false">Your username: <b>%s</b></string>
|
||||
<string name="stats_username_change">Click to change your username</string>
|
||||
<string name="stats_username_change_unknown_error">Unable to change username: Status: %d %s</string>
|
||||
<string name="stats_username_change_unknown_error" formatted="false">Unable to change username: Status: %d %s</string>
|
||||
<string name="stats_username_changed">Username successfully changed</string>
|
||||
<string name="stats_submissions">Submissions: <b>%s</b></string>
|
||||
<string name="stats_saved">You\'ve saved people from <b>%s</b> segments.</string>
|
||||
@ -326,7 +333,8 @@
|
||||
<string name="stats_self_saved">You\'ve skipped <b>%s</b> segments.</string>
|
||||
<string name="stats_self_saved_sum">That\'s <b>%s</b>.</string>
|
||||
<string name="minutes">minutes</string>
|
||||
<string name="color_change">Change colors</string>
|
||||
<string name="color_change">Are you looking for changing colors?</string>
|
||||
<string name="color_change_sum">You can now change a category\'s color by clicking on it above.</string>
|
||||
<string name="color_choose_category">Choose the category</string>
|
||||
<string name="color_changed">Color changed</string>
|
||||
<string name="color_reset">Color reset</string>
|
||||
@ -334,22 +342,42 @@
|
||||
<string name="change">Change</string>
|
||||
<string name="reset">Reset</string>
|
||||
|
||||
<string name="action_copy">Copy</string>
|
||||
<string name="action_tcopy">TCopy</string>
|
||||
<string name="action_copy">Copy link</string>
|
||||
<string name="action_tcopy">Timestamp</string>
|
||||
<string name="action_ads">Ads</string>
|
||||
<string name="action_segments">Segments</string>
|
||||
|
||||
<string name="vanced_video_ad_settings_title">Video ad settings</string>
|
||||
<string name="vanced_videoadwhitelisting_title">Video ad whitelisting</string>
|
||||
<string name="vanced_videoadwhitelisting_summary_off">Video ad whitelisting is turned off</string>
|
||||
<string name="vanced_videoadwhitelisting_summary_on">Video ad whitelisting is turned on. Use the ad button under the player to whitelist a channel</string>
|
||||
<string name="vanced_videoadwhitelisting_summary_on">Video ad whitelisting is turned on. Use the Ads button under the player to whitelist a channel</string>
|
||||
<string name="vanced_whitelisting_ads">Ads</string>
|
||||
<string name="vanced_whitelisting_sponsorblock">SponsorBlock</string>
|
||||
<string name="vanced_whitelisting_added" formatted="false">Channel %s was added to the %s whitelist</string>
|
||||
<string name="vanced_whitelisting_removed" formatted="false">Channel %s was removed from the %s whitelist</string>
|
||||
<string name="vanced_whitelisting_add_failed" formatted="false">Failed to add channel %s to the %s whitelist</string>
|
||||
<string name="vanced_whitelisting_remove_failed" formatted="false">Failed to remove channel %s from the %s whitelist</string>
|
||||
<string name="vanced_whitelisting_fetch_failed" formatted="false">Failed to retrieve channel details, received code %d</string>
|
||||
<string name="vanced_button_location_entry_none">Hidden</string>
|
||||
<string name="vanced_button_location_entry_player">In player</string>
|
||||
<string name="vanced_button_location_entry_buttoncontainer">Under player</string>
|
||||
<string name="vanced_button_location_entry_both">Both</string>
|
||||
<string name="vanced_ryd_settings_title">RYD settings</string>
|
||||
<string name="vanced_ryd_settings_title">Return YouTube Dislike settings</string>
|
||||
<string name="vanced_ryd_settings_summary">Uses the RYD API</string>
|
||||
<string name="vanced_ryd_title">Enable RYD</string>
|
||||
<string name="vanced_ryd_summary">Switch this on to see the dislike counts again</string>
|
||||
<string name="vanced_ryd_attribution_title">Return YouTube Dislike Integration</string>
|
||||
<string name="vanced_ryd_attribution_summary">This integration uses the RYD API from https://returnyoutubedislike.com. Tap to learn more</string>
|
||||
|
||||
<string name="xfile_xfenster_tablet_title">Tablet style</string>
|
||||
<string name="xfile_xfenster_tablet_summary_on">Tablet style is turned on. For example suggested videos are only partially working</string>
|
||||
<string name="xfile_xfenster_tablet_summary_off">Tablet style is turned off</string>
|
||||
|
||||
<string name="vanced_ryd">Return YouTube Dislike</string>
|
||||
<string name="vanced_ryd_firstrun">Want to enable Return YouTube Dislikes to see dislikes again? Your likes/dislikes will be sent to RYD API (anonymously) after enabling RYD integration. You can enable/disable this in the settings at any time.</string>
|
||||
<string name="vanced_sb">SponsorBlock</string>
|
||||
<string name="vanced_sb_firstrun">Are you aware of the SponsorBlock integration in Vanced? With it you can skip sponsored segments in the videos. You can enable/disable this in the settings at any time.</string>
|
||||
<string name="vanced_learnmore">Learn more</string>
|
||||
<string name="vanced_disable">Disable</string>
|
||||
<string name="vanced_enable">Enable</string>
|
||||
</resources>
|
||||
|
@ -5,7 +5,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.0'
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
Loading…
Reference in New Issue
Block a user