mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-01-06 01:55:50 +01:00
Merge pull request #68 from YTVanced/ryd-integration
This commit is contained in:
commit
b094547dd3
@ -1,16 +1,22 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.2"
|
||||
compileSdkVersion 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId "pl.jakubweg"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
applicationId "vanced.integrations"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 31
|
||||
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 {
|
||||
@ -26,6 +32,6 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.annotation:annotation:1.2.0'
|
||||
implementation 'androidx.annotation:annotation:1.3.0'
|
||||
}
|
||||
|
||||
|
@ -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>
|
@ -0,0 +1,25 @@
|
||||
package com.google.android.apps.youtube.app.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class SlimMetadataScrollableButtonContainerLayout extends ViewGroup {
|
||||
|
||||
public SlimMetadataScrollableButtonContainerLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SlimMetadataScrollableButtonContainerLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SlimMetadataScrollableButtonContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fi.vanced.libraries.youtube.player;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ChannelModel implements Serializable {
|
||||
private String author;
|
||||
private String channelId;
|
||||
|
||||
public ChannelModel(String author, String channelId) {
|
||||
this.author = author;
|
||||
this.channelId = channelId;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getChannelId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
public void setChannelId(String channelId) {
|
||||
this.channelId = channelId;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package fi.vanced.libraries.youtube.player;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
public class VideoHelpers {
|
||||
public static final String TAG = "VideoHelpers";
|
||||
|
||||
public static void copyVideoUrlToClipboard() {
|
||||
generateVideoUrl(false);
|
||||
}
|
||||
|
||||
public static void copyVideoUrlWithTimeStampToClipboard() {
|
||||
generateVideoUrl(true);
|
||||
}
|
||||
|
||||
private static void generateVideoUrl(boolean appendTimeStamp) {
|
||||
try {
|
||||
String videoId = VideoInformation.currentVideoId;
|
||||
if (videoId == null || videoId.isEmpty()) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "VideoId was empty");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String videoUrl = String.format("https://youtu.be/%s", videoId);
|
||||
if (appendTimeStamp) {
|
||||
long videoTime = VideoInformation.lastKnownVideoTime;
|
||||
videoUrl += String.format("?t=%s", (videoTime / 1000));
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "Video URL: " + videoUrl);
|
||||
}
|
||||
|
||||
setClipboard(YouTubeTikTokRoot_Application.getAppContext(), videoUrl);
|
||||
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), str("share_copy_url_success"), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Couldn't generate video url", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setClipboard(Context context, String text) {
|
||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
} else {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText("link", text);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,86 @@
|
||||
package fi.vanced.libraries.youtube.player;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import fi.vanced.libraries.youtube.ryd.ReturnYouTubeDislikes;
|
||||
|
||||
public class VideoInformation {
|
||||
private static final String TAG = "VI - VideoInfo";
|
||||
|
||||
public static String currentVideoId;
|
||||
public static Integer dislikeCount;
|
||||
public static String channelName;
|
||||
public static long lastKnownVideoTime = -1L;
|
||||
|
||||
private static boolean tempInfoSaved = false;
|
||||
private static String tempVideoId;
|
||||
private static Integer tempDislikeCount;
|
||||
|
||||
// Call hook in the YT code when the video changes
|
||||
public static void setCurrentVideoId(final String videoId) {
|
||||
if (videoId == null) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "setCurrentVideoId - new id was null - currentVideoId was" + currentVideoId);
|
||||
}
|
||||
clearInformation(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore temporary information that was stored from the last watched video
|
||||
if (tempInfoSaved) {
|
||||
restoreTempInformation();
|
||||
}
|
||||
|
||||
if (videoId.equals(currentVideoId)) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "setCurrentVideoId - new and current video were equal - " + videoId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "setCurrentVideoId - video id updated from " + currentVideoId + " to " + videoId);
|
||||
}
|
||||
|
||||
currentVideoId = videoId;
|
||||
|
||||
// New video
|
||||
ReturnYouTubeDislikes.newVideoLoaded(videoId);
|
||||
}
|
||||
|
||||
// Call hook in the YT code when the video ends
|
||||
public static void videoEnded() {
|
||||
saveTempInformation();
|
||||
clearInformation(false);
|
||||
}
|
||||
|
||||
// Information is cleared once a video ends
|
||||
// It's cleared because the setCurrentVideoId isn't called for Shorts
|
||||
// so Shorts would otherwise use the information from the last watched video
|
||||
private static void clearInformation(boolean full) {
|
||||
if (full) {
|
||||
currentVideoId = null;
|
||||
dislikeCount = null;
|
||||
}
|
||||
channelName = null;
|
||||
}
|
||||
|
||||
// Temporary information is saved once a video ends
|
||||
// so that if the user watches the same video again,
|
||||
// the information can be restored without having to fetch again
|
||||
private static void saveTempInformation() {
|
||||
tempVideoId = currentVideoId;
|
||||
tempDislikeCount = dislikeCount;
|
||||
tempInfoSaved = true;
|
||||
}
|
||||
|
||||
private static void restoreTempInformation() {
|
||||
currentVideoId = tempVideoId;
|
||||
dislikeCount = tempDislikeCount;
|
||||
tempVideoId = null;
|
||||
tempDislikeCount = null;
|
||||
tempInfoSaved = false;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
|
||||
public class RYDFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getPreferenceManager().setSharedPreferencesName(PREFERENCES_NAME);
|
||||
|
||||
final Activity context = this.getActivity();
|
||||
|
||||
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
|
||||
setPreferenceScreen(preferenceScreen);
|
||||
|
||||
// RYD enable toggle
|
||||
{
|
||||
SwitchPreference preference = new SwitchPreference(context);
|
||||
preferenceScreen.addPreference(preference);
|
||||
preference.setKey(PREFERENCES_KEY_RYD_ENABLED);
|
||||
preference.setDefaultValue(false);
|
||||
preference.setChecked(SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED));
|
||||
preference.setTitle(str("vanced_ryd_title"));
|
||||
preference.setSummary(str("vanced_ryd_summary"));
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
final boolean value = (Boolean) newValue;
|
||||
ReturnYouTubeDislikes.onEnabledChange(value);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private void addAboutCategory(Context context, PreferenceScreen screen) {
|
||||
PreferenceCategory category = new PreferenceCategory(context);
|
||||
screen.addPreference(category);
|
||||
category.setTitle(str("about"));
|
||||
|
||||
{
|
||||
Preference preference = new Preference(context);
|
||||
screen.addPreference(preference);
|
||||
preference.setTitle(str("vanced_ryd_attribution_title"));
|
||||
preference.setSummary(str("vanced_ryd_attribution_summary"));
|
||||
preference.setOnPreferenceClickListener(pref -> {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse("https://returnyoutubedislike.com"));
|
||||
pref.getContext().startActivity(i);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
Preference preference = new Preference(context);
|
||||
screen.addPreference(preference);
|
||||
preference.setTitle("GitHub");
|
||||
preference.setOnPreferenceClickListener(pref -> {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse("https://github.com/Anarios/return-youtube-dislike"));
|
||||
pref.getContext().startActivity(i);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
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";
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
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.randomString;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
|
||||
|
||||
public class Registration {
|
||||
private static final String TAG = "VI - RYD - Registration";
|
||||
|
||||
private String userId;
|
||||
private Context context;
|
||||
|
||||
public Registration(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId != null ? userId : fetchUserId();
|
||||
}
|
||||
|
||||
private String fetchUserId() {
|
||||
try {
|
||||
if (this.context == null) throw new Exception("Unable to fetch userId because context was null");
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
this.userId = preferences.getString(PREFERENCES_KEY_USERID, null);
|
||||
|
||||
if (this.userId == null) {
|
||||
this.userId = register();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to fetch the userId from shared preferences", ex);
|
||||
}
|
||||
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
public void saveUserId(String userId) {
|
||||
try {
|
||||
if (this.context == null) throw new Exception("Unable to save userId because context was null");
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString(PREFERENCES_KEY_USERID, userId).apply();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to save the userId in shared preferences", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String register() {
|
||||
String userId = randomString(36);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to register the following userId: " + userId);
|
||||
}
|
||||
return RYDRequester.register(userId, this);
|
||||
}
|
||||
}
|
@ -0,0 +1,318 @@
|
||||
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 android.content.Context;
|
||||
import android.icu.text.CompactDecimalFormat;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
|
||||
public class ReturnYouTubeDislikes {
|
||||
public static boolean isEnabled;
|
||||
public static final String TAG = "VI - RYD";
|
||||
private static View _dislikeView = null;
|
||||
private static Thread _dislikeFetchThread = null;
|
||||
private static Thread _votingThread = null;
|
||||
private static Registration registration;
|
||||
private static Voting voting;
|
||||
private static boolean likeActive;
|
||||
private static boolean dislikeActive;
|
||||
private static int votingValue = 0; // 1 = like, -1 = dislike, 0 = no vote
|
||||
private static CompactDecimalFormat compactNumberFormatter;
|
||||
|
||||
static {
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
isEnabled = SharedPrefUtils.getBoolean(Objects.requireNonNull(context), PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, false);
|
||||
if (isEnabled) {
|
||||
registration = new Registration(context);
|
||||
voting = new Voting(context, registration);
|
||||
}
|
||||
|
||||
Locale locale = context.getResources().getConfiguration().locale;
|
||||
if (debug) {
|
||||
Log.d(TAG, "locale - " + locale);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
compactNumberFormatter = CompactDecimalFormat.getInstance(
|
||||
locale,
|
||||
CompactDecimalFormat.CompactStyle.SHORT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onEnabledChange(boolean enabled) {
|
||||
isEnabled = enabled;
|
||||
if (registration == null) {
|
||||
registration = new Registration(YouTubeTikTokRoot_Application.getAppContext());
|
||||
}
|
||||
if (voting == null) {
|
||||
voting = new Voting(YouTubeTikTokRoot_Application.getAppContext(), registration);
|
||||
}
|
||||
}
|
||||
|
||||
public static void newVideoLoaded(String videoId) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "newVideoLoaded - " + videoId);
|
||||
}
|
||||
|
||||
dislikeCount = null;
|
||||
if (!isEnabled) return;
|
||||
|
||||
try {
|
||||
if (_dislikeFetchThread != null && _dislikeFetchThread.getState() != Thread.State.TERMINATED) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Interrupting the thread. Current state " + _dislikeFetchThread.getState());
|
||||
}
|
||||
_dislikeFetchThread.interrupt();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error in the dislike fetch thread", ex);
|
||||
}
|
||||
|
||||
_dislikeFetchThread = new Thread(() -> RYDRequester.fetchDislikes(videoId));
|
||||
_dislikeFetchThread.start();
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void setLikeTag(View view) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
setTag(view, "like");
|
||||
}
|
||||
|
||||
public static void setLikeTag(View view, boolean active) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
likeActive = active;
|
||||
if (likeActive) {
|
||||
votingValue = 1;
|
||||
}
|
||||
if (debug) {
|
||||
Log.d(TAG, "Like tag active " + likeActive);
|
||||
}
|
||||
setTag(view, "like");
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void setDislikeTag(View view) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
_dislikeView = view;
|
||||
setTag(view, "dislike");
|
||||
}
|
||||
|
||||
public static void setDislikeTag(View view, boolean active) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
dislikeActive = active;
|
||||
if (dislikeActive) {
|
||||
votingValue = -1;
|
||||
}
|
||||
_dislikeView = view;
|
||||
if (debug) {
|
||||
Log.d(TAG, "Dislike tag active " + dislikeActive);
|
||||
}
|
||||
setTag(view, "dislike");
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static CharSequence onSetText(View view, CharSequence originalText) {
|
||||
if (!isEnabled) return originalText;
|
||||
return handleOnSetText(view, originalText);
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void onClick(View view, boolean inactive) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
handleOnClick(view, inactive);
|
||||
}
|
||||
|
||||
private static CharSequence handleOnSetText(View view, CharSequence originalText) {
|
||||
if (!isEnabled) return originalText;
|
||||
|
||||
try {
|
||||
CharSequence tag = (CharSequence) view.getTag();
|
||||
if (debug) {
|
||||
Log.d(TAG, "handleOnSetText - " + tag + " - original text - " + originalText);
|
||||
}
|
||||
if (tag == null) return originalText;
|
||||
|
||||
if (tag == "like") {
|
||||
return originalText;
|
||||
}
|
||||
else if (tag == "dislike") {
|
||||
return dislikeCount != null ? formatDislikes(dislikeCount) : originalText;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error while handling the setText", ex);
|
||||
}
|
||||
|
||||
return originalText;
|
||||
}
|
||||
|
||||
public static void trySetDislikes(String dislikeCount) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
try {
|
||||
// Try to set normal video dislike count
|
||||
if (_dislikeView == null) {
|
||||
if (debug) { Log.d(TAG, "_dislikeView was null"); }
|
||||
return;
|
||||
}
|
||||
|
||||
View buttonView = _dislikeView.findViewById(getIdentifier("button_text", "id"));
|
||||
if (buttonView == null) {
|
||||
if (debug) { Log.d(TAG, "buttonView was null"); }
|
||||
return;
|
||||
}
|
||||
TextView button = (TextView) buttonView;
|
||||
button.setText(dislikeCount);
|
||||
if (debug) {
|
||||
Log.d(TAG, "trySetDislikes - " + dislikeCount);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (debug) {
|
||||
Log.e(TAG, "Error while trying to set dislikes text", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleOnClick(View view, boolean previousState) {
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
if (!isEnabled || SharedPrefUtils.getBoolean(Objects.requireNonNull(context),"youtube","user_signed_out",true)) return;
|
||||
|
||||
try {
|
||||
String tag = (String) view.getTag();
|
||||
if (debug) {
|
||||
Log.d(TAG, "handleOnClick - " + tag + " - previousState - " + previousState);
|
||||
}
|
||||
if (tag == null) return;
|
||||
|
||||
// If active status was removed, vote should be none
|
||||
if (previousState) { votingValue = 0; }
|
||||
if (tag.equals("like")) {
|
||||
|
||||
// Like was activated
|
||||
if (!previousState) { votingValue = 1; likeActive = true; }
|
||||
else { likeActive = false; }
|
||||
|
||||
// Like was activated and dislike was previously activated
|
||||
if (!previousState && dislikeActive) { dislikeCount--; trySetDislikes(formatDislikes(dislikeCount)); }
|
||||
dislikeActive = false;
|
||||
}
|
||||
else if (tag.equals("dislike")) {
|
||||
likeActive = false;
|
||||
|
||||
// Dislike was activated
|
||||
if (!previousState) { votingValue = -1; dislikeActive = true; dislikeCount++; }
|
||||
// Dislike was removed
|
||||
else { dislikeActive = false; dislikeCount--; }
|
||||
trySetDislikes(formatDislikes(dislikeCount));
|
||||
}
|
||||
else {
|
||||
// Unknown tag
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "New vote status - " + votingValue);
|
||||
Log.d(TAG, "Like button " + likeActive + " | Dislike button " + dislikeActive);
|
||||
}
|
||||
|
||||
sendVote(votingValue);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error while handling the onClick", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendVote(int vote) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "sending vote - " + vote + " for video " + currentVideoId);
|
||||
}
|
||||
|
||||
try {
|
||||
if (_votingThread != null && _votingThread.getState() != Thread.State.TERMINATED) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Interrupting the thread. Current state " + _votingThread.getState());
|
||||
}
|
||||
_votingThread.interrupt();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error in the voting thread", ex);
|
||||
}
|
||||
|
||||
_votingThread = new Thread(() -> {
|
||||
try {
|
||||
boolean result = voting.sendVote(currentVideoId, vote);
|
||||
if (debug) {
|
||||
Log.d(TAG, "sendVote status " + result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to send vote", ex);
|
||||
}
|
||||
});
|
||||
_votingThread.start();
|
||||
}
|
||||
|
||||
private static void setTag(View view, String tag) {
|
||||
if (!isEnabled) return;
|
||||
|
||||
try {
|
||||
if (view == null) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "View was empty");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "setTag - " + tag);
|
||||
}
|
||||
|
||||
view.setTag(tag);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error while trying to set tag to view", ex);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Log.d(TAG, "Formatting dislikes - " + dislikes + " - " + formatted);
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "Couldn't format dislikes, using the unformatted count - " + dislikes);
|
||||
}
|
||||
return String.valueOf(dislikes);
|
||||
}
|
||||
}
|
65
app/src/main/java/fi/vanced/libraries/youtube/ryd/Utils.java
Normal file
65
app/src/main/java/fi/vanced/libraries/youtube/ryd/Utils.java
Normal file
@ -0,0 +1,65 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = "VI - RYD - Utils";
|
||||
|
||||
public static String solvePuzzle(String challenge, int difficulty) {
|
||||
byte[] decodedChallenge = Base64.decode(challenge, Base64.NO_WRAP);
|
||||
|
||||
byte[] buffer = new byte[20];
|
||||
for (int i = 4; i < 20; i++) {
|
||||
buffer[i] = decodedChallenge[i - 4];
|
||||
}
|
||||
|
||||
try {
|
||||
int maxCount = (int) (Math.pow(2, difficulty + 1) * 5);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
for (int i = 0; i < maxCount; i++) {
|
||||
buffer[0] = (byte)i;
|
||||
buffer[1] = (byte)(i >> 8);
|
||||
buffer[2] = (byte)(i >> 16);
|
||||
buffer[3] = (byte)(i >> 24);
|
||||
byte[] messageDigest = md.digest(buffer);
|
||||
|
||||
if (countLeadingZeroes(messageDigest) >= difficulty) {
|
||||
String encode = Base64.encodeToString(new byte[]{buffer[0], buffer[1], buffer[2], buffer[3]}, Base64.NO_WRAP);
|
||||
return encode;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to solve puzzle", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static int countLeadingZeroes(byte[] uInt8View) {
|
||||
int zeroes = 0;
|
||||
int value = 0;
|
||||
for (int i = 0; i < uInt8View.length; i++) {
|
||||
value = uInt8View[i] & 0xFF;
|
||||
if (value == 0) {
|
||||
zeroes += 8;
|
||||
} else {
|
||||
int count = 1;
|
||||
if (value >>> 4 == 0) {
|
||||
count += 4;
|
||||
value <<= 4;
|
||||
}
|
||||
if (value >>> 6 == 0) {
|
||||
count += 2;
|
||||
value <<= 2;
|
||||
}
|
||||
zeroes += count - (value >>> 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return zeroes;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
|
||||
|
||||
public class Voting {
|
||||
private static final String TAG = "VI - RYD - Voting";
|
||||
|
||||
private Registration registration;
|
||||
private Context context;
|
||||
|
||||
public Voting(Context context, Registration registration) {
|
||||
this.context = context;
|
||||
this.registration = registration;
|
||||
}
|
||||
|
||||
public boolean sendVote(String videoId, int vote) {
|
||||
String userId = registration.getUserId();
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to vote the following video: " + videoId + " with vote " + vote + " and userId: " + userId);
|
||||
}
|
||||
return RYDRequester.sendVote(videoId, userId, vote);
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
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);
|
||||
int dislikes = json.getInt("dislikes");
|
||||
dislikeCount = dislikes;
|
||||
if (debug) {
|
||||
Log.d(TAG, "dislikes fetched - " + dislikeCount);
|
||||
}
|
||||
|
||||
// Set the dislikes
|
||||
new Handler(Looper.getMainLooper()).post(() -> ReturnYouTubeDislikes.trySetDislikes(ReturnYouTubeDislikes.formatDislikes(dislikes)));
|
||||
}
|
||||
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() {}
|
||||
}
|
@ -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,36 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
|
||||
public class ButtonVisibility {
|
||||
public static Visibility getButtonVisibility(Context context, String key) {
|
||||
return getButtonVisibility(context, key, "youtube");
|
||||
}
|
||||
|
||||
public static Visibility getButtonVisibility(Context context, String key, String preferenceName) {
|
||||
String value = SharedPrefUtils.getString(context, preferenceName, key, null);
|
||||
|
||||
if (value == null || value.isEmpty()) return Visibility.NONE;
|
||||
|
||||
switch (value.toUpperCase()) {
|
||||
case "PLAYER": return Visibility.PLAYER;
|
||||
case "BUTTON_CONTAINER": return Visibility.BUTTON_CONTAINER;
|
||||
case "BOTH": return Visibility.BOTH;
|
||||
default: return Visibility.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isVisibleInContainer(Context context, String key) {
|
||||
return isVisibleInContainer(getButtonVisibility(context, key));
|
||||
}
|
||||
|
||||
public static boolean isVisibleInContainer(Context context, String key, String preferenceName) {
|
||||
return isVisibleInContainer(getButtonVisibility(context, key, preferenceName));
|
||||
}
|
||||
|
||||
public static boolean isVisibleInContainer(Visibility visibility) {
|
||||
return visibility == Visibility.BOTH || visibility == Visibility.BUTTON_CONTAINER;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
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 fi.vanced.libraries.youtube.player.VideoHelpers;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class CopyButton extends SlimButton {
|
||||
public CopyButton(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_button_list"));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_copy_icon", "drawable"));
|
||||
this.button_text.setText(str("action_copy"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
VideoHelpers.copyVideoUrlToClipboard();
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoHelpers;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
public class CopyWithTimestamp extends SlimButton {
|
||||
public CopyWithTimestamp(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_timestamp_button_list"));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_copy_icon_with_time", "drawable"));
|
||||
this.button_text.setText(str("action_tcopy"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
VideoHelpers.copyVideoUrlWithTimeStampToClipboard();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
import pl.jakubweg.SponsorBlockSettings;
|
||||
import pl.jakubweg.SponsorBlockUtils;
|
||||
|
||||
public class SBBrowserButton extends SlimButton {
|
||||
private static final String BROWSER_URL = "https://sb.ltn.fi/video/";
|
||||
|
||||
public SBBrowserButton(Context context, ViewGroup container) {
|
||||
super(context, container, SLIM_METADATA_BUTTON_ID,
|
||||
SponsorBlockUtils.isSBButtonEnabled(context, SponsorBlockSettings.PREFERENCES_KEY_BROWSER_BUTTON));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_sb_browser", "drawable"));
|
||||
this.button_text.setText(str("action_browser"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Uri uri = Uri.parse(BROWSER_URL + VideoInformation.currentVideoId);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
@ -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.VancedUtils;
|
||||
import pl.jakubweg.SponsorBlockUtils;
|
||||
|
||||
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,
|
||||
SponsorBlockUtils.isSBButtonEnabled(context, WhitelistType.SPONSORBLOCK.getPreferenceEnabledName()));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public abstract class SlimButton implements View.OnClickListener {
|
||||
private static final String TAG = "VI - Slim - Button";
|
||||
public static int SLIM_METADATA_BUTTON_ID;
|
||||
public final View view;
|
||||
public final Context context;
|
||||
private final ViewGroup container;
|
||||
protected final ImageView button_icon;
|
||||
protected final TextView button_text;
|
||||
private boolean viewAdded = false;
|
||||
|
||||
static {
|
||||
SLIM_METADATA_BUTTON_ID = VancedUtils.getIdentifier("slim_metadata_button", "layout");
|
||||
}
|
||||
|
||||
public SlimButton(Context context, ViewGroup container, int id, boolean visible) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Adding button with id " + id + " and visibility of " + visible);
|
||||
}
|
||||
this.context = context;
|
||||
this.container = container;
|
||||
view = LayoutInflater.from(context).inflate(id, container, false);
|
||||
button_icon = (ImageView)view.findViewById(VancedUtils.getIdentifier("button_icon", "id"));
|
||||
button_text = (TextView)view.findViewById(VancedUtils.getIdentifier("button_text", "id"));
|
||||
|
||||
view.setOnClickListener(this);
|
||||
|
||||
setVisible(visible);
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
try {
|
||||
if (!viewAdded && visible) {
|
||||
container.addView(view);
|
||||
viewAdded = true;
|
||||
}
|
||||
else if (viewAdded && !visible) {
|
||||
container.removeView(view);
|
||||
viewAdded = false;
|
||||
}
|
||||
setContainerVisibility();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error while changing button visibility", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void setContainerVisibility() {
|
||||
if (container == null) return;
|
||||
|
||||
for (int i = 0; i < container.getChildCount(); i++) {
|
||||
if (container.getChildAt(i).getVisibility() == View.VISIBLE) {
|
||||
container.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
container.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_BROWSER_BUTTON;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.google.android.apps.youtube.app.ui.SlimMetadataScrollableButtonContainerLayout;
|
||||
|
||||
import fi.vanced.libraries.youtube.whitelisting.Whitelist;
|
||||
import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
import pl.jakubweg.SponsorBlockSettings;
|
||||
|
||||
public class SlimButtonContainer extends SlimMetadataScrollableButtonContainerLayout {
|
||||
private static final String TAG = "VI - Slim - Container";
|
||||
private ViewGroup container;
|
||||
private CopyButton copyButton;
|
||||
private CopyWithTimestamp copyWithTimestampButton;
|
||||
public static AdButton adBlockButton;
|
||||
public static SBWhitelistButton sbWhitelistButton;
|
||||
private SBBrowserButton sbBrowserButton;
|
||||
private final Context context;
|
||||
SharedPreferences.OnSharedPreferenceChangeListener listener;
|
||||
|
||||
public SlimButtonContainer(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.initialize(context);
|
||||
}
|
||||
|
||||
public SlimButtonContainer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
this.initialize(context);
|
||||
}
|
||||
|
||||
public SlimButtonContainer(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.context = context;
|
||||
this.initialize(context);
|
||||
}
|
||||
|
||||
public void initialize(Context context) {
|
||||
try {
|
||||
container = this.findViewById(VancedUtils.getIdentifier("button_container_vanced", "id"));
|
||||
if (container == null) throw new Exception("Unable to initialize the button container because the button_container_vanced couldn't be found");
|
||||
|
||||
copyButton = new CopyButton(context, this);
|
||||
copyWithTimestampButton = new CopyWithTimestamp(context, this);
|
||||
adBlockButton = new AdButton(context, this);
|
||||
sbWhitelistButton = new SBWhitelistButton(context, this);
|
||||
sbBrowserButton = new SBBrowserButton(context, this);
|
||||
new SponsorBlockVoting(context, this);
|
||||
|
||||
addSharedPrefsChangeListener();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to initialize the button container", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSharedPrefsChangeListener() {
|
||||
listener = (sharedPreferences, key) -> {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, String.format("SharedPreference changed with key %s", key));
|
||||
}
|
||||
if ("pref_copy_video_url_button_list".equals(key) && copyButton != null) {
|
||||
copyButton.setVisible(ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_button_list"));
|
||||
return;
|
||||
}
|
||||
if ("pref_copy_video_url_timestamp_button_list".equals(key) && copyWithTimestampButton != null) {
|
||||
copyWithTimestampButton.setVisible(ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_timestamp_button_list"));
|
||||
return;
|
||||
}
|
||||
if (PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED.equals(key)) {
|
||||
if (sbWhitelistButton != null) {
|
||||
if (SponsorBlockSettings.isSponsorBlockEnabled) {
|
||||
toggleWhitelistButton();
|
||||
}
|
||||
else {
|
||||
Whitelist.setEnabled(WhitelistType.SPONSORBLOCK, false);
|
||||
sbWhitelistButton.setVisible(false);
|
||||
}
|
||||
}
|
||||
if (sbBrowserButton != null) {
|
||||
if (SponsorBlockSettings.isSponsorBlockEnabled) {
|
||||
toggleBrowserButton();
|
||||
}
|
||||
else {
|
||||
sbBrowserButton.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PREFERENCES_KEY_BROWSER_BUTTON.equals(key) && sbBrowserButton != null) {
|
||||
toggleBrowserButton();
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (WhitelistType.SPONSORBLOCK.getPreferenceEnabledName().equals(key) && sbWhitelistButton != null) {
|
||||
toggleWhitelistButton();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error handling shared preference change", ex);
|
||||
}
|
||||
};
|
||||
|
||||
context.getSharedPreferences(WhitelistType.ADS.getSharedPreferencesName(), Context.MODE_PRIVATE)
|
||||
.registerOnSharedPreferenceChangeListener(listener);
|
||||
context.getSharedPreferences(WhitelistType.SPONSORBLOCK.getSharedPreferencesName(), Context.MODE_PRIVATE)
|
||||
.registerOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
|
||||
private void toggleWhitelistButton() {
|
||||
WhitelistType whitelistSB = WhitelistType.SPONSORBLOCK;
|
||||
String sbEnabledPreferenceName = whitelistSB.getPreferenceEnabledName();
|
||||
boolean enabled = SharedPrefUtils.getBoolean(context, whitelistSB.getSharedPreferencesName(), sbEnabledPreferenceName, false);
|
||||
Whitelist.setEnabled(whitelistSB, enabled);
|
||||
sbWhitelistButton.setVisible(enabled);
|
||||
}
|
||||
|
||||
private void toggleBrowserButton() {
|
||||
sbBrowserButton.setVisible(SharedPrefUtils.getBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_BROWSER_BUTTON, false));
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
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.utils.VancedUtils;
|
||||
|
||||
public class SponsorBlockVoting extends SlimButton {
|
||||
public SponsorBlockVoting(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_voting", "drawable"));
|
||||
this.button_text.setText("SB Voting");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), "Nothing atm", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
public enum Visibility {
|
||||
NONE,
|
||||
PLAYER,
|
||||
BUTTON_CONTAINER,
|
||||
BOTH,
|
||||
}
|
@ -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() {}
|
||||
}
|
83
app/src/main/java/fi/vanced/utils/ObjectSerializer.java
Normal file
83
app/src/main/java/fi/vanced/utils/ObjectSerializer.java
Normal file
@ -0,0 +1,83 @@
|
||||
package fi.vanced.utils;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Modifications copyright (C) 2022 Vanced
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ObjectSerializer {
|
||||
private static final String TAG = "VI - ObjectSerializer";
|
||||
|
||||
public static String serialize(Serializable obj) throws IOException {
|
||||
if (obj == null) return "";
|
||||
try {
|
||||
ByteArrayOutputStream serialObj = new ByteArrayOutputStream();
|
||||
ObjectOutputStream objStream = new ObjectOutputStream(serialObj);
|
||||
objStream.writeObject(obj);
|
||||
objStream.close();
|
||||
return encodeBytes(serialObj.toByteArray());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Serialization error: " + e.getMessage(), e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object deserialize(String str) throws IOException {
|
||||
if (str == null || str.length() == 0) return null;
|
||||
try {
|
||||
ByteArrayInputStream serialObj = new ByteArrayInputStream(decodeBytes(str));
|
||||
ObjectInputStream objStream = new ObjectInputStream(serialObj);
|
||||
return objStream.readObject();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Deserialization error: " + e.getMessage(), e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String encodeBytes(byte[] bytes) {
|
||||
StringBuffer strBuf = new StringBuffer();
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
|
||||
strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
|
||||
}
|
||||
|
||||
return strBuf.toString();
|
||||
}
|
||||
|
||||
public static byte[] decodeBytes(String str) {
|
||||
byte[] bytes = new byte[str.length() / 2];
|
||||
for (int i = 0; i < str.length(); i+=2) {
|
||||
char c = str.charAt(i);
|
||||
bytes[i/2] = (byte) ((c - 'a') << 4);
|
||||
c = str.charAt(i+1);
|
||||
bytes[i/2] += (c - 'a');
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
43
app/src/main/java/fi/vanced/utils/SharedPrefUtils.java
Normal file
43
app/src/main/java/fi/vanced/utils/SharedPrefUtils.java
Normal file
@ -0,0 +1,43 @@
|
||||
package fi.vanced.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class SharedPrefUtils {
|
||||
public static void saveString(Context context, String preferenceName, String key, String value){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
sharedPreferences.edit().putString(key, value).apply();
|
||||
}
|
||||
public static void saveBoolean(Context context, String preferenceName, String key, Boolean value){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
sharedPreferences.edit().putBoolean(key, value).apply();
|
||||
}
|
||||
public static void saveInt(Context context, String preferenceName, String key, Integer value){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
sharedPreferences.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
public static String getString(Context context, String preferenceName, String key){
|
||||
return getString(context, preferenceName, key, null);
|
||||
}
|
||||
public static String getString(Context context, String preferenceName, String key, String _default){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
return (sharedPreferences.getString(key, _default));
|
||||
}
|
||||
|
||||
public static Boolean getBoolean(Context context, String preferenceName, String key){
|
||||
return getBoolean(context, preferenceName, key, false);
|
||||
}
|
||||
public static Boolean getBoolean(Context context, String preferenceName, String key, Boolean _default){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
return (sharedPreferences.getBoolean(key, _default));
|
||||
}
|
||||
|
||||
public static Integer getInt(Context context, String preferenceName, String key){
|
||||
return getInt(context, preferenceName, key, -1);
|
||||
}
|
||||
public static Integer getInt(Context context, String preferenceName, String key, Integer _default){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
return (sharedPreferences.getInt(key, _default));
|
||||
}
|
||||
}
|
63
app/src/main/java/fi/vanced/utils/VancedUtils.java
Normal file
63
app/src/main/java/fi/vanced/utils/VancedUtils.java
Normal file
@ -0,0 +1,63 @@
|
||||
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.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 int getIdentifier(String name, String defType) {
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
return context.getResources().getIdentifier(name, defType, context.getPackageName());
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/157202
|
||||
static final String AB = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
static SecureRandom rnd = new SecureRandom();
|
||||
|
||||
public static String randomString(int len){
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
for(int i = 0; i < len; i++)
|
||||
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);
|
||||
}
|
||||
}
|
57
app/src/main/java/fi/vanced/utils/requests/Requester.java
Normal file
57
app/src/main/java/fi/vanced/utils/requests/Requester.java
Normal file
@ -0,0 +1,57 @@
|
||||
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(), false);
|
||||
}
|
||||
|
||||
public static String parseJson(InputStream inputStream, boolean isError) throws IOException {
|
||||
StringBuilder jsonBuilder = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
jsonBuilder.append(line);
|
||||
if (isError)
|
||||
jsonBuilder.append("\n");
|
||||
}
|
||||
inputStream.close();
|
||||
return jsonBuilder.toString();
|
||||
}
|
||||
|
||||
public static String parseErrorJson(HttpURLConnection connection) throws IOException {
|
||||
return parseJson(connection.getErrorStream(), true);
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -64,8 +67,6 @@ public class PlayerController {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoInformation.currentVideoId = videoId;
|
||||
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
if(context == null){
|
||||
Log.e(TAG, "context is null");
|
||||
@ -125,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)
|
||||
@ -283,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,23 @@
|
||||
package pl.jakubweg;
|
||||
|
||||
import static pl.jakubweg.SponsorBlockSettings.DefaultBehaviour;
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static pl.jakubweg.SponsorBlockSettings.DEFAULT_API_URL;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_API_URL;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_BROWSER_BUTTON;
|
||||
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;
|
||||
@ -21,29 +27,38 @@ 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.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;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.InputType;
|
||||
import android.util.Patterns;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
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 {
|
||||
public static final DecimalFormat FORMATTER = new DecimalFormat("#,###,###");
|
||||
public static final String SAVED_TEMPLATE = "%dh %.1f %s";
|
||||
private static final APIURLChangeListener API_URL_CHANGE_LISTENER = new APIURLChangeListener();
|
||||
private final ArrayList<Preference> preferencesToDisableWhenSBDisabled = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
@ -75,6 +90,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);
|
||||
@ -122,7 +149,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
setSeenGuidelines(context);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("https://github.com/ajayyy/SponsorBlock/wiki/Guidelines"));
|
||||
intent.setData(Uri.parse("https://wiki.sponsor.ajay.app/w/Guidelines"));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@ -143,7 +170,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 +182,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 +211,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 +292,27 @@ 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);
|
||||
}
|
||||
|
||||
{
|
||||
Preference preference = new SwitchPreference(context);
|
||||
preference.setTitle(str("general_browser_button"));
|
||||
preference.setSummary(str("general_browser_button_sum"));
|
||||
preference.setKey(PREFERENCES_KEY_BROWSER_BUTTON);
|
||||
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 +321,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"));
|
||||
@ -299,6 +342,31 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
preferencesToDisableWhenSBDisabled.add(preference);
|
||||
}
|
||||
|
||||
{
|
||||
Preference preference = new Preference(context);
|
||||
String title = str("general_api_url");
|
||||
preference.setTitle(title);
|
||||
preference.setSummary(Html.fromHtml(str("general_api_url_sum")));
|
||||
preference.setOnPreferenceClickListener(preference1 -> {
|
||||
EditText editText = new EditText(context);
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
||||
editText.setText(SponsorBlockSettings.apiUrl);
|
||||
|
||||
API_URL_CHANGE_LISTENER.setEditTextRef(editText);
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(title)
|
||||
.setView(editText)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNeutralButton(str("reset"), API_URL_CHANGE_LISTENER)
|
||||
.setPositiveButton(android.R.string.ok, API_URL_CHANGE_LISTENER)
|
||||
.show();
|
||||
return true;
|
||||
});
|
||||
|
||||
screen.addPreference(preference);
|
||||
preferencesToDisableWhenSBDisabled.add(preference);
|
||||
}
|
||||
|
||||
{
|
||||
EditTextPreference preference = new EditTextPreference(context);
|
||||
Context applicationContext = context.getApplicationContext();
|
||||
@ -315,6 +383,48 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
|
||||
}
|
||||
}
|
||||
|
||||
private static class APIURLChangeListener implements DialogInterface.OnClickListener {
|
||||
private WeakReference<EditText> editTextRef;
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
EditText editText = editTextRef.get();
|
||||
if (editText == null)
|
||||
return;
|
||||
Context context = ((AlertDialog) dialog).getContext();
|
||||
Context applicationContext = context.getApplicationContext();
|
||||
SharedPreferences preferences = SponsorBlockSettings.getPreferences(context);
|
||||
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_NEUTRAL:
|
||||
preferences.edit().putString(PREFERENCES_KEY_API_URL, DEFAULT_API_URL).apply();
|
||||
Toast.makeText(applicationContext, str("api_url_reset"), Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
Editable text = editText.getText();
|
||||
Toast invalidToast = Toast.makeText(applicationContext, str("api_url_invalid"), Toast.LENGTH_SHORT);
|
||||
if (text == null) {
|
||||
invalidToast.show();
|
||||
}
|
||||
else {
|
||||
String textAsString = text.toString();
|
||||
if (textAsString.isEmpty() || !Patterns.WEB_URL.matcher(textAsString).matches()) {
|
||||
invalidToast.show();
|
||||
}
|
||||
else {
|
||||
preferences.edit().putString(PREFERENCES_KEY_API_URL, textAsString).apply();
|
||||
Toast.makeText(applicationContext, str("api_url_changed"), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setEditTextRef(EditText editText) {
|
||||
editTextRef = new WeakReference<>(editText);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
SponsorBlockSettings.update(getActivity());
|
||||
|
@ -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";
|
||||
@ -30,8 +32,14 @@ public class SponsorBlockSettings {
|
||||
public static final String PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME = "sb-skipped-segments-time";
|
||||
public static final String PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS = "sb-length-without-segments";
|
||||
public static final String PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX = "_color";
|
||||
public static final String PREFERENCES_KEY_BROWSER_BUTTON = "sb-browser-button";
|
||||
public static final String PREFERENCES_KEY_IS_VIP = "sb-is-vip";
|
||||
public static final String PREFERENCES_KEY_LAST_VIP_CHECK = "sb-last-vip-check";
|
||||
public static final String PREFERENCES_KEY_API_URL = "sb-api-url";
|
||||
|
||||
public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SKIP_AUTOMATICALLY;
|
||||
public static final String DEFAULT_SERVER_URL = "https://sponsor.ajay.app";
|
||||
public static final String DEFAULT_API_URL = DEFAULT_SERVER_URL + "/api/";
|
||||
|
||||
public static boolean isSponsorBlockEnabled = false;
|
||||
public static boolean seenGuidelinesPopup = false;
|
||||
@ -40,8 +48,12 @@ public class SponsorBlockSettings {
|
||||
public static boolean showToastWhenSkippedAutomatically = true;
|
||||
public static boolean countSkips = true;
|
||||
public static boolean showTimeWithoutSegments = true;
|
||||
public static boolean vip = false;
|
||||
public static long lastVipCheck = 0;
|
||||
public static int adjustNewSegmentMillis = 150;
|
||||
public static float minDuration = 0f;
|
||||
public static String uuid = "<invalid>";
|
||||
public static String apiUrl = DEFAULT_API_URL;
|
||||
public static String sponsorBlockUrlCategories = "[]";
|
||||
public static int skippedSegments;
|
||||
public static long skippedTime;
|
||||
@ -100,9 +112,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 +120,13 @@ public class SponsorBlockSettings {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (behaviour == null)
|
||||
behaviour = DefaultBehaviour;
|
||||
if (behaviour != null) {
|
||||
segment.behaviour = behaviour;
|
||||
}
|
||||
else {
|
||||
behaviour = segment.behaviour;
|
||||
}
|
||||
|
||||
segment.behaviour = behaviour;
|
||||
if (behaviour.showOnTimeBar && segment != SegmentInfo.UNSUBMITTED)
|
||||
enabledCategories.add(segment.key);
|
||||
}
|
||||
@ -132,8 +145,19 @@ 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);
|
||||
vip = preferences.getBoolean(PREFERENCES_KEY_IS_VIP, false);
|
||||
|
||||
String vipCheckTmp = preferences.getString(PREFERENCES_KEY_LAST_VIP_CHECK, null);
|
||||
if (vipCheckTmp != null)
|
||||
lastVipCheck = Long.parseLong(vipCheckTmp);
|
||||
|
||||
apiUrl = preferences.getString(PREFERENCES_KEY_API_URL, DEFAULT_API_URL);
|
||||
|
||||
uuid = preferences.getString(PREFERENCES_KEY_UUID, null);
|
||||
if (uuid == null) {
|
||||
@ -179,13 +203,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 +220,8 @@ public class SponsorBlockSettings {
|
||||
INTERACTION,
|
||||
SELF_PROMO,
|
||||
MUSIC_OFFTOPIC,
|
||||
PREVIEW
|
||||
PREVIEW,
|
||||
FILLER
|
||||
};
|
||||
private static final Map<String, SegmentInfo> mValuesMap = new HashMap<>(values().length);
|
||||
|
||||
|
@ -10,21 +10,32 @@ import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
|
||||
import static pl.jakubweg.PlayerController.sponsorSegmentsOfCurrentVideo;
|
||||
import static pl.jakubweg.SponsorBlockPreferenceFragment.FORMATTER;
|
||||
import static pl.jakubweg.SponsorBlockPreferenceFragment.SAVED_TEMPLATE;
|
||||
import static pl.jakubweg.SponsorBlockSettings.DEFAULT_API_URL;
|
||||
import static pl.jakubweg.SponsorBlockSettings.DEFAULT_SERVER_URL;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_API_URL;
|
||||
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_IS_VIP;
|
||||
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_LAST_VIP_CHECK;
|
||||
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.PREFERENCES_NAME;
|
||||
import static pl.jakubweg.SponsorBlockSettings.apiUrl;
|
||||
import static pl.jakubweg.SponsorBlockSettings.countSkips;
|
||||
import static pl.jakubweg.SponsorBlockSettings.getPreferences;
|
||||
import static pl.jakubweg.SponsorBlockSettings.isSponsorBlockEnabled;
|
||||
import static pl.jakubweg.SponsorBlockSettings.lastVipCheck;
|
||||
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.SponsorBlockSettings.vip;
|
||||
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 +43,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;
|
||||
@ -59,9 +68,10 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
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 {
|
||||
@ -72,6 +82,7 @@ public abstract class SponsorBlockUtils {
|
||||
public static boolean videoHasSegments = false;
|
||||
public static String timeWithoutSegments = "";
|
||||
private static final int sponsorBtnId = 1234;
|
||||
private static final String LOCKED_COLOR = "#FFC83D";
|
||||
public static final View.OnClickListener sponsorBlockBtnListener = v -> {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Shield button clicked");
|
||||
@ -212,21 +223,27 @@ public abstract class SponsorBlockUtils {
|
||||
final SponsorSegment segment = sponsorSegmentsOfCurrentVideo[which];
|
||||
|
||||
final VoteOption[] voteOptions = VoteOption.values();
|
||||
String[] items = new String[voteOptions.length];
|
||||
CharSequence[] items = new CharSequence[voteOptions.length];
|
||||
|
||||
for (int i = 0; i < voteOptions.length; i++) {
|
||||
items[i] = voteOptions[i].title;
|
||||
VoteOption voteOption = voteOptions[i];
|
||||
String title = voteOption.title;
|
||||
if (vip && segment.isLocked && voteOption.shouldHighlight) {
|
||||
items[i] = Html.fromHtml(String.format("<font color=\"%s\">%s</font>", LOCKED_COLOR, title));
|
||||
}
|
||||
else {
|
||||
items[i] = title;
|
||||
}
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setItems(items, (dialog1, which1) -> {
|
||||
appContext = new WeakReference<>(context.getApplicationContext());
|
||||
switch (voteOptions[which1]) {
|
||||
VoteOption voteOption = voteOptions[which1];
|
||||
switch (voteOption) {
|
||||
case UPVOTE:
|
||||
voteForSegment(segment, VoteOption.UPVOTE, appContext.get(), toastRunnable);
|
||||
break;
|
||||
case DOWNVOTE:
|
||||
voteForSegment(segment, VoteOption.DOWNVOTE, appContext.get(), toastRunnable);
|
||||
voteForSegment(segment, voteOption, appContext.get());
|
||||
break;
|
||||
case CATEGORY_CHANGE:
|
||||
onNewCategorySelect(segment, context);
|
||||
@ -235,40 +252,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 +264,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 +384,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();
|
||||
}
|
||||
|
||||
@ -416,7 +399,7 @@ public abstract class SponsorBlockUtils {
|
||||
final SponsorSegment[] segments = original == null ? new SponsorSegment[1] : Arrays.copyOf(original, original.length + 1);
|
||||
|
||||
segments[segments.length - 1] = new SponsorSegment(newSponsorSegmentStartMillis, newSponsorSegmentEndMillis,
|
||||
SponsorBlockSettings.SegmentInfo.UNSUBMITTED, null);
|
||||
SponsorBlockSettings.SegmentInfo.UNSUBMITTED, null, false);
|
||||
|
||||
Arrays.sort(segments);
|
||||
sponsorSegmentsOfCurrentVideo = segments;
|
||||
@ -485,15 +468,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 +488,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,7 +571,17 @@ 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.putBoolean(PREFERENCES_KEY_IS_VIP, settingsJson.getBoolean("isVip"));
|
||||
editor.putString(PREFERENCES_KEY_MIN_DURATION, settingsJson.getString("minDuration"));
|
||||
editor.putString(PREFERENCES_KEY_UUID, settingsJson.getString("userID"));
|
||||
editor.putString(PREFERENCES_KEY_LAST_VIP_CHECK, settingsJson.getString("lastIsVipUpdate"));
|
||||
|
||||
String serverAddress = settingsJson.getString("serverAddress");
|
||||
if (serverAddress.equalsIgnoreCase(DEFAULT_SERVER_URL)) {
|
||||
serverAddress = DEFAULT_API_URL;
|
||||
}
|
||||
editor.putString(PREFERENCES_KEY_API_URL, serverAddress);
|
||||
|
||||
editor.apply();
|
||||
|
||||
Toast.makeText(context, str("settings_import_successful"), Toast.LENGTH_SHORT).show();
|
||||
@ -633,9 +617,18 @@ 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);
|
||||
json.put("isVip", vip);
|
||||
json.put("lastIsVipUpdate", lastVipCheck);
|
||||
|
||||
String apiAddress = apiUrl;
|
||||
if (apiAddress.equalsIgnoreCase(DEFAULT_API_URL)) {
|
||||
apiAddress = DEFAULT_SERVER_URL;
|
||||
}
|
||||
json.put("serverAddress", apiAddress);
|
||||
|
||||
return json.toString();
|
||||
}
|
||||
@ -650,15 +643,22 @@ public abstract class SponsorBlockUtils {
|
||||
return isSponsorBlockEnabled && setting;
|
||||
}
|
||||
|
||||
public static boolean isSBButtonEnabled(Context context, String key) {
|
||||
return isSettingEnabled(SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, key, false));
|
||||
}
|
||||
|
||||
public enum VoteOption {
|
||||
UPVOTE(str("vote_upvote")),
|
||||
DOWNVOTE(str("vote_downvote")),
|
||||
CATEGORY_CHANGE(str("vote_category"));
|
||||
UPVOTE(str("vote_upvote"), false),
|
||||
DOWNVOTE(str("vote_downvote"), true),
|
||||
CATEGORY_CHANGE(str("vote_category"), true);
|
||||
|
||||
public final String title;
|
||||
public final boolean shouldHighlight;
|
||||
|
||||
VoteOption(String title) {
|
||||
|
||||
VoteOption(String title, boolean shouldHighlight) {
|
||||
this.title = title;
|
||||
this.shouldHighlight = shouldHighlight;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,123 @@
|
||||
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.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
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));
|
||||
mEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
try {
|
||||
Color.parseColor(s.toString()); // validation
|
||||
getDialog().setTitle(Html.fromHtml(String.format("<font color=\"%s\">⬤</font> %s", s, category.title)));
|
||||
}
|
||||
catch (Exception ex) {}
|
||||
}
|
||||
});
|
||||
builder.setView(mEditText);
|
||||
builder.setTitle(category.getTitleWithDot());
|
||||
|
||||
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();
|
||||
reformatTitle();
|
||||
});
|
||||
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();
|
||||
reformatTitle();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private void reformatTitle() {
|
||||
this.setTitle(getCategoryBySelf().getTitleWithDot());
|
||||
}
|
||||
}
|
@ -7,12 +7,14 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
public final long end;
|
||||
public final SponsorBlockSettings.SegmentInfo category;
|
||||
public final String UUID;
|
||||
public final boolean isLocked;
|
||||
|
||||
public SponsorSegment(long start, long end, SponsorBlockSettings.SegmentInfo category, String UUID) {
|
||||
public SponsorSegment(long start, long end, SponsorBlockSettings.SegmentInfo category, String UUID, boolean isLocked) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.category = category;
|
||||
this.UUID = UUID;
|
||||
this.isLocked = isLocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -21,6 +23,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
"start=" + start +
|
||||
", end=" + end +
|
||||
", category='" + category + '\'' +
|
||||
", locked=" + isLocked +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
@ -1,207 +0,0 @@
|
||||
package pl.jakubweg.requests;
|
||||
|
||||
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.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
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 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 {
|
||||
private static final String SPONSORBLOCK_API_URL = "https://sponsor.ajay.app/api/";
|
||||
private static final String TIME_TEMPLATE = "%.3f";
|
||||
|
||||
private Requester() {}
|
||||
|
||||
public static synchronized SponsorSegment[] getSegments(String videoId) {
|
||||
List<SponsorSegment> segments = new ArrayList<>();
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories);
|
||||
int responseCode = connection.getResponseCode();
|
||||
videoHasSegments = false;
|
||||
timeWithoutSegments = "";
|
||||
|
||||
if (responseCode == 200) {
|
||||
JSONArray responseArray = new JSONArray(parseJson(connection));
|
||||
int length = responseArray.length();
|
||||
for (int i = 0; i < length; 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);
|
||||
String category = obj.getString("category");
|
||||
String uuid = obj.getString("UUID");
|
||||
|
||||
SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category);
|
||||
if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) {
|
||||
SponsorSegment sponsorSegment = new SponsorSegment(start, end, segmentCategory, uuid);
|
||||
segments.add(sponsorSegment);
|
||||
}
|
||||
}
|
||||
videoHasSegments = true;
|
||||
timeWithoutSegments = SponsorBlockUtils.getTimeWithoutSegments(segments.toArray(new SponsorSegment[0]));
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return segments.toArray(new SponsorSegment[0]);
|
||||
}
|
||||
|
||||
public static void submitSegments(String videoId, String uuid, float startTime, float endTime, String category, Runnable toastRunnable) {
|
||||
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);
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
switch (responseCode) {
|
||||
case 200:
|
||||
SponsorBlockUtils.messageToToast = str("submit_succeeded");
|
||||
break;
|
||||
case 409:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_duplicate");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_forbidden");
|
||||
break;
|
||||
case 429:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_rate_limit");
|
||||
break;
|
||||
default:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
break;
|
||||
}
|
||||
new Handler(Looper.getMainLooper()).post(toastRunnable);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendViewCountRequest(SponsorSegment segment) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.VIEWED_SEGMENT, segment.UUID);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void voteForSegment(SponsorSegment segment, VoteOption voteOption, Context context, Runnable toastRunnable, String... args) {
|
||||
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();
|
||||
|
||||
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);
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
switch (responseCode) {
|
||||
case 200:
|
||||
SponsorBlockUtils.messageToToast = str("vote_succeeded");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_forbidden");
|
||||
break;
|
||||
default:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
break;
|
||||
}
|
||||
new Handler(Looper.getMainLooper()).post(toastRunnable);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void retrieveUserStats(PreferenceCategory category, Preference loadingPreference) {
|
||||
if (!SponsorBlockSettings.isSponsorBlockEnabled) {
|
||||
loadingPreference.setTitle(str("stats_sb_disabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.GET_USER_STATS, SponsorBlockSettings.uuid);
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
connection.disconnect();
|
||||
UserStats stats = new UserStats(json.getString("userName"), json.getDouble("minutesSaved"), json.getInt("segmentCount"),
|
||||
json.getInt("viewCount"));
|
||||
SponsorBlockUtils.addUserStats(category, loadingPreference, stats);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void setUsername(String username, Runnable toastRunnable) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(Route.CHANGE_USERNAME, SponsorBlockSettings.uuid, username);
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
if (responseCode == 200) {
|
||||
SponsorBlockUtils.messageToToast = str("stats_username_changed");
|
||||
}
|
||||
else {
|
||||
SponsorBlockUtils.messageToToast = str("stats_username_change_unknown_error", responseCode, connection.getResponseMessage());
|
||||
}
|
||||
new Handler(Looper.getMainLooper()).post(toastRunnable);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
236
app/src/main/java/pl/jakubweg/requests/SBRequester.java
Normal file
236
app/src/main/java/pl/jakubweg/requests/SBRequester.java
Normal file
@ -0,0 +1,236 @@
|
||||
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.content.SharedPreferences;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import fi.vanced.utils.requests.Requester;
|
||||
import fi.vanced.utils.requests.Route;
|
||||
import pl.jakubweg.PlayerController;
|
||||
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 SBRequester {
|
||||
private static final String TIME_TEMPLATE = "%.3f";
|
||||
|
||||
private SBRequester() {}
|
||||
|
||||
public static synchronized SponsorSegment[] getSegments(String videoId) {
|
||||
List<SponsorSegment> segments = new ArrayList<>();
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories);
|
||||
int responseCode = connection.getResponseCode();
|
||||
runVipCheck();
|
||||
|
||||
if (responseCode == 200) {
|
||||
JSONArray responseArray = Requester.getJSONArray(connection);
|
||||
int length = responseArray.length();
|
||||
for (int i = 0; i < length; 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");
|
||||
boolean locked = obj.getInt("locked") == 1;
|
||||
|
||||
SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category);
|
||||
if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) {
|
||||
SponsorSegment sponsorSegment = new SponsorSegment(start, end, segmentCategory, uuid, locked);
|
||||
segments.add(sponsorSegment);
|
||||
}
|
||||
}
|
||||
if (!segments.isEmpty()) {
|
||||
videoHasSegments = true;
|
||||
timeWithoutSegments = SponsorBlockUtils.getTimeWithoutSegments(segments.toArray(new SponsorSegment[0]));
|
||||
}
|
||||
}
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return segments.toArray(new SponsorSegment[0]);
|
||||
}
|
||||
|
||||
public static void submitSegments(String videoId, String uuid, float startTime, float endTime, String category, Runnable toastRunnable) {
|
||||
try {
|
||||
String start = String.format(Locale.US, TIME_TEMPLATE, startTime);
|
||||
String end = String.format(Locale.US, TIME_TEMPLATE, endTime);
|
||||
String duration = String.valueOf(PlayerController.getCurrentVideoLength() / 1000);
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.SUBMIT_SEGMENTS, videoId, uuid, start, end, category, duration);
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
switch (responseCode) {
|
||||
case 200:
|
||||
SponsorBlockUtils.messageToToast = str("submit_succeeded");
|
||||
break;
|
||||
case 409:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_duplicate");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_forbidden", Requester.parseErrorJson(connection));
|
||||
break;
|
||||
case 429:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_rate_limit");
|
||||
break;
|
||||
default:
|
||||
SponsorBlockUtils.messageToToast = str("submit_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
break;
|
||||
}
|
||||
runOnMainThread(toastRunnable);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendViewCountRequest(SponsorSegment segment) {
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.VIEWED_SEGMENT, segment.UUID);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
runOnMainThread(() -> Toast.makeText(context, str("vote_started"), Toast.LENGTH_SHORT).show());
|
||||
|
||||
HttpURLConnection connection = voteOption == VoteOption.CATEGORY_CHANGE
|
||||
? 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) {
|
||||
case 200:
|
||||
SponsorBlockUtils.messageToToast = str("vote_succeeded");
|
||||
break;
|
||||
case 403:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_forbidden", Requester.parseErrorJson(connection));
|
||||
break;
|
||||
default:
|
||||
SponsorBlockUtils.messageToToast = str("vote_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
if (!SponsorBlockSettings.isSponsorBlockEnabled) {
|
||||
loadingPreference.setTitle(str("stats_sb_disabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void setUsername(String username, EditTextPreference preference, Runnable toastRunnable) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
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());
|
||||
}
|
||||
runOnMainThread(toastRunnable);
|
||||
connection.disconnect();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void runVipCheck() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now < (SponsorBlockSettings.lastVipCheck + TimeUnit.DAYS.toMillis(3))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSONObject json = getJSONObject(SBRoutes.IS_USER_VIP, SponsorBlockSettings.uuid);
|
||||
boolean vip = json.getBoolean("vip");
|
||||
SponsorBlockSettings.vip = vip;
|
||||
SponsorBlockSettings.lastVipCheck = now;
|
||||
|
||||
SharedPreferences.Editor edit = SponsorBlockSettings.getPreferences(YouTubeTikTokRoot_Application.getAppContext()).edit();
|
||||
edit.putString(SponsorBlockSettings.PREFERENCES_KEY_LAST_VIP_CHECK, String.valueOf(now));
|
||||
edit.putBoolean(SponsorBlockSettings.PREFERENCES_KEY_IS_VIP, vip);
|
||||
edit.apply();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(SponsorBlockSettings.apiUrl, route, params);
|
||||
}
|
||||
|
||||
private static JSONObject getJSONObject(Route route, String... params) throws Exception {
|
||||
return Requester.getJSONObject(getConnectionFromRoute(route, params));
|
||||
}
|
||||
}
|
19
app/src/main/java/pl/jakubweg/requests/SBRoutes.java
Normal file
19
app/src/main/java/pl/jakubweg/requests/SBRoutes.java
Normal file
@ -0,0 +1,19 @@
|
||||
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 IS_USER_VIP = new Route(GET, "isUserVIP?userID={user_id}");
|
||||
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}&videoDuration={duration}");
|
||||
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() {}
|
||||
}
|
15
app/src/main/res/values/arrays.xml
Normal file
15
app/src/main/res/values/arrays.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="vanced_button_location_entries">
|
||||
<item>@string/vanced_button_location_entry_none</item>
|
||||
<item>@string/vanced_button_location_entry_player</item>
|
||||
<item>@string/vanced_button_location_entry_buttoncontainer</item>
|
||||
<item>@string/vanced_button_location_entry_both</item>
|
||||
</string-array>
|
||||
<string-array name="vanced_button_location_entry_values">
|
||||
<item>NONE</item>
|
||||
<item>PLAYER</item>
|
||||
<item>BUTTON_CONTAINER</item>
|
||||
<item>BOTH</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -151,10 +151,14 @@
|
||||
<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>
|
||||
<string name="settings_ie_sum">This is your entire configuration that is applicable in the desktop extension in JSON. This includes your userID, so be sure to share this wisely.</string>
|
||||
<string name="general_api_url">Change API URL</string>
|
||||
<string name="general_api_url_sum">The address SponsorBlock uses to make calls to the server. <b>Don\'t change this unless you know what you\'re doing.</b></string>
|
||||
<string name="settings_import_successful">Settings were successfully imported</string>
|
||||
<string name="settings_import_failed">Failed to import settings</string>
|
||||
<string name="settings_export_failed">Failed to export settings</string>
|
||||
@ -173,6 +177,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/Jokes</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 +187,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 +200,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 +318,18 @@
|
||||
|
||||
<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="general_browser_button">Enable SB Browser button</string>
|
||||
<string name="general_browser_button_sum">Clicking this button under the player will open sb.ltn.fi where you can see more details about segments.</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,11 +337,56 @@
|
||||
<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>
|
||||
<string name="color_invalid">Invalid hex code</string>
|
||||
<string name="change">Change</string>
|
||||
<string name="reset">Reset</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="action_browser">SB Browser</string>
|
||||
|
||||
<string name="api_url_changed">API URL changed</string>
|
||||
<string name="api_url_reset">API URL reset</string>
|
||||
<string name="api_url_invalid">Provided API URL is invalid</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 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 (ALPHA)</string>
|
||||
<string name="vanced_button_location_entry_both">Both</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 (ALPHA)</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.1.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Mon Jun 07 19:51:48 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
Loading…
Reference in New Issue
Block a user