├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ ├── com
│ │ └── google
│ │ │ └── android
│ │ │ └── apps
│ │ │ └── youtube
│ │ │ └── app
│ │ │ ├── YouTubeTikTokRoot_Application.java
│ │ │ └── ui
│ │ │ └── SlimMetadataScrollableButtonContainerLayout.java
│ ├── fi
│ │ ├── razerman
│ │ │ └── youtube
│ │ │ │ ├── Helpers
│ │ │ │ └── XSwipeHelper.java
│ │ │ │ └── XGlobals.java
│ │ └── vanced
│ │ │ ├── libraries
│ │ │ └── youtube
│ │ │ │ ├── dialog
│ │ │ │ └── Dialogs.java
│ │ │ │ ├── player
│ │ │ │ ├── ChannelModel.java
│ │ │ │ ├── PlayerType.java
│ │ │ │ ├── VideoHelpers.java
│ │ │ │ └── VideoInformation.java
│ │ │ │ ├── ryd
│ │ │ │ ├── RYDFragment.java
│ │ │ │ ├── RYDSettings.java
│ │ │ │ ├── Registration.java
│ │ │ │ ├── ReturnYouTubeDislikes.java
│ │ │ │ ├── Utils.java
│ │ │ │ ├── Voting.java
│ │ │ │ └── requests
│ │ │ │ │ ├── RYDRequester.java
│ │ │ │ │ └── RYDRoutes.java
│ │ │ │ ├── sponsors
│ │ │ │ └── player
│ │ │ │ │ └── ui
│ │ │ │ │ ├── NewSegmentLayout.java
│ │ │ │ │ ├── SkipSponsorButton.java
│ │ │ │ │ └── SponsorBlockView.java
│ │ │ │ ├── ui
│ │ │ │ ├── AdButton.java
│ │ │ │ ├── ButtonVisibility.java
│ │ │ │ ├── CopyButton.java
│ │ │ │ ├── CopyWithTimestamp.java
│ │ │ │ ├── SBBrowserButton.java
│ │ │ │ ├── SBWhitelistButton.java
│ │ │ │ ├── SlimButton.java
│ │ │ │ ├── SlimButtonContainer.java
│ │ │ │ ├── SponsorBlockVoting.java
│ │ │ │ └── Visibility.java
│ │ │ │ └── whitelisting
│ │ │ │ ├── Whitelist.java
│ │ │ │ ├── WhitelistType.java
│ │ │ │ └── requests
│ │ │ │ ├── WhitelistRequester.java
│ │ │ │ └── WhitelistRoutes.java
│ │ │ └── utils
│ │ │ ├── ObjectSerializer.java
│ │ │ ├── SharedPrefUtils.java
│ │ │ ├── VancedUtils.java
│ │ │ └── requests
│ │ │ ├── Requester.java
│ │ │ └── Route.java
│ └── pl
│ │ └── jakubweg
│ │ ├── InjectedPlugin.java
│ │ ├── NewSegmentHelperLayout.java
│ │ ├── PlayerController.java
│ │ ├── ShieldButton.java
│ │ ├── SkipSegmentView.java
│ │ ├── SponsorBlockPreferenceFragment.java
│ │ ├── SponsorBlockSettings.java
│ │ ├── SponsorBlockUtils.java
│ │ ├── StringRef.java
│ │ ├── VotingButton.java
│ │ ├── objects
│ │ ├── EditTextListPreference.java
│ │ ├── SponsorSegment.java
│ │ └── UserStats.java
│ │ └── requests
│ │ ├── SBRequester.java
│ │ └── SBRoutes.java
│ └── res
│ ├── drawable-xxxhdpi
│ ├── quantum_ic_fast_forward_grey600_36.png
│ ├── quantum_ic_fast_forward_white_36.png
│ ├── quantum_ic_fast_rewind_grey600_36.png
│ └── quantum_ic_fast_rewind_white_36.png
│ ├── drawable
│ ├── ic_sb_adjust.xml
│ ├── ic_sb_compare.xml
│ ├── ic_sb_edit.xml
│ ├── ic_sb_logo.xml
│ ├── ic_sb_publish.xml
│ ├── ic_sb_voting.xml
│ ├── player_fast_forward.xml
│ └── player_fast_rewind.xml
│ ├── layout
│ ├── inline_sponsor_overlay.xml
│ ├── new_segment.xml
│ └── skip_sponsor_button.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ └── values
│ ├── arrays.xml
│ └── strings.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | /build
11 | /captures
12 | .externalNativeBuild
13 | .cxx
14 | /.idea
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SponsorBlock YouTube Vanced Implementation
2 | In order to use this in YouTube/Vanced you must first apply the smali mods applied to vanced (the patching process used for this is currently automated using our closed source tools with no plans to open source it for the time being) (if you mod vanced directly it is not required)
3 | * First make your edits in android studio
4 | * Change the string "replaceMeWithsetMillisecondMethod" on PlayerController.java to the method name of YouTube package
5 | * Compile debug apk
6 | * Decompile this apk using apktool https://github.com/iBotPeaches/Apktool
7 | * Take this decompiled folder and look for a folder labeled pl in one of your dex class folders
8 | * Decompile YouTube/Vanced using apktool (you only need to decompile the base apk files (for vanced you can get these using vanced manager and looking in android/data/com.vanced.manager for black or dark.apk), if you are decompiling stock youtube you must also merge a dpi split into it (todo))
9 | * Copy the pl folder from earlier into the dex class folder (remove any existing one completely first)
10 | * Recompile your modded YouTube/Vanced using apktool and sign it + all splits required for your device using the same key
11 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 32
5 |
6 | defaultConfig {
7 | applicationId "vanced.integrations"
8 | minSdkVersion 23
9 | targetSdkVersion 31
10 | versionCode 1
11 | versionName "1.0"
12 | multiDexEnabled false
13 |
14 | Properties properties = new Properties()
15 | if (rootProject.file("local.properties").exists()) {
16 | properties.load(rootProject.file("local.properties").newDataInputStream())
17 | }
18 |
19 | buildConfigField "String", "YT_API_KEY", "\"${properties.getProperty("youtubeAPIKey", "")}\""
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility JavaVersion.VERSION_1_8
30 | targetCompatibility JavaVersion.VERSION_1_8
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation 'androidx.annotation:annotation:1.3.0'
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/src/main/java/com/google/android/apps/youtube/app/YouTubeTikTokRoot_Application.java:
--------------------------------------------------------------------------------
1 | package com.google.android.apps.youtube.app;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.os.Bundle;
6 |
7 | public class YouTubeTikTokRoot_Application extends Application {
8 | protected void onCreate(final Bundle bundle) {
9 | super.onCreate();
10 | }
11 |
12 | public static Context getAppContext() {
13 | return null;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/google/android/apps/youtube/app/ui/SlimMetadataScrollableButtonContainerLayout.java:
--------------------------------------------------------------------------------
1 | package com.google.android.apps.youtube.app.ui;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.ViewGroup;
6 |
7 | public class SlimMetadataScrollableButtonContainerLayout extends ViewGroup {
8 |
9 | public SlimMetadataScrollableButtonContainerLayout(Context context) {
10 | super(context);
11 | }
12 |
13 | public SlimMetadataScrollableButtonContainerLayout(Context context, AttributeSet attrs) {
14 | super(context, attrs);
15 | }
16 |
17 | public SlimMetadataScrollableButtonContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
18 | super(context, attrs, defStyleAttr);
19 | }
20 |
21 | @Override
22 | protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/razerman/youtube/Helpers/XSwipeHelper.java:
--------------------------------------------------------------------------------
1 | package fi.razerman.youtube.Helpers;
2 |
3 | import android.view.ViewGroup;
4 |
5 | public class XSwipeHelper {
6 | // Implementation in another repo
7 | public static ViewGroup nextGenWatchLayout;
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/razerman/youtube/XGlobals.java:
--------------------------------------------------------------------------------
1 | package fi.razerman.youtube;
2 |
3 | // Ignore this file, the implementation is in another repository
4 | public class XGlobals {
5 | public static Boolean debug = false;
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/dialog/Dialogs.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.dialog;
2 |
3 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_ENABLED;
4 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_HINT_SHOWN;
5 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
6 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
7 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN;
8 | import static pl.jakubweg.StringRef.str;
9 |
10 | import android.app.Activity;
11 | import android.app.AlertDialog;
12 | import android.content.Context;
13 | import android.content.DialogInterface;
14 | import android.content.Intent;
15 | import android.graphics.LightingColorFilter;
16 | import android.net.Uri;
17 | import android.os.Build;
18 |
19 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
20 |
21 | import fi.vanced.utils.SharedPrefUtils;
22 | import fi.vanced.utils.VancedUtils;
23 | import pl.jakubweg.SponsorBlockSettings;
24 |
25 | public class Dialogs {
26 | // Inject call from YT to this
27 | public static void showDialogsAtStartup(Activity activity) {
28 | rydFirstRun(activity);
29 | sbFirstRun(activity);
30 | }
31 |
32 | private static void rydFirstRun(Activity activity) {
33 | Context context = YouTubeTikTokRoot_Application.getAppContext();
34 | boolean enabled = SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, false);
35 | boolean hintShown = SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, false);
36 |
37 | // If RYD is enabled or hint has been shown, exit
38 | if (enabled || hintShown) {
39 | // If RYD is enabled but hint hasn't been shown, mark it as shown
40 | if (enabled && !hintShown) {
41 | SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, true);
42 | }
43 | return;
44 | }
45 |
46 | AlertDialog.Builder builder;
47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
48 | builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
49 | } else {
50 | builder = new AlertDialog.Builder(activity);
51 | }
52 | builder.setTitle(str("vanced_ryd"));
53 | builder.setIcon(VancedUtils.getIdentifier("reel_dislike_icon", "drawable"));
54 | builder.setCancelable(false);
55 | builder.setMessage(str("vanced_ryd_firstrun"));
56 | builder.setPositiveButton(str("vanced_enable"),
57 | (dialog, id) -> {
58 | SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, true);
59 | SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, true);
60 | dialog.dismiss();
61 | });
62 |
63 | builder.setNegativeButton(str("vanced_disable"),
64 | (dialog, id) -> {
65 | SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN, true);
66 | SharedPrefUtils.saveBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, false);
67 | dialog.dismiss();
68 | });
69 |
70 | builder.setNeutralButton(str("vanced_learnmore"), null);
71 |
72 | AlertDialog dialog = builder.create();
73 | dialog.show();
74 |
75 | // Set black background
76 | dialog.getWindow().getDecorView().getBackground().setColorFilter(new LightingColorFilter(0xFF000000, VancedUtils.getIdentifier("ytBrandBackgroundSolid", "color")));
77 |
78 | // Set learn more action (set here so clicking it doesn't dismiss the dialog)
79 | dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
80 | Uri uri = Uri.parse("https://www.returnyoutubedislike.com/");
81 | Intent intent = new Intent(Intent.ACTION_VIEW, uri);
82 | activity.startActivity(intent);
83 | });
84 | }
85 |
86 | private static void sbFirstRun(Activity activity) {
87 | Context context = YouTubeTikTokRoot_Application.getAppContext();
88 | boolean enabled = SharedPrefUtils.getBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, false);
89 | boolean hintShown = SharedPrefUtils.getBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, false);
90 |
91 | // If SB is enabled or hint has been shown, exit
92 | if (enabled || hintShown) {
93 | // If SB is enabled but hint hasn't been shown, mark it as shown
94 | if (enabled && !hintShown) {
95 | SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, true);
96 | }
97 | return;
98 | }
99 |
100 | AlertDialog.Builder builder;
101 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
102 | builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
103 | } else {
104 | builder = new AlertDialog.Builder(activity);
105 | }
106 | builder.setTitle(str("vanced_sb"));
107 | builder.setIcon(VancedUtils.getIdentifier("ic_sb_logo", "drawable"));
108 | builder.setCancelable(false);
109 | builder.setMessage(str("vanced_sb_firstrun"));
110 | builder.setPositiveButton(str("vanced_enable"),
111 | (dialog, id) -> {
112 | SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, true);
113 | SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, true);
114 | dialog.dismiss();
115 | });
116 |
117 | builder.setNegativeButton(str("vanced_disable"),
118 | (dialog, id) -> {
119 | SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN, true);
120 | SharedPrefUtils.saveBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, false);
121 | dialog.dismiss();
122 | });
123 |
124 | builder.setNeutralButton(str("vanced_learnmore"), null);
125 |
126 | AlertDialog dialog = builder.create();
127 | dialog.show();
128 |
129 | // Set black background
130 | dialog.getWindow().getDecorView().getBackground().setColorFilter(new LightingColorFilter(0xFF000000, VancedUtils.getIdentifier("ytBrandBackgroundSolid", "color")));
131 |
132 | // Set learn more action (set here so clicking it doesn't dismiss the dialog)
133 | dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
134 | Uri uri = Uri.parse("https://sponsor.ajay.app/");
135 | Intent intent = new Intent(Intent.ACTION_VIEW, uri);
136 | activity.startActivity(intent);
137 | });
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/player/ChannelModel.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.player;
2 |
3 | import java.io.Serializable;
4 |
5 | public class ChannelModel implements Serializable {
6 | private String author;
7 | private String channelId;
8 |
9 | public ChannelModel(String author, String channelId) {
10 | this.author = author;
11 | this.channelId = channelId;
12 | }
13 |
14 | public String getAuthor() {
15 | return author;
16 | }
17 |
18 | public void setAuthor(String author) {
19 | this.author = author;
20 | }
21 |
22 | public String getChannelId() {
23 | return channelId;
24 | }
25 |
26 | public void setChannelId(String channelId) {
27 | this.channelId = channelId;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/player/PlayerType.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.player;
2 |
3 | import fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView;
4 | import pl.jakubweg.SponsorBlockUtils;
5 |
6 | public class PlayerType {
7 | public static void playerTypeChanged(String playerType) {
8 | SponsorBlockView.playerTypeChanged(playerType);
9 | SponsorBlockUtils.playerTypeChanged(playerType);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/player/VideoHelpers.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.player;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import android.widget.Toast;
6 |
7 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
8 |
9 | import static fi.razerman.youtube.XGlobals.debug;
10 | import static pl.jakubweg.StringRef.str;
11 |
12 | public class VideoHelpers {
13 | public static final String TAG = "VideoHelpers";
14 |
15 | public static void copyVideoUrlToClipboard() {
16 | generateVideoUrl(false);
17 | }
18 |
19 | public static void copyVideoUrlWithTimeStampToClipboard() {
20 | generateVideoUrl(true);
21 | }
22 |
23 | private static void generateVideoUrl(boolean appendTimeStamp) {
24 | try {
25 | String videoId = VideoInformation.currentVideoId;
26 | if (videoId == null || videoId.isEmpty()) {
27 | if (debug) {
28 | Log.d(TAG, "VideoId was empty");
29 | }
30 | return;
31 | }
32 |
33 | String videoUrl = String.format("https://youtu.be/%s", videoId);
34 | if (appendTimeStamp) {
35 | long videoTime = VideoInformation.lastKnownVideoTime;
36 | videoUrl += String.format("?t=%s", (videoTime / 1000));
37 | }
38 |
39 | if (debug) {
40 | Log.d(TAG, "Video URL: " + videoUrl);
41 | }
42 |
43 | setClipboard(YouTubeTikTokRoot_Application.getAppContext(), videoUrl);
44 |
45 | Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), str("share_copy_url_success"), Toast.LENGTH_SHORT).show();
46 | }
47 | catch (Exception ex) {
48 | Log.e(TAG, "Couldn't generate video url", ex);
49 | }
50 | }
51 |
52 | private static void setClipboard(Context context, String text) {
53 | if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
54 | android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
55 | clipboard.setText(text);
56 | } else {
57 | android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
58 | android.content.ClipData clip = android.content.ClipData.newPlainText("link", text);
59 | clipboard.setPrimaryClip(clip);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/player/VideoInformation.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.player;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 |
5 | import android.util.Log;
6 |
7 | import fi.vanced.libraries.youtube.ryd.ReturnYouTubeDislikes;
8 |
9 | public class VideoInformation {
10 | private static final String TAG = "VI - VideoInfo";
11 |
12 | public static String currentVideoId;
13 | public static Integer dislikeCount;
14 | public static String channelName;
15 | public static long lastKnownVideoTime = -1L;
16 |
17 | private static boolean tempInfoSaved = false;
18 | private static String tempVideoId;
19 | private static Integer tempDislikeCount;
20 |
21 | // Call hook in the YT code when the video changes
22 | public static void setCurrentVideoId(final String videoId) {
23 | if (videoId == null) {
24 | if (debug) {
25 | Log.d(TAG, "setCurrentVideoId - new id was null - currentVideoId was" + currentVideoId);
26 | }
27 | clearInformation(true);
28 | return;
29 | }
30 |
31 | // Restore temporary information that was stored from the last watched video
32 | if (tempInfoSaved) {
33 | restoreTempInformation();
34 | }
35 |
36 | if (videoId.equals(currentVideoId)) {
37 | if (debug) {
38 | Log.d(TAG, "setCurrentVideoId - new and current video were equal - " + videoId);
39 | }
40 | return;
41 | }
42 |
43 | if (debug) {
44 | Log.d(TAG, "setCurrentVideoId - video id updated from " + currentVideoId + " to " + videoId);
45 | }
46 |
47 | currentVideoId = videoId;
48 |
49 | // New video
50 | ReturnYouTubeDislikes.newVideoLoaded(videoId);
51 | }
52 |
53 | // Call hook in the YT code when the video ends
54 | public static void videoEnded() {
55 | saveTempInformation();
56 | clearInformation(false);
57 | }
58 |
59 | // Information is cleared once a video ends
60 | // It's cleared because the setCurrentVideoId isn't called for Shorts
61 | // so Shorts would otherwise use the information from the last watched video
62 | private static void clearInformation(boolean full) {
63 | if (full) {
64 | currentVideoId = null;
65 | dislikeCount = null;
66 | }
67 | channelName = null;
68 | }
69 |
70 | // Temporary information is saved once a video ends
71 | // so that if the user watches the same video again,
72 | // the information can be restored without having to fetch again
73 | private static void saveTempInformation() {
74 | tempVideoId = currentVideoId;
75 | tempDislikeCount = dislikeCount;
76 | tempInfoSaved = true;
77 | }
78 |
79 | private static void restoreTempInformation() {
80 | currentVideoId = tempVideoId;
81 | dislikeCount = tempDislikeCount;
82 | tempVideoId = null;
83 | tempDislikeCount = null;
84 | tempInfoSaved = false;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/RYDFragment.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_ENABLED;
5 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_HINT_SHOWN;
6 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
7 | import static pl.jakubweg.StringRef.str;
8 |
9 | import android.app.Activity;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.net.Uri;
13 | import android.os.Bundle;
14 | import android.preference.Preference;
15 | import android.preference.PreferenceCategory;
16 | import android.preference.PreferenceFragment;
17 | import android.preference.PreferenceScreen;
18 | import android.preference.SwitchPreference;
19 |
20 | import fi.vanced.utils.SharedPrefUtils;
21 |
22 | public class RYDFragment extends PreferenceFragment {
23 | @Override
24 | public void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | getPreferenceManager().setSharedPreferencesName(PREFERENCES_NAME);
27 |
28 | final Activity context = this.getActivity();
29 |
30 | PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
31 | setPreferenceScreen(preferenceScreen);
32 |
33 | // RYD enable toggle
34 | {
35 | SwitchPreference preference = new SwitchPreference(context);
36 | preferenceScreen.addPreference(preference);
37 | preference.setKey(PREFERENCES_KEY_RYD_ENABLED);
38 | preference.setDefaultValue(false);
39 | preference.setChecked(SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED));
40 | preference.setTitle(str("vanced_ryd_title"));
41 | preference.setSummary(str("vanced_ryd_summary"));
42 | preference.setOnPreferenceChangeListener((pref, newValue) -> {
43 | final boolean value = (Boolean) newValue;
44 | ReturnYouTubeDislikes.onEnabledChange(value);
45 | return true;
46 | });
47 | }
48 |
49 | // Clear hint
50 | if (debug) {
51 | SwitchPreference preference = new SwitchPreference(context);
52 | preferenceScreen.addPreference(preference);
53 | preference.setKey(PREFERENCES_KEY_RYD_HINT_SHOWN);
54 | preference.setDefaultValue(false);
55 | preference.setChecked(SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_RYD_HINT_SHOWN));
56 | preference.setTitle("Hint debug");
57 | preference.setSummary("Debug toggle for clearing the hint shown preference");
58 | preference.setOnPreferenceChangeListener((pref, newValue) -> true);
59 | }
60 |
61 | // About category
62 | addAboutCategory(context, preferenceScreen);
63 | }
64 |
65 | private void addAboutCategory(Context context, PreferenceScreen screen) {
66 | PreferenceCategory category = new PreferenceCategory(context);
67 | screen.addPreference(category);
68 | category.setTitle(str("about"));
69 |
70 | {
71 | Preference preference = new Preference(context);
72 | screen.addPreference(preference);
73 | preference.setTitle(str("vanced_ryd_attribution_title"));
74 | preference.setSummary(str("vanced_ryd_attribution_summary"));
75 | preference.setOnPreferenceClickListener(pref -> {
76 | Intent i = new Intent(Intent.ACTION_VIEW);
77 | i.setData(Uri.parse("https://returnyoutubedislike.com"));
78 | pref.getContext().startActivity(i);
79 | return false;
80 | });
81 | }
82 |
83 | {
84 | Preference preference = new Preference(context);
85 | screen.addPreference(preference);
86 | preference.setTitle("GitHub");
87 | preference.setOnPreferenceClickListener(pref -> {
88 | Intent i = new Intent(Intent.ACTION_VIEW);
89 | i.setData(Uri.parse("https://github.com/Anarios/return-youtube-dislike"));
90 | pref.getContext().startActivity(i);
91 | return false;
92 | });
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/RYDSettings.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd;
2 |
3 | public class RYDSettings {
4 | public static final String PREFERENCES_NAME = "ryd";
5 | public static final String PREFERENCES_KEY_USERID = "userId";
6 | public static final String PREFERENCES_KEY_RYD_ENABLED = "ryd-enabled";
7 | public static final String PREFERENCES_KEY_RYD_HINT_SHOWN = "ryd_hint_shown";
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/Registration.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_USERID;
5 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
6 | import static fi.vanced.utils.VancedUtils.getPreferences;
7 | import static fi.vanced.utils.VancedUtils.randomString;
8 |
9 | import android.content.Context;
10 | import android.content.SharedPreferences;
11 | import android.util.Log;
12 |
13 | import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
14 |
15 | public class Registration {
16 | private static final String TAG = "VI - RYD - Registration";
17 |
18 | private String userId;
19 | private Context context;
20 |
21 | public Registration(Context context) {
22 | this.context = context;
23 | }
24 |
25 | public String getUserId() {
26 | return userId != null ? userId : fetchUserId();
27 | }
28 |
29 | private String fetchUserId() {
30 | try {
31 | if (this.context == null) throw new Exception("Unable to fetch userId because context was null");
32 |
33 | SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
34 | this.userId = preferences.getString(PREFERENCES_KEY_USERID, null);
35 |
36 | if (this.userId == null) {
37 | this.userId = register();
38 | }
39 | }
40 | catch (Exception ex) {
41 | Log.e(TAG, "Unable to fetch the userId from shared preferences", ex);
42 | }
43 |
44 | return this.userId;
45 | }
46 |
47 | public void saveUserId(String userId) {
48 | try {
49 | if (this.context == null) throw new Exception("Unable to save userId because context was null");
50 |
51 | SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
52 | SharedPreferences.Editor editor = preferences.edit();
53 | editor.putString(PREFERENCES_KEY_USERID, userId).apply();
54 | }
55 | catch (Exception ex) {
56 | Log.e(TAG, "Unable to save the userId in shared preferences", ex);
57 | }
58 | }
59 |
60 | private String register() {
61 | String userId = randomString(36);
62 | if (debug) {
63 | Log.d(TAG, "Trying to register the following userId: " + userId);
64 | }
65 | return RYDRequester.register(userId, this);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/ReturnYouTubeDislikes.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
5 | import static fi.vanced.libraries.youtube.player.VideoInformation.dislikeCount;
6 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_KEY_RYD_ENABLED;
7 | import static fi.vanced.libraries.youtube.ryd.RYDSettings.PREFERENCES_NAME;
8 | import static fi.vanced.utils.VancedUtils.getIdentifier;
9 |
10 | import android.content.Context;
11 | import android.icu.text.CompactDecimalFormat;
12 | import android.os.Build;
13 | import android.util.Log;
14 | import android.view.View;
15 | import android.widget.TextView;
16 |
17 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
18 |
19 | import java.util.Locale;
20 | import java.util.Objects;
21 |
22 | import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
23 | import fi.vanced.utils.SharedPrefUtils;
24 |
25 | public class ReturnYouTubeDislikes {
26 | public static boolean isEnabled;
27 | public static final String TAG = "VI - RYD";
28 | private static View _dislikeView = null;
29 | private static Thread _dislikeFetchThread = null;
30 | private static Thread _votingThread = null;
31 | private static Registration registration;
32 | private static Voting voting;
33 | private static boolean likeActive;
34 | private static boolean dislikeActive;
35 | private static int votingValue = 0; // 1 = like, -1 = dislike, 0 = no vote
36 | private static CompactDecimalFormat compactNumberFormatter;
37 |
38 | static {
39 | Context context = YouTubeTikTokRoot_Application.getAppContext();
40 | isEnabled = SharedPrefUtils.getBoolean(Objects.requireNonNull(context), PREFERENCES_NAME, PREFERENCES_KEY_RYD_ENABLED, false);
41 | if (isEnabled) {
42 | registration = new Registration(context);
43 | voting = new Voting(context, registration);
44 | }
45 |
46 | Locale locale = context.getResources().getConfiguration().locale;
47 | if (debug) {
48 | Log.d(TAG, "locale - " + locale);
49 | }
50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
51 | compactNumberFormatter = CompactDecimalFormat.getInstance(
52 | locale,
53 | CompactDecimalFormat.CompactStyle.SHORT
54 | );
55 | }
56 | }
57 |
58 | public static void onEnabledChange(boolean enabled) {
59 | isEnabled = enabled;
60 | if (registration == null) {
61 | registration = new Registration(YouTubeTikTokRoot_Application.getAppContext());
62 | }
63 | if (voting == null) {
64 | voting = new Voting(YouTubeTikTokRoot_Application.getAppContext(), registration);
65 | }
66 | }
67 |
68 | public static void newVideoLoaded(String videoId) {
69 | if (debug) {
70 | Log.d(TAG, "newVideoLoaded - " + videoId);
71 | }
72 |
73 | dislikeCount = null;
74 | if (!isEnabled) return;
75 |
76 | try {
77 | if (_dislikeFetchThread != null && _dislikeFetchThread.getState() != Thread.State.TERMINATED) {
78 | if (debug) {
79 | Log.d(TAG, "Interrupting the thread. Current state " + _dislikeFetchThread.getState());
80 | }
81 | _dislikeFetchThread.interrupt();
82 | }
83 | }
84 | catch (Exception ex) {
85 | Log.e(TAG, "Error in the dislike fetch thread", ex);
86 | }
87 |
88 | _dislikeFetchThread = new Thread(() -> RYDRequester.fetchDislikes(videoId));
89 | _dislikeFetchThread.start();
90 | }
91 |
92 | // Call to this needs to be injected in YT code
93 | public static void setLikeTag(View view) {
94 | if (!isEnabled) return;
95 |
96 | setTag(view, "like");
97 | }
98 |
99 | public static void setLikeTag(View view, boolean active) {
100 | if (!isEnabled) return;
101 |
102 | likeActive = active;
103 | if (likeActive) {
104 | votingValue = 1;
105 | }
106 | if (debug) {
107 | Log.d(TAG, "Like tag active " + likeActive);
108 | }
109 | setTag(view, "like");
110 | }
111 |
112 | // Call to this needs to be injected in YT code
113 | public static void setDislikeTag(View view) {
114 | if (!isEnabled) return;
115 |
116 | _dislikeView = view;
117 | setTag(view, "dislike");
118 | }
119 |
120 | public static void setDislikeTag(View view, boolean active) {
121 | if (!isEnabled) return;
122 |
123 | dislikeActive = active;
124 | if (dislikeActive) {
125 | votingValue = -1;
126 | }
127 | _dislikeView = view;
128 | if (debug) {
129 | Log.d(TAG, "Dislike tag active " + dislikeActive);
130 | }
131 | setTag(view, "dislike");
132 | }
133 |
134 | // Call to this needs to be injected in YT code
135 | public static CharSequence onSetText(View view, CharSequence originalText) {
136 | if (!isEnabled) return originalText;
137 | return handleOnSetText(view, originalText);
138 | }
139 |
140 | // Call to this needs to be injected in YT code
141 | public static void onClick(View view, boolean inactive) {
142 | if (!isEnabled) return;
143 |
144 | handleOnClick(view, inactive);
145 | }
146 |
147 | private static CharSequence handleOnSetText(View view, CharSequence originalText) {
148 | if (!isEnabled) return originalText;
149 |
150 | try {
151 | CharSequence tag = (CharSequence) view.getTag();
152 | if (debug) {
153 | Log.d(TAG, "handleOnSetText - " + tag + " - original text - " + originalText);
154 | }
155 | if (tag == null) return originalText;
156 |
157 | if (tag == "like") {
158 | return originalText;
159 | }
160 | else if (tag == "dislike") {
161 | return dislikeCount != null ? formatDislikes(dislikeCount) : originalText;
162 | }
163 | }
164 | catch (Exception ex) {
165 | Log.e(TAG, "Error while handling the setText", ex);
166 | }
167 |
168 | return originalText;
169 | }
170 |
171 | public static void trySetDislikes(String dislikeCount) {
172 | if (!isEnabled) return;
173 |
174 | try {
175 | // Try to set normal video dislike count
176 | if (_dislikeView == null) {
177 | if (debug) { Log.d(TAG, "_dislikeView was null"); }
178 | return;
179 | }
180 |
181 | View buttonView = _dislikeView.findViewById(getIdentifier("button_text", "id"));
182 | if (buttonView == null) {
183 | if (debug) { Log.d(TAG, "buttonView was null"); }
184 | return;
185 | }
186 | TextView button = (TextView) buttonView;
187 | button.setText(dislikeCount);
188 | if (debug) {
189 | Log.d(TAG, "trySetDislikes - " + dislikeCount);
190 | }
191 | }
192 | catch (Exception ex) {
193 | if (debug) {
194 | Log.e(TAG, "Error while trying to set dislikes text", ex);
195 | }
196 | }
197 | }
198 |
199 | private static void handleOnClick(View view, boolean previousState) {
200 | Context context = YouTubeTikTokRoot_Application.getAppContext();
201 | if (!isEnabled || SharedPrefUtils.getBoolean(Objects.requireNonNull(context),"youtube","user_signed_out",true)) return;
202 |
203 | try {
204 | String tag = (String) view.getTag();
205 | if (debug) {
206 | Log.d(TAG, "handleOnClick - " + tag + " - previousState - " + previousState);
207 | }
208 | if (tag == null) return;
209 |
210 | // If active status was removed, vote should be none
211 | if (previousState) { votingValue = 0; }
212 | if (tag.equals("like")) {
213 |
214 | // Like was activated
215 | if (!previousState) { votingValue = 1; likeActive = true; }
216 | else { likeActive = false; }
217 |
218 | // Like was activated and dislike was previously activated
219 | if (!previousState && dislikeActive) { dislikeCount--; trySetDislikes(formatDislikes(dislikeCount)); }
220 | dislikeActive = false;
221 | }
222 | else if (tag.equals("dislike")) {
223 | likeActive = false;
224 |
225 | // Dislike was activated
226 | if (!previousState) { votingValue = -1; dislikeActive = true; dislikeCount++; }
227 | // Dislike was removed
228 | else { dislikeActive = false; dislikeCount--; }
229 | trySetDislikes(formatDislikes(dislikeCount));
230 | }
231 | else {
232 | // Unknown tag
233 | return;
234 | }
235 |
236 | if (debug) {
237 | Log.d(TAG, "New vote status - " + votingValue);
238 | Log.d(TAG, "Like button " + likeActive + " | Dislike button " + dislikeActive);
239 | }
240 |
241 | sendVote(votingValue);
242 | }
243 | catch (Exception ex) {
244 | Log.e(TAG, "Error while handling the onClick", ex);
245 | }
246 | }
247 |
248 | private static void sendVote(int vote) {
249 | if (!isEnabled) return;
250 |
251 | if (debug) {
252 | Log.d(TAG, "sending vote - " + vote + " for video " + currentVideoId);
253 | }
254 |
255 | try {
256 | if (_votingThread != null && _votingThread.getState() != Thread.State.TERMINATED) {
257 | if (debug) {
258 | Log.d(TAG, "Interrupting the thread. Current state " + _votingThread.getState());
259 | }
260 | _votingThread.interrupt();
261 | }
262 | }
263 | catch (Exception ex) {
264 | Log.e(TAG, "Error in the voting thread", ex);
265 | }
266 |
267 | _votingThread = new Thread(() -> {
268 | try {
269 | boolean result = voting.sendVote(currentVideoId, vote);
270 | if (debug) {
271 | Log.d(TAG, "sendVote status " + result);
272 | }
273 | }
274 | catch (Exception ex) {
275 | Log.e(TAG, "Failed to send vote", ex);
276 | }
277 | });
278 | _votingThread.start();
279 | }
280 |
281 | private static void setTag(View view, String tag) {
282 | if (!isEnabled) return;
283 |
284 | try {
285 | if (view == null) {
286 | if (debug) {
287 | Log.d(TAG, "View was empty");
288 | }
289 | return;
290 | }
291 |
292 | if (debug) {
293 | Log.d(TAG, "setTag - " + tag);
294 | }
295 |
296 | view.setTag(tag);
297 | }
298 | catch (Exception ex) {
299 | Log.e(TAG, "Error while trying to set tag to view", ex);
300 | }
301 | }
302 |
303 | public static String formatDislikes(int dislikes) {
304 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && compactNumberFormatter != null) {
305 | final String formatted = compactNumberFormatter.format(dislikes);
306 | if (debug) {
307 | Log.d(TAG, "Formatting dislikes - " + dislikes + " - " + formatted);
308 | }
309 |
310 | return formatted;
311 | }
312 |
313 | if (debug) {
314 | Log.d(TAG, "Couldn't format dislikes, using the unformatted count - " + dislikes);
315 | }
316 | return String.valueOf(dislikes);
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/Utils.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd;
2 |
3 | import android.util.Base64;
4 | import android.util.Log;
5 |
6 | import java.security.MessageDigest;
7 |
8 | public class Utils {
9 | private static final String TAG = "VI - RYD - Utils";
10 |
11 | public static String solvePuzzle(String challenge, int difficulty) {
12 | byte[] decodedChallenge = Base64.decode(challenge, Base64.NO_WRAP);
13 |
14 | byte[] buffer = new byte[20];
15 | for (int i = 4; i < 20; i++) {
16 | buffer[i] = decodedChallenge[i - 4];
17 | }
18 |
19 | try {
20 | int maxCount = (int) (Math.pow(2, difficulty + 1) * 5);
21 | MessageDigest md = MessageDigest.getInstance("SHA-512");
22 | for (int i = 0; i < maxCount; i++) {
23 | buffer[0] = (byte)i;
24 | buffer[1] = (byte)(i >> 8);
25 | buffer[2] = (byte)(i >> 16);
26 | buffer[3] = (byte)(i >> 24);
27 | byte[] messageDigest = md.digest(buffer);
28 |
29 | if (countLeadingZeroes(messageDigest) >= difficulty) {
30 | String encode = Base64.encodeToString(new byte[]{buffer[0], buffer[1], buffer[2], buffer[3]}, Base64.NO_WRAP);
31 | return encode;
32 | }
33 | }
34 | }
35 | catch (Exception ex) {
36 | Log.e(TAG, "Failed to solve puzzle", ex);
37 | }
38 |
39 | return null;
40 | }
41 |
42 | static int countLeadingZeroes(byte[] uInt8View) {
43 | int zeroes = 0;
44 | int value = 0;
45 | for (int i = 0; i < uInt8View.length; i++) {
46 | value = uInt8View[i] & 0xFF;
47 | if (value == 0) {
48 | zeroes += 8;
49 | } else {
50 | int count = 1;
51 | if (value >>> 4 == 0) {
52 | count += 4;
53 | value <<= 4;
54 | }
55 | if (value >>> 6 == 0) {
56 | count += 2;
57 | value <<= 2;
58 | }
59 | zeroes += count - (value >>> 7);
60 | break;
61 | }
62 | }
63 | return zeroes;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/Voting.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 |
5 | import android.content.Context;
6 | import android.util.Log;
7 |
8 | import fi.vanced.libraries.youtube.ryd.requests.RYDRequester;
9 |
10 | public class Voting {
11 | private static final String TAG = "VI - RYD - Voting";
12 |
13 | private Registration registration;
14 | private Context context;
15 |
16 | public Voting(Context context, Registration registration) {
17 | this.context = context;
18 | this.registration = registration;
19 | }
20 |
21 | public boolean sendVote(String videoId, int vote) {
22 | String userId = registration.getUserId();
23 | if (debug) {
24 | Log.d(TAG, "Trying to vote the following video: " + videoId + " with vote " + vote + " and userId: " + userId);
25 | }
26 | return RYDRequester.sendVote(videoId, userId, vote);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/requests/RYDRequester.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd.requests;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.player.VideoInformation.dislikeCount;
5 | import static fi.vanced.libraries.youtube.ryd.ReturnYouTubeDislikes.TAG;
6 | import static fi.vanced.utils.requests.Requester.parseJson;
7 |
8 | import android.os.Handler;
9 | import android.os.Looper;
10 | import android.util.Log;
11 |
12 | import org.json.JSONObject;
13 |
14 | import java.io.IOException;
15 | import java.io.OutputStream;
16 | import java.net.HttpURLConnection;
17 | import java.nio.charset.StandardCharsets;
18 |
19 | import fi.vanced.libraries.youtube.ryd.Registration;
20 | import fi.vanced.libraries.youtube.ryd.ReturnYouTubeDislikes;
21 | import fi.vanced.libraries.youtube.ryd.Utils;
22 | import fi.vanced.utils.requests.Requester;
23 | import fi.vanced.utils.requests.Route;
24 |
25 | public class RYDRequester {
26 | private static final String RYD_API_URL = "https://returnyoutubedislikeapi.com/";
27 |
28 | private RYDRequester() {}
29 |
30 | public static void fetchDislikes(String videoId) {
31 | try {
32 | if (debug) {
33 | Log.d(TAG, "Fetching dislikes for " + videoId);
34 | }
35 | HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.GET_DISLIKES, videoId);
36 | connection.setConnectTimeout(5 * 1000);
37 | if (connection.getResponseCode() == 200) {
38 | JSONObject json = getJSONObject(connection);
39 | int dislikes = json.getInt("dislikes");
40 | dislikeCount = dislikes;
41 | if (debug) {
42 | Log.d(TAG, "dislikes fetched - " + dislikeCount);
43 | }
44 |
45 | // Set the dislikes
46 | new Handler(Looper.getMainLooper()).post(() -> ReturnYouTubeDislikes.trySetDislikes(ReturnYouTubeDislikes.formatDislikes(dislikes)));
47 | }
48 | else if (debug) {
49 | Log.d(TAG, "dislikes fetch response was " + connection.getResponseCode());
50 | }
51 | connection.disconnect();
52 | }
53 | catch (Exception ex) {
54 | dislikeCount = null;
55 | Log.e(TAG, "Failed to fetch dislikes", ex);
56 | }
57 | }
58 |
59 | public static String register(String userId, Registration registration) {
60 | try {
61 | HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.GET_REGISTRATION, userId);
62 | connection.setConnectTimeout(5 * 1000);
63 | if (connection.getResponseCode() == 200) {
64 | JSONObject json = getJSONObject(connection);
65 | String challenge = json.getString("challenge");
66 | int difficulty = json.getInt("difficulty");
67 | if (debug) {
68 | Log.d(TAG, "Registration challenge - " + challenge + " with difficulty of " + difficulty);
69 | }
70 |
71 | // Solve the puzzle
72 | String solution = Utils.solvePuzzle(challenge, difficulty);
73 | if (debug) {
74 | Log.d(TAG, "Registration confirmation solution is " + solution);
75 | }
76 |
77 | return confirmRegistration(userId, solution, registration);
78 | }
79 | else if (debug) {
80 | Log.d(TAG, "Registration response was " + connection.getResponseCode());
81 | }
82 | connection.disconnect();
83 | }
84 | catch (Exception ex){
85 | Log.e(TAG, "Failed to register userId", ex);
86 | }
87 | return null;
88 | }
89 |
90 | private static String confirmRegistration(String userId, String solution, Registration registration) {
91 | try {
92 | if (debug) {
93 | Log.d(TAG, "Trying to confirm registration for the following userId: " + userId + " with solution: " + solution);
94 | }
95 |
96 | HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.CONFIRM_REGISTRATION, userId);
97 | applyCommonRequestSettings(connection);
98 |
99 | String jsonInputString = "{\"solution\": \"" + solution + "\"}";
100 | try(OutputStream os = connection.getOutputStream()) {
101 | byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
102 | os.write(input, 0, input.length);
103 | }
104 | if (connection.getResponseCode() == 200) {
105 | String result = parseJson(connection);
106 | if (debug) {
107 | Log.d(TAG, "Registration confirmation result was " + result);
108 | }
109 |
110 | if (result.equalsIgnoreCase("true")) {
111 | registration.saveUserId(userId);
112 | if (debug) {
113 | Log.d(TAG, "Registration was successful for user " + userId);
114 | }
115 |
116 | return userId;
117 | }
118 | }
119 | else if (debug) {
120 | Log.d(TAG, "Registration confirmation response was " + connection.getResponseCode());
121 | }
122 | connection.disconnect();
123 | }
124 | catch (Exception ex) {
125 | Log.e(TAG, "Failed to confirm registration", ex);
126 | }
127 |
128 | return null;
129 | }
130 |
131 | public static boolean sendVote(String videoId, String userId, int vote) {
132 | try {
133 | HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.SEND_VOTE);
134 | applyCommonRequestSettings(connection);
135 |
136 | String voteJsonString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote + "\"}";
137 | try(OutputStream os = connection.getOutputStream()) {
138 | byte[] input = voteJsonString.getBytes(StandardCharsets.UTF_8);
139 | os.write(input, 0, input.length);
140 | }
141 |
142 | if (connection.getResponseCode() == 200) {
143 | JSONObject json = getJSONObject(connection);
144 | String challenge = json.getString("challenge");
145 | int difficulty = json.getInt("difficulty");
146 | if (debug) {
147 | Log.d(TAG, "Vote challenge - " + challenge + " with difficulty of " + difficulty);
148 | }
149 |
150 | // Solve the puzzle
151 | String solution = Utils.solvePuzzle(challenge, difficulty);
152 | if (debug) {
153 | Log.d(TAG, "Vote confirmation solution is " + solution);
154 | }
155 |
156 | // Confirm vote
157 | return confirmVote(videoId, userId, solution);
158 | }
159 | else if (debug) {
160 | Log.d(TAG, "Vote response was " + connection.getResponseCode());
161 | }
162 | connection.disconnect();
163 | }
164 | catch (Exception ex) {
165 | Log.e(TAG, "Failed to send vote", ex);
166 | }
167 | return false;
168 | }
169 |
170 | private static boolean confirmVote(String videoId, String userId, String solution) {
171 | try {
172 | HttpURLConnection connection = getConnectionFromRoute(RYDRoutes.CONFIRM_VOTE);
173 | applyCommonRequestSettings(connection);
174 |
175 | String jsonInputString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}";
176 | try(OutputStream os = connection.getOutputStream()) {
177 | byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
178 | os.write(input, 0, input.length);
179 | }
180 | if (connection.getResponseCode() == 200) {
181 | String result = parseJson(connection);
182 | if (debug) {
183 | Log.d(TAG, "Vote confirmation result was " + result);
184 | }
185 |
186 | if (result.equalsIgnoreCase("true")) {
187 | if (debug) {
188 | Log.d(TAG, "Vote was successful for user " + userId);
189 | }
190 |
191 | return true;
192 | }
193 | }
194 | else if (debug) {
195 | Log.d(TAG, "Vote confirmation response was " + connection.getResponseCode());
196 | }
197 | connection.disconnect();
198 | }
199 | catch (Exception ex) {
200 | Log.e(TAG, "Failed to confirm vote", ex);
201 | }
202 | return false;
203 | }
204 |
205 | // utils
206 |
207 | private static void applyCommonRequestSettings(HttpURLConnection connection) throws Exception {
208 | connection.setRequestMethod("POST");
209 | connection.setRequestProperty("Content-Type", "application/json");
210 | connection.setRequestProperty("Accept", "application/json");
211 | connection.setDoOutput(true);
212 | connection.setConnectTimeout(5 * 1000);
213 | }
214 |
215 | // helpers
216 |
217 | private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
218 | return Requester.getConnectionFromRoute(RYD_API_URL, route, params);
219 | }
220 |
221 | private static JSONObject getJSONObject(HttpURLConnection connection) throws Exception {
222 | return Requester.getJSONObject(connection);
223 | }
224 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ryd/requests/RYDRoutes.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ryd.requests;
2 |
3 | import static fi.vanced.utils.requests.Route.Method.GET;
4 | import static fi.vanced.utils.requests.Route.Method.POST;
5 |
6 | import fi.vanced.utils.requests.Route;
7 |
8 | public class RYDRoutes {
9 | public static final Route SEND_VOTE = new Route(POST,"interact/vote");
10 | public static final Route CONFIRM_VOTE = new Route(POST,"interact/confirmVote");
11 | public static final Route GET_DISLIKES = new Route(GET, "votes?videoId={video_id}");
12 | public static final Route GET_REGISTRATION = new Route(GET, "puzzle/registration?userId={user_id}");
13 | public static final Route CONFIRM_REGISTRATION = new Route(POST,"puzzle/registration?userId={user_id}");
14 |
15 | private RYDRoutes() {}
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/NewSegmentLayout.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.sponsors.player.ui;
2 |
3 | import android.content.Context;
4 | import android.content.res.ColorStateList;
5 | import android.content.res.Resources;
6 | import android.graphics.drawable.RippleDrawable;
7 | import android.util.AttributeSet;
8 | import android.util.Log;
9 | import android.util.TypedValue;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.widget.FrameLayout;
13 | import android.widget.ImageButton;
14 | import android.widget.LinearLayout;
15 |
16 | import pl.jakubweg.NewSegmentHelperLayout;
17 | import pl.jakubweg.PlayerController;
18 | import pl.jakubweg.SponsorBlockSettings;
19 | import pl.jakubweg.SponsorBlockUtils;
20 |
21 | import static fi.razerman.youtube.XGlobals.debug;
22 |
23 | public class NewSegmentLayout extends FrameLayout {
24 | static String TAG = "NewSegmentLayout";
25 |
26 | private LinearLayout newSegmentContainer;
27 | public int defaultBottomMargin;
28 | public int ctaBottomMargin;
29 | public ImageButton rewindButton;
30 | public ImageButton forwardButton;
31 | public ImageButton adjustButton;
32 | public ImageButton compareButton;
33 | public ImageButton editButton;
34 | public ImageButton publishButton;
35 | private int rippleEffectId;
36 |
37 | public NewSegmentLayout(Context context) {
38 | super(context);
39 | this.initialize(context);
40 | }
41 |
42 | public NewSegmentLayout(Context context, AttributeSet attributeSet) {
43 | super(context, attributeSet);
44 | this.initialize(context);
45 | }
46 |
47 | public NewSegmentLayout(Context context, AttributeSet attributeSet, int defStyleAttr) {
48 | super(context, attributeSet, defStyleAttr);
49 | this.initialize(context);
50 | }
51 |
52 | public NewSegmentLayout(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
53 | super(context, attributeSet, defStyleAttr, defStyleRes);
54 | this.initialize(context);
55 | }
56 |
57 | private final void initialize(Context context) {
58 | LayoutInflater.from(context).inflate(getIdentifier(context, "new_segment", "layout"), this, true);
59 | Resources resources = context.getResources();
60 |
61 | TypedValue rippleEffect = new TypedValue();
62 | getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, rippleEffect, true);
63 | rippleEffectId = rippleEffect.resourceId;
64 |
65 | this.newSegmentContainer = (LinearLayout)this.findViewById(getIdentifier(context, "new_segment_container", "id"));
66 |
67 | this.rewindButton = (ImageButton)this.findViewById(getIdentifier(context, "new_segment_rewind", "id"));
68 | if (this.rewindButton != null) {
69 | setClickEffect(this.rewindButton);
70 | this.rewindButton.setOnClickListener(new View.OnClickListener() {
71 | @Override
72 | public void onClick(View v) {
73 | if (debug) { Log.d(TAG, "Rewind button clicked"); }
74 | PlayerController.skipRelativeMilliseconds(-SponsorBlockSettings.adjustNewSegmentMillis);
75 | }
76 | });
77 | }
78 | this.forwardButton = (ImageButton)this.findViewById(getIdentifier(context, "new_segment_forward", "id"));
79 | if (this.forwardButton != null) {
80 | setClickEffect(this.forwardButton);
81 | this.forwardButton.setOnClickListener(new View.OnClickListener() {
82 | @Override
83 | public void onClick(View v) {
84 | if (debug) { Log.d(TAG, "Forward button clicked"); }
85 | PlayerController.skipRelativeMilliseconds(SponsorBlockSettings.adjustNewSegmentMillis);
86 | }
87 | });
88 | }
89 | this.adjustButton = (ImageButton)this.findViewById(getIdentifier(context, "new_segment_adjust", "id"));
90 | if (this.adjustButton != null) {
91 | setClickEffect(this.adjustButton);
92 | this.adjustButton.setOnClickListener(new View.OnClickListener() {
93 | @Override
94 | public void onClick(View v) {
95 | if (debug) { Log.d(TAG, "Adjust button clicked"); }
96 | SponsorBlockUtils.onMarkLocationClicked(NewSegmentHelperLayout.context);
97 | }
98 | });
99 | }
100 | this.compareButton = (ImageButton)this.findViewById(getIdentifier(context, "new_segment_compare", "id"));
101 | if (this.compareButton != null) {
102 | setClickEffect(this.compareButton);
103 | this.compareButton.setOnClickListener(new View.OnClickListener() {
104 | @Override
105 | public void onClick(View v) {
106 | if (debug) { Log.d(TAG, "Compare button clicked"); }
107 | SponsorBlockUtils.onPreviewClicked(NewSegmentHelperLayout.context);
108 | }
109 | });
110 | }
111 | this.editButton = (ImageButton)this.findViewById(getIdentifier(context, "new_segment_edit", "id"));
112 | if (this.editButton != null) {
113 | setClickEffect(this.editButton);
114 | this.editButton.setOnClickListener(new View.OnClickListener() {
115 | @Override
116 | public void onClick(View v) {
117 | if (debug) { Log.d(TAG, "Edit button clicked"); }
118 | SponsorBlockUtils.onEditByHandClicked(NewSegmentHelperLayout.context);
119 | }
120 | });
121 | }
122 | this.publishButton = (ImageButton)this.findViewById(getIdentifier(context, "new_segment_publish", "id"));
123 | if (this.publishButton != null) {
124 | setClickEffect(this.publishButton);
125 | this.publishButton.setOnClickListener(new View.OnClickListener() {
126 | @Override
127 | public void onClick(View v) {
128 | if (debug) { Log.d(TAG, "Publish button clicked"); }
129 | SponsorBlockUtils.onPublishClicked(NewSegmentHelperLayout.context);
130 | }
131 | });
132 | }
133 |
134 | this.defaultBottomMargin = resources.getDimensionPixelSize(getIdentifier(context, "brand_interaction_default_bottom_margin", "dimen"));
135 | this.ctaBottomMargin = resources.getDimensionPixelSize(getIdentifier(context, "brand_interaction_cta_bottom_margin", "dimen"));
136 | }
137 |
138 | private void setClickEffect(ImageButton btn) {
139 | btn.setBackgroundResource(rippleEffectId);
140 |
141 | RippleDrawable rippleDrawable = (RippleDrawable)btn.getBackground();
142 |
143 | int[][] states = new int[][] { new int[] { android.R.attr.state_enabled } };
144 | int[] colors = new int[] { 0x33ffffff }; // sets the ripple color to white
145 |
146 | ColorStateList colorStateList = new ColorStateList(states, colors);
147 | rippleDrawable.setColor(colorStateList);
148 | }
149 |
150 | private int getIdentifier(Context context, String name, String defType) {
151 | return context.getResources().getIdentifier(name, defType, context.getPackageName());
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.sponsors.player.ui;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.graphics.Canvas;
6 | import android.graphics.Paint;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.os.Build;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.widget.FrameLayout;
14 | import android.widget.ImageView;
15 | import android.widget.LinearLayout;
16 | import android.widget.TextView;
17 |
18 | import pl.jakubweg.PlayerController;
19 |
20 | import static fi.razerman.youtube.XGlobals.debug;
21 |
22 | public class SkipSponsorButton extends FrameLayout {
23 | String TAG = "SkipSponsorButton";
24 | public CharSequence skipSponsorTextViewText;
25 | public CharSequence skipSponsorText;
26 | public ImageView skipSponsorButtonIcon;
27 | public TextView skipSponsorTextView;
28 | public int currentTextColor;
29 | public int invertedButtonForegroundColor;
30 | public int backgroundColor;
31 | public int invertedBackgroundColor;
32 | public ColorDrawable backgroundColorDrawable;
33 | public int defaultBottomMargin;
34 | public int ctaBottomMargin;
35 | private LinearLayout skipSponsorBtnContainer;
36 | private final Paint background;
37 | private final Paint border;
38 | private boolean highContrast = true;
39 |
40 | public SkipSponsorButton(Context context) {
41 | super(context);
42 | this.background = new Paint();
43 | this.border = new Paint();
44 | this.initialize(context);
45 | }
46 |
47 | public SkipSponsorButton(Context context, AttributeSet attributeSet) {
48 | super(context, attributeSet);
49 | this.background = new Paint();
50 | this.border = new Paint();
51 | this.initialize(context);
52 | }
53 |
54 | public SkipSponsorButton(Context context, AttributeSet attributeSet, int defStyleAttr) {
55 | super(context, attributeSet, defStyleAttr);
56 | this.background = new Paint();
57 | this.border = new Paint();
58 | this.initialize(context);
59 | }
60 |
61 | public SkipSponsorButton(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
62 | super(context, attributeSet, defStyleAttr, defStyleRes);
63 | this.background = new Paint();
64 | this.border = new Paint();
65 | this.initialize(context);
66 | }
67 |
68 | private final void initialize(Context context) {
69 | LayoutInflater.from(context).inflate(getIdentifier(context, "skip_sponsor_button", "layout"), this, true); // layout:skip_ad_button
70 | this.setMinimumHeight(this.getResources().getDimensionPixelSize(getIdentifier(context, "ad_skip_ad_button_min_height", "dimen"))); // dimen:ad_skip_ad_button_min_height
71 | this.skipSponsorBtnContainer = (LinearLayout)this.findViewById(getIdentifier(context, "skip_sponsor_button_container", "id")); // id:skip_ad_button_container
72 | this.skipSponsorButtonIcon = (ImageView)this.findViewById(getIdentifier(context, "skip_sponsor_button_icon", "id")); // id:skip_ad_button_icon
73 | this.backgroundColor = getColor(context, getIdentifier(context, "skip_ad_button_background_color", "color")); // color:skip_ad_button_background_color
74 | this.invertedBackgroundColor = getColor(context, getIdentifier(context, "skip_ad_button_inverted_background_color", "color")); // color:skip_ad_button_inverted_background_color
75 | this.background.setColor(this.backgroundColor);
76 | this.background.setStyle(Paint.Style.FILL);
77 | int borderColor = getColor(context, getIdentifier(context, "skip_ad_button_border_color", "color")); // color:skip_ad_button_border_color
78 | this.border.setColor(borderColor);
79 | float borderWidth = this.getResources().getDimension(getIdentifier(context, "ad_skip_ad_button_border_width", "dimen")); // dimen:ad_skip_ad_button_border_width
80 | this.border.setStrokeWidth(borderWidth);
81 | this.border.setStyle(Paint.Style.STROKE);
82 | TextView skipSponsorText = (TextView)this.findViewById(getIdentifier(context, "skip_sponsor_button_text", "id")); // id:skip_ad_button_text
83 | this.skipSponsorTextView = skipSponsorText;
84 | this.skipSponsorTextViewText = skipSponsorText.getText();
85 | this.currentTextColor = this.skipSponsorTextView.getCurrentTextColor();
86 | this.invertedButtonForegroundColor = getColor(context, getIdentifier(context, "skip_ad_button_inverted_foreground_color", "color")); // color:skip_ad_button_inverted_foreground_color
87 | this.backgroundColorDrawable = new ColorDrawable(this.backgroundColor);
88 | Resources resources = context.getResources();
89 | this.defaultBottomMargin = resources.getDimensionPixelSize(getIdentifier(context, "skip_button_default_bottom_margin", "dimen")); // dimen:skip_button_default_bottom_margin
90 | this.ctaBottomMargin = resources.getDimensionPixelSize(getIdentifier(context, "skip_button_cta_bottom_margin", "dimen")); // dimen:skip_button_cta_bottom_margin
91 | this.skipSponsorText = resources.getText(getIdentifier(context, "skip_sponsor", "string")); // string:skip_ads "Skip ads"
92 |
93 | this.skipSponsorBtnContainer.setOnClickListener(new View.OnClickListener() {
94 | @Override
95 | public void onClick(View v) {
96 | if (debug) {
97 | Log.d(TAG, "Skip button clicked");
98 | }
99 | PlayerController.onSkipSponsorClicked();
100 | }
101 | });
102 | }
103 |
104 | @Override // android.view.ViewGroup
105 | protected final void dispatchDraw(Canvas canvas) {
106 | int width = this.skipSponsorBtnContainer.getWidth();
107 | int height = this.skipSponsorBtnContainer.getHeight();
108 | int top = this.skipSponsorBtnContainer.getTop();
109 | int left = this.skipSponsorBtnContainer.getLeft();
110 | float floatLeft = (float)left;
111 | float floatTop = (float)top;
112 | float floatWidth = (float)(left + width);
113 | float floatHeight = (float)(top + height);
114 | canvas.drawRect(floatLeft, floatTop, floatWidth, floatHeight, this.background);
115 | if (!this.highContrast) {
116 | canvas.drawLines(new float[] { floatWidth, floatTop, floatLeft, floatTop, floatLeft, floatTop, floatLeft, floatHeight, floatLeft, floatHeight, floatWidth, floatHeight }, this.border);
117 | }
118 |
119 | super.dispatchDraw(canvas);
120 | }
121 |
122 |
123 | public static int getColor(Context context, int arg3) {
124 | return Build.VERSION.SDK_INT < 23 ? context.getResources().getColor(arg3) : context.getColor(arg3);
125 | }
126 |
127 | private int getIdentifier(Context context, String name, String defType) {
128 | return context.getResources().getIdentifier(name, defType, context.getPackageName());
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.sponsors.player.ui;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.RelativeLayout;
9 |
10 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
11 |
12 | import java.lang.ref.WeakReference;
13 |
14 | import fi.razerman.youtube.Helpers.XSwipeHelper;
15 |
16 | import static fi.razerman.youtube.XGlobals.debug;
17 |
18 | public class SponsorBlockView {
19 | static String TAG = "SponsorBlockView";
20 | static RelativeLayout inlineSponsorOverlay;
21 | static ViewGroup _youtubeOverlaysLayout;
22 | static WeakReference _skipSponsorButton = new WeakReference<>(null);
23 | static WeakReference _newSegmentLayout = new WeakReference<>(null);
24 | static boolean shouldShowOnPlayerType = true;
25 |
26 | public static void initialize(Object viewGroup) {
27 | try {
28 | if(debug){
29 | Log.d(TAG, "initializing");
30 | }
31 |
32 | _youtubeOverlaysLayout = (ViewGroup) viewGroup;
33 |
34 | addView();
35 | }
36 | catch (Exception ex) {
37 | Log.e(TAG, "Unable to set ViewGroup", ex);
38 | }
39 | }
40 |
41 | public static void showSkipButton() {
42 | skipSponsorButtonVisibility(true);
43 | }
44 | public static void hideSkipButton() {
45 | skipSponsorButtonVisibility(false);
46 | }
47 |
48 | public static void showNewSegmentLayout() {
49 | newSegmentLayoutVisibility(true);
50 | }
51 | public static void hideNewSegmentLayout() {
52 | newSegmentLayoutVisibility(false);
53 | }
54 |
55 | public static void playerTypeChanged(String playerType) {
56 | try {
57 | shouldShowOnPlayerType = playerType.equalsIgnoreCase("WATCH_WHILE_FULLSCREEN") || playerType.equalsIgnoreCase("WATCH_WHILE_MAXIMIZED");
58 |
59 | if (playerType.equalsIgnoreCase("WATCH_WHILE_FULLSCREEN")) {
60 | setSkipBtnMargins(true);
61 | setNewSegmentLayoutMargins(true);
62 | return;
63 | }
64 |
65 | setSkipBtnMargins(false);
66 | setNewSegmentLayoutMargins(false);
67 | }
68 | catch (Exception ex) {
69 | Log.e(TAG, "Player type changed caused a crash.", ex);
70 | }
71 | }
72 |
73 | private static void addView() {
74 | inlineSponsorOverlay = new RelativeLayout(YouTubeTikTokRoot_Application.getAppContext());
75 | setLayoutParams(inlineSponsorOverlay);
76 | LayoutInflater.from(YouTubeTikTokRoot_Application.getAppContext()).inflate(getIdentifier("inline_sponsor_overlay", "layout"), inlineSponsorOverlay);
77 |
78 | _youtubeOverlaysLayout.addView(inlineSponsorOverlay, _youtubeOverlaysLayout.getChildCount() - 2);
79 |
80 | SkipSponsorButton skipSponsorButton = (SkipSponsorButton) inlineSponsorOverlay.findViewById(getIdentifier("skip_sponsor_button", "id"));
81 | _skipSponsorButton = new WeakReference<>(skipSponsorButton);
82 |
83 | NewSegmentLayout newSegmentView = (NewSegmentLayout) inlineSponsorOverlay.findViewById(getIdentifier("new_segment_view", "id"));
84 | _newSegmentLayout = new WeakReference<>(newSegmentView);
85 | }
86 |
87 | private static void setLayoutParams(RelativeLayout relativeLayout) {
88 | relativeLayout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
89 | }
90 |
91 | private static void setSkipBtnMargins(boolean fullScreen) {
92 | SkipSponsorButton skipSponsorButton = _skipSponsorButton.get();
93 | if (skipSponsorButton == null) {
94 | Log.e(TAG, "Unable to setSkipBtnMargins");
95 | return;
96 | }
97 |
98 | RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) skipSponsorButton.getLayoutParams();
99 | if (params == null) {
100 | Log.e(TAG, "Unable to setSkipBtnMargins");
101 | return;
102 | }
103 | params.bottomMargin = fullScreen ? skipSponsorButton.ctaBottomMargin : skipSponsorButton.defaultBottomMargin;
104 | skipSponsorButton.setLayoutParams(params);
105 | }
106 |
107 | private static void skipSponsorButtonVisibility(boolean visible) {
108 | SkipSponsorButton skipSponsorButton = _skipSponsorButton.get();
109 | if (skipSponsorButton == null) {
110 | Log.e(TAG, "Unable to skipSponsorButtonVisibility");
111 | return;
112 | }
113 |
114 | visible &= shouldShowOnPlayerType;
115 |
116 | skipSponsorButton.setVisibility(visible ? View.VISIBLE : View.GONE);
117 | bringLayoutToFront();
118 | }
119 |
120 | private static void setNewSegmentLayoutMargins(boolean fullScreen) {
121 | NewSegmentLayout newSegmentLayout = _newSegmentLayout.get();
122 | if (newSegmentLayout == null) {
123 | Log.e(TAG, "Unable to setNewSegmentLayoutMargins");
124 | return;
125 | }
126 |
127 | RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) newSegmentLayout.getLayoutParams();
128 | if (params == null) {
129 | Log.e(TAG, "Unable to setNewSegmentLayoutMargins");
130 | return;
131 | }
132 | params.bottomMargin = fullScreen ? newSegmentLayout.ctaBottomMargin : newSegmentLayout.defaultBottomMargin;
133 | newSegmentLayout.setLayoutParams(params);
134 | }
135 |
136 | private static void newSegmentLayoutVisibility(boolean visible) {
137 | NewSegmentLayout newSegmentLayout = _newSegmentLayout.get();
138 | if (newSegmentLayout == null) {
139 | Log.e(TAG, "Unable to newSegmentLayoutVisibility");
140 | return;
141 | }
142 |
143 | visible &= shouldShowOnPlayerType;
144 |
145 | newSegmentLayout.setVisibility(visible ? View.VISIBLE : View.GONE);
146 | bringLayoutToFront();
147 | }
148 |
149 | private static void bringLayoutToFront() {
150 | checkLayout();
151 | inlineSponsorOverlay.bringToFront();
152 | inlineSponsorOverlay.requestLayout();
153 | inlineSponsorOverlay.invalidate();
154 | }
155 |
156 | private static void checkLayout() {
157 | if (inlineSponsorOverlay.getHeight() == 0) {
158 | View layout = XSwipeHelper.nextGenWatchLayout.findViewById(getIdentifier("player_overlays", "id"));
159 | if (layout != null) {
160 |
161 | initialize(layout);
162 |
163 | if (debug){
164 | Log.d("XGlobals", "player_overlays refreshed for SB");
165 | }
166 | }
167 | else if (debug){
168 | Log.d("XGlobals", "player_overlays was not found for SB");
169 | }
170 | }
171 | }
172 |
173 | private static int getIdentifier(String name, String defType) {
174 | Context context = YouTubeTikTokRoot_Application.getAppContext();
175 | return context.getResources().getIdentifier(name, defType, context.getPackageName());
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/AdButton.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
5 | import static pl.jakubweg.StringRef.str;
6 |
7 | import android.content.Context;
8 | import android.util.Log;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.ImageView;
12 |
13 | import fi.vanced.libraries.youtube.player.VideoInformation;
14 | import fi.vanced.libraries.youtube.whitelisting.Whitelist;
15 | import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
16 | import fi.vanced.libraries.youtube.whitelisting.requests.WhitelistRequester;
17 | import fi.vanced.utils.SharedPrefUtils;
18 | import fi.vanced.utils.VancedUtils;
19 |
20 | public class AdButton extends SlimButton {
21 | public static final String TAG = "VI - AdButton - Button";
22 |
23 | public AdButton(Context context, ViewGroup container) {
24 | super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID,
25 | SharedPrefUtils.getBoolean(context, WhitelistType.ADS.getSharedPreferencesName(), WhitelistType.ADS.getPreferenceEnabledName(), false));
26 |
27 | initialize();
28 | }
29 |
30 | private void initialize() {
31 | this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_ad_button", "drawable"));
32 | this.button_text.setText(str("action_ads"));
33 | changeEnabled(Whitelist.shouldShowAds());
34 | }
35 |
36 | public void changeEnabled(boolean enabled) {
37 | if (debug) {
38 | Log.d(TAG, "changeEnabled " + enabled);
39 | }
40 | this.button_icon.setEnabled(enabled);
41 | }
42 |
43 | @Override
44 | public void onClick(View view) {
45 | this.view.setEnabled(false);
46 | if (this.button_icon.isEnabled()) {
47 | removeFromWhitelist();
48 | return;
49 | }
50 | //this.button_icon.setEnabled(!this.button_icon.isEnabled());
51 |
52 | addToWhiteList(this.view, this.button_icon);
53 | }
54 |
55 | private void removeFromWhitelist() {
56 | try {
57 | Whitelist.removeFromWhitelist(WhitelistType.ADS, this.context, VideoInformation.channelName);
58 | changeEnabled(false);
59 | }
60 | catch (Exception ex) {
61 | Log.e(TAG, "Failed to remove from whitelist", ex);
62 | return;
63 | }
64 |
65 | this.view.setEnabled(true);
66 | }
67 |
68 | private void addToWhiteList(View view, ImageView buttonIcon) {
69 | new Thread(() -> {
70 | if (debug) {
71 | Log.d(TAG, "Fetching channelId for " + currentVideoId);
72 | }
73 | WhitelistRequester.addChannelToWhitelist(WhitelistType.ADS, view, buttonIcon, this.context);
74 | }).start();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/ButtonVisibility.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import android.content.Context;
4 |
5 | import fi.vanced.utils.SharedPrefUtils;
6 |
7 | public class ButtonVisibility {
8 | public static Visibility getButtonVisibility(Context context, String key) {
9 | return getButtonVisibility(context, key, "youtube");
10 | }
11 |
12 | public static Visibility getButtonVisibility(Context context, String key, String preferenceName) {
13 | String value = SharedPrefUtils.getString(context, preferenceName, key, null);
14 |
15 | if (value == null || value.isEmpty()) return Visibility.NONE;
16 |
17 | switch (value.toUpperCase()) {
18 | case "PLAYER": return Visibility.PLAYER;
19 | case "BUTTON_CONTAINER": return Visibility.BUTTON_CONTAINER;
20 | case "BOTH": return Visibility.BOTH;
21 | default: return Visibility.NONE;
22 | }
23 | }
24 |
25 | public static boolean isVisibleInContainer(Context context, String key) {
26 | return isVisibleInContainer(getButtonVisibility(context, key));
27 | }
28 |
29 | public static boolean isVisibleInContainer(Context context, String key, String preferenceName) {
30 | return isVisibleInContainer(getButtonVisibility(context, key, preferenceName));
31 | }
32 |
33 | public static boolean isVisibleInContainer(Visibility visibility) {
34 | return visibility == Visibility.BOTH || visibility == Visibility.BUTTON_CONTAINER;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/CopyButton.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import static pl.jakubweg.StringRef.str;
4 |
5 | import android.content.Context;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import fi.vanced.libraries.youtube.player.VideoHelpers;
10 | import fi.vanced.utils.VancedUtils;
11 |
12 | public class CopyButton extends SlimButton {
13 | public CopyButton(Context context, ViewGroup container) {
14 | super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_button_list"));
15 |
16 | initialize();
17 | }
18 |
19 | private void initialize() {
20 | this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_copy_icon", "drawable"));
21 | this.button_text.setText(str("action_copy"));
22 | }
23 |
24 | @Override
25 | public void onClick(View view) {
26 | VideoHelpers.copyVideoUrlToClipboard();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/CopyWithTimestamp.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 |
7 | import fi.vanced.libraries.youtube.player.VideoHelpers;
8 | import fi.vanced.utils.VancedUtils;
9 |
10 | import static pl.jakubweg.StringRef.str;
11 |
12 | public class CopyWithTimestamp extends SlimButton {
13 | public CopyWithTimestamp(Context context, ViewGroup container) {
14 | super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_timestamp_button_list"));
15 |
16 | initialize();
17 | }
18 |
19 | private void initialize() {
20 | this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_copy_icon_with_time", "drawable"));
21 | this.button_text.setText(str("action_tcopy"));
22 | }
23 |
24 | @Override
25 | public void onClick(View view) {
26 | VideoHelpers.copyVideoUrlWithTimeStampToClipboard();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/SBBrowserButton.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import static pl.jakubweg.StringRef.str;
4 |
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.net.Uri;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 |
11 | import fi.vanced.libraries.youtube.player.VideoInformation;
12 | import fi.vanced.utils.VancedUtils;
13 | import pl.jakubweg.SponsorBlockSettings;
14 | import pl.jakubweg.SponsorBlockUtils;
15 |
16 | public class SBBrowserButton extends SlimButton {
17 | private static final String BROWSER_URL = "https://sb.ltn.fi/video/";
18 |
19 | public SBBrowserButton(Context context, ViewGroup container) {
20 | super(context, container, SLIM_METADATA_BUTTON_ID,
21 | SponsorBlockUtils.isSBButtonEnabled(context, SponsorBlockSettings.PREFERENCES_KEY_BROWSER_BUTTON));
22 |
23 | initialize();
24 | }
25 |
26 | private void initialize() {
27 | this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_sb_browser", "drawable"));
28 | this.button_text.setText(str("action_browser"));
29 | }
30 |
31 | @Override
32 | public void onClick(View v) {
33 | Uri uri = Uri.parse(BROWSER_URL + VideoInformation.currentVideoId);
34 | Intent intent = new Intent(Intent.ACTION_VIEW, uri);
35 | context.startActivity(intent);
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/SBWhitelistButton.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
5 | import static pl.jakubweg.StringRef.str;
6 |
7 | import android.content.Context;
8 | import android.util.Log;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.ImageView;
12 |
13 | import fi.vanced.libraries.youtube.player.VideoInformation;
14 | import fi.vanced.libraries.youtube.whitelisting.Whitelist;
15 | import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
16 | import fi.vanced.libraries.youtube.whitelisting.requests.WhitelistRequester;
17 | import fi.vanced.utils.VancedUtils;
18 | import pl.jakubweg.SponsorBlockUtils;
19 |
20 | public class SBWhitelistButton extends SlimButton {
21 | public static final String TAG = "VI - SBWhitelistButton";
22 |
23 | public SBWhitelistButton(Context context, ViewGroup container) {
24 | super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID,
25 | SponsorBlockUtils.isSBButtonEnabled(context, WhitelistType.SPONSORBLOCK.getPreferenceEnabledName()));
26 |
27 | initialize();
28 | }
29 |
30 | private void initialize() {
31 | this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_sb_button", "drawable"));
32 | this.button_text.setText(str("action_segments"));
33 | changeEnabled(Whitelist.isChannelSBWhitelisted());
34 | }
35 |
36 | public void changeEnabled(boolean enabled) {
37 | if (debug) {
38 | Log.d(TAG, "changeEnabled " + enabled);
39 | }
40 | this.button_icon.setEnabled(!enabled); // enabled == true -> strikethrough (no segments), enabled == false -> clear (segments)
41 | }
42 |
43 | @Override
44 | public void onClick(View view) {
45 | this.view.setEnabled(false);
46 | if (Whitelist.isChannelSBWhitelisted()) {
47 | removeFromWhitelist();
48 | return;
49 | }
50 | //this.button_icon.setEnabled(!this.button_icon.isEnabled());
51 |
52 | addToWhiteList(this.view, this.button_icon);
53 | }
54 |
55 | private void removeFromWhitelist() {
56 | try {
57 | Whitelist.removeFromWhitelist(WhitelistType.SPONSORBLOCK, this.context, VideoInformation.channelName);
58 | changeEnabled(false);
59 | }
60 | catch (Exception ex) {
61 | Log.e(TAG, "Failed to remove from whitelist", ex);
62 | return;
63 | }
64 |
65 | this.view.setEnabled(true);
66 | }
67 |
68 | private void addToWhiteList(View view, ImageView buttonIcon) {
69 | new Thread(() -> {
70 | if (debug) {
71 | Log.d(TAG, "Fetching channelId for " + currentVideoId);
72 | }
73 | WhitelistRequester.addChannelToWhitelist(WhitelistType.SPONSORBLOCK, view, buttonIcon, this.context);
74 | }).start();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/SlimButton.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 |
5 | import android.content.Context;
6 | import android.util.Log;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | import fi.vanced.utils.VancedUtils;
14 |
15 | public abstract class SlimButton implements View.OnClickListener {
16 | private static final String TAG = "VI - Slim - Button";
17 | public static int SLIM_METADATA_BUTTON_ID;
18 | public final View view;
19 | public final Context context;
20 | private final ViewGroup container;
21 | protected final ImageView button_icon;
22 | protected final TextView button_text;
23 | private boolean viewAdded = false;
24 |
25 | static {
26 | SLIM_METADATA_BUTTON_ID = VancedUtils.getIdentifier("slim_metadata_button", "layout");
27 | }
28 |
29 | public SlimButton(Context context, ViewGroup container, int id, boolean visible) {
30 | if (debug) {
31 | Log.d(TAG, "Adding button with id " + id + " and visibility of " + visible);
32 | }
33 | this.context = context;
34 | this.container = container;
35 | view = LayoutInflater.from(context).inflate(id, container, false);
36 | button_icon = (ImageView)view.findViewById(VancedUtils.getIdentifier("button_icon", "id"));
37 | button_text = (TextView)view.findViewById(VancedUtils.getIdentifier("button_text", "id"));
38 |
39 | view.setOnClickListener(this);
40 |
41 | setVisible(visible);
42 | }
43 |
44 | public void setVisible(boolean visible) {
45 | try {
46 | if (!viewAdded && visible) {
47 | container.addView(view);
48 | viewAdded = true;
49 | }
50 | else if (viewAdded && !visible) {
51 | container.removeView(view);
52 | viewAdded = false;
53 | }
54 | setContainerVisibility();
55 | }
56 | catch (Exception ex) {
57 | Log.e(TAG, "Error while changing button visibility", ex);
58 | }
59 | }
60 |
61 | private void setContainerVisibility() {
62 | if (container == null) return;
63 |
64 | for (int i = 0; i < container.getChildCount(); i++) {
65 | if (container.getChildAt(i).getVisibility() == View.VISIBLE) {
66 | container.setVisibility(View.VISIBLE);
67 | return;
68 | }
69 | }
70 |
71 | container.setVisibility(View.GONE);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/SlimButtonContainer.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_BROWSER_BUTTON;
5 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
6 |
7 | import android.content.Context;
8 | import android.content.SharedPreferences;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 | import android.view.ViewGroup;
12 |
13 | import com.google.android.apps.youtube.app.ui.SlimMetadataScrollableButtonContainerLayout;
14 |
15 | import fi.vanced.libraries.youtube.whitelisting.Whitelist;
16 | import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
17 | import fi.vanced.utils.SharedPrefUtils;
18 | import fi.vanced.utils.VancedUtils;
19 | import pl.jakubweg.SponsorBlockSettings;
20 |
21 | public class SlimButtonContainer extends SlimMetadataScrollableButtonContainerLayout {
22 | private static final String TAG = "VI - Slim - Container";
23 | private ViewGroup container;
24 | private CopyButton copyButton;
25 | private CopyWithTimestamp copyWithTimestampButton;
26 | public static AdButton adBlockButton;
27 | public static SBWhitelistButton sbWhitelistButton;
28 | private SBBrowserButton sbBrowserButton;
29 | private final Context context;
30 | SharedPreferences.OnSharedPreferenceChangeListener listener;
31 |
32 | public SlimButtonContainer(Context context) {
33 | super(context);
34 | this.context = context;
35 | this.initialize(context);
36 | }
37 |
38 | public SlimButtonContainer(Context context, AttributeSet attrs) {
39 | super(context, attrs);
40 | this.context = context;
41 | this.initialize(context);
42 | }
43 |
44 | public SlimButtonContainer(Context context, AttributeSet attrs, int defStyleAttr) {
45 | super(context, attrs, defStyleAttr);
46 | this.context = context;
47 | this.initialize(context);
48 | }
49 |
50 | public void initialize(Context context) {
51 | try {
52 | container = this.findViewById(VancedUtils.getIdentifier("button_container_vanced", "id"));
53 | if (container == null) throw new Exception("Unable to initialize the button container because the button_container_vanced couldn't be found");
54 |
55 | copyButton = new CopyButton(context, this);
56 | copyWithTimestampButton = new CopyWithTimestamp(context, this);
57 | adBlockButton = new AdButton(context, this);
58 | sbWhitelistButton = new SBWhitelistButton(context, this);
59 | sbBrowserButton = new SBBrowserButton(context, this);
60 | new SponsorBlockVoting(context, this);
61 |
62 | addSharedPrefsChangeListener();
63 | }
64 | catch (Exception ex) {
65 | Log.e(TAG, "Unable to initialize the button container", ex);
66 | }
67 | }
68 |
69 | private void addSharedPrefsChangeListener() {
70 | listener = (sharedPreferences, key) -> {
71 | try {
72 | if (debug) {
73 | Log.d(TAG, String.format("SharedPreference changed with key %s", key));
74 | }
75 | if ("pref_copy_video_url_button_list".equals(key) && copyButton != null) {
76 | copyButton.setVisible(ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_button_list"));
77 | return;
78 | }
79 | if ("pref_copy_video_url_timestamp_button_list".equals(key) && copyWithTimestampButton != null) {
80 | copyWithTimestampButton.setVisible(ButtonVisibility.isVisibleInContainer(context, "pref_copy_video_url_timestamp_button_list"));
81 | return;
82 | }
83 | if (PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED.equals(key)) {
84 | if (sbWhitelistButton != null) {
85 | if (SponsorBlockSettings.isSponsorBlockEnabled) {
86 | toggleWhitelistButton();
87 | }
88 | else {
89 | Whitelist.setEnabled(WhitelistType.SPONSORBLOCK, false);
90 | sbWhitelistButton.setVisible(false);
91 | }
92 | }
93 | if (sbBrowserButton != null) {
94 | if (SponsorBlockSettings.isSponsorBlockEnabled) {
95 | toggleBrowserButton();
96 | }
97 | else {
98 | sbBrowserButton.setVisible(false);
99 | }
100 | }
101 | }
102 | if (PREFERENCES_KEY_BROWSER_BUTTON.equals(key) && sbBrowserButton != null) {
103 | toggleBrowserButton();
104 | return;
105 | }
106 | WhitelistType whitelistAds = WhitelistType.ADS;
107 | String adsEnabledPreferenceName = whitelistAds.getPreferenceEnabledName();
108 | if (adsEnabledPreferenceName.equals(key) && adBlockButton != null) {
109 | boolean enabled = SharedPrefUtils.getBoolean(context, whitelistAds.getSharedPreferencesName(), adsEnabledPreferenceName, false);
110 | Whitelist.setEnabled(whitelistAds, enabled);
111 | adBlockButton.setVisible(enabled);
112 | return;
113 | }
114 | if (WhitelistType.SPONSORBLOCK.getPreferenceEnabledName().equals(key) && sbWhitelistButton != null) {
115 | toggleWhitelistButton();
116 | return;
117 | }
118 | }
119 | catch (Exception ex) {
120 | Log.e(TAG, "Error handling shared preference change", ex);
121 | }
122 | };
123 |
124 | context.getSharedPreferences(WhitelistType.ADS.getSharedPreferencesName(), Context.MODE_PRIVATE)
125 | .registerOnSharedPreferenceChangeListener(listener);
126 | context.getSharedPreferences(WhitelistType.SPONSORBLOCK.getSharedPreferencesName(), Context.MODE_PRIVATE)
127 | .registerOnSharedPreferenceChangeListener(listener);
128 | }
129 |
130 | private void toggleWhitelistButton() {
131 | WhitelistType whitelistSB = WhitelistType.SPONSORBLOCK;
132 | String sbEnabledPreferenceName = whitelistSB.getPreferenceEnabledName();
133 | boolean enabled = SharedPrefUtils.getBoolean(context, whitelistSB.getSharedPreferencesName(), sbEnabledPreferenceName, false);
134 | Whitelist.setEnabled(whitelistSB, enabled);
135 | sbWhitelistButton.setVisible(enabled);
136 | }
137 |
138 | private void toggleBrowserButton() {
139 | sbBrowserButton.setVisible(SharedPrefUtils.getBoolean(context, SponsorBlockSettings.PREFERENCES_NAME, PREFERENCES_KEY_BROWSER_BUTTON, false));
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/SponsorBlockVoting.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 | import android.widget.Toast;
7 |
8 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
9 |
10 | import fi.vanced.utils.VancedUtils;
11 |
12 | public class SponsorBlockVoting extends SlimButton {
13 | public SponsorBlockVoting(Context context, ViewGroup container) {
14 | super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, false);
15 |
16 | initialize();
17 | }
18 |
19 | private void initialize() {
20 | this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_sb_voting", "drawable"));
21 | this.button_text.setText("SB Voting");
22 | }
23 |
24 | @Override
25 | public void onClick(View view) {
26 | Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), "Nothing atm", Toast.LENGTH_SHORT).show();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/ui/Visibility.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.ui;
2 |
3 | public enum Visibility {
4 | NONE,
5 | PLAYER,
6 | BUTTON_CONTAINER,
7 | BOTH,
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/whitelisting/Whitelist.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.whitelisting;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.player.VideoInformation.channelName;
5 | import static fi.vanced.libraries.youtube.ui.SlimButtonContainer.adBlockButton;
6 | import static fi.vanced.libraries.youtube.ui.SlimButtonContainer.sbWhitelistButton;
7 | import static fi.vanced.utils.VancedUtils.getPreferences;
8 | import static pl.jakubweg.StringRef.str;
9 |
10 | import android.content.Context;
11 | import android.content.SharedPreferences;
12 | import android.util.Log;
13 | import android.widget.Toast;
14 |
15 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
16 |
17 | import java.io.IOException;
18 | import java.util.ArrayList;
19 | import java.util.Collections;
20 | import java.util.EnumMap;
21 | import java.util.Iterator;
22 | import java.util.List;
23 | import java.util.Map;
24 |
25 | import fi.vanced.libraries.youtube.player.ChannelModel;
26 | import fi.vanced.libraries.youtube.player.VideoInformation;
27 | import fi.vanced.utils.ObjectSerializer;
28 | import fi.vanced.utils.SharedPrefUtils;
29 | import fi.vanced.utils.VancedUtils;
30 |
31 | public class Whitelist {
32 | private static final String TAG = "VI - Whitelisting";
33 | private static final Map> whitelistMap = parseWhitelist(YouTubeTikTokRoot_Application.getAppContext());
34 | private static final Map enabledMap = parseEnabledMap(YouTubeTikTokRoot_Application.getAppContext());
35 |
36 | private Whitelist() {}
37 |
38 | // injected calls
39 |
40 | public static boolean shouldShowAds() {
41 | return isWhitelisted(WhitelistType.ADS);
42 | }
43 |
44 | public static void setChannelName(String channelName) {
45 | if (debug) {
46 | Log.d(TAG, "channel name set to " + channelName);
47 | }
48 | VideoInformation.channelName = channelName;
49 |
50 | if (enabledMap.get(WhitelistType.ADS) && adBlockButton != null) {
51 | adBlockButton.changeEnabled(shouldShowAds());
52 | }
53 | if (enabledMap.get(WhitelistType.SPONSORBLOCK) && sbWhitelistButton != null) {
54 | sbWhitelistButton.changeEnabled(isChannelSBWhitelisted());
55 | }
56 | }
57 |
58 | // the rest
59 |
60 | public static boolean isChannelSBWhitelisted() {
61 | return isWhitelisted(WhitelistType.SPONSORBLOCK);
62 | }
63 |
64 | private static Map> parseWhitelist(Context context) {
65 | if (context == null) {
66 | return Collections.emptyMap();
67 | }
68 | WhitelistType[] whitelistTypes = WhitelistType.values();
69 | Map> whitelistMap = new EnumMap<>(WhitelistType.class);
70 |
71 | for (WhitelistType whitelistType : whitelistTypes) {
72 | SharedPreferences preferences = VancedUtils.getPreferences(context, whitelistType.getPreferencesName());
73 | String serializedChannels = preferences.getString("channels", null);
74 | if (serializedChannels == null) {
75 | if (debug) {
76 | Log.d(TAG, String.format("channels string was null for %s whitelisting", whitelistType));
77 | }
78 | whitelistMap.put(whitelistType, new ArrayList<>());
79 | continue;
80 | }
81 | try {
82 | ArrayList deserializedChannels = (ArrayList) ObjectSerializer.deserialize(serializedChannels);
83 | if (debug) {
84 | Log.d(TAG, serializedChannels);
85 | for (ChannelModel channel : deserializedChannels) {
86 | Log.d(TAG, String.format("Whitelisted channel %s (%s) for type %s", channel.getAuthor(), channel.getChannelId(), whitelistType));
87 | }
88 | }
89 | whitelistMap.put(whitelistType, deserializedChannels);
90 | }
91 | catch (Exception ex) {
92 | ex.printStackTrace();
93 | }
94 | }
95 | return whitelistMap;
96 | }
97 |
98 | private static Map parseEnabledMap(Context context) {
99 | if (context == null) {
100 | return Collections.emptyMap();
101 | }
102 | Map enabledMap = new EnumMap<>(WhitelistType.class);
103 | for (WhitelistType whitelistType : WhitelistType.values()) {
104 | enabledMap.put(whitelistType, SharedPrefUtils.getBoolean(context, whitelistType.getSharedPreferencesName(), whitelistType.getPreferenceEnabledName()));
105 | }
106 | return enabledMap;
107 | }
108 |
109 | private static boolean isWhitelisted(WhitelistType whitelistType) {
110 | boolean isEnabled = enabledMap.get(whitelistType);
111 | if (!isEnabled) {
112 | return false;
113 | }
114 | if (channelName == null || channelName.trim().isEmpty()) {
115 | if (debug) {
116 | Log.d(TAG, String.format("Can't check whitelist status for %s because channel name was missing", whitelistType));
117 | }
118 | return false;
119 | }
120 | List whitelistedChannels = whitelistMap.get(whitelistType);
121 | for (ChannelModel channel : whitelistedChannels) {
122 | if (channel.getAuthor().equals(channelName)) {
123 | if (debug) {
124 | Log.d(TAG, String.format("Whitelist for channel %s for type %s", channelName, whitelistType));
125 | }
126 | return true;
127 | }
128 | }
129 | return false;
130 | }
131 |
132 | public static boolean addToWhitelist(WhitelistType whitelistType, Context context, ChannelModel channel) {
133 | ArrayList whitelisted = whitelistMap.get(whitelistType);
134 | for (ChannelModel whitelistedChannel : whitelisted) {
135 | String channelId = channel.getChannelId();
136 | if (whitelistedChannel.getChannelId().equals(channelId)) {
137 | if (debug) {
138 | Log.d(TAG, String.format("Tried whitelisting an existing channel again. Old info (%1$s | %2$s) - New info (%3$s | %4$s)",
139 | whitelistedChannel.getAuthor(), channelId, channelName, channelId));
140 | }
141 | return true;
142 | }
143 | }
144 | whitelisted.add(channel);
145 | return updateWhitelist(whitelistType, whitelisted, context);
146 | }
147 |
148 | public static void removeFromWhitelist(WhitelistType whitelistType, Context context, String channelName) {
149 | ArrayList channels = whitelistMap.get(whitelistType);
150 | Iterator iterator = channels.iterator();
151 | while (iterator.hasNext()) {
152 | ChannelModel channel = iterator.next();
153 | if (channel.getAuthor().equals(channelName)) {
154 | iterator.remove();
155 | break;
156 | }
157 | }
158 | boolean success = updateWhitelist(whitelistType, channels, context);
159 | String friendlyName = whitelistType.getFriendlyName();
160 | if (success) {
161 | Toast.makeText(context, str("vanced_whitelisting_removed", channelName, friendlyName), Toast.LENGTH_SHORT).show();
162 | }
163 | else {
164 | Toast.makeText(context, str("vanced_whitelisting_remove_failed", channelName, friendlyName), Toast.LENGTH_SHORT).show();
165 | }
166 | }
167 |
168 | private static boolean updateWhitelist(WhitelistType whitelistType, ArrayList channels, Context context) {
169 | if (context == null) {
170 | return false;
171 | }
172 | SharedPreferences preferences = getPreferences(context, whitelistType.getPreferencesName());
173 | SharedPreferences.Editor editor = preferences.edit();
174 |
175 | try {
176 | editor.putString("channels", ObjectSerializer.serialize(channels));
177 | editor.apply();
178 | return true;
179 | } catch (IOException e) {
180 | e.printStackTrace();
181 | return false;
182 | }
183 | }
184 |
185 | public static void setEnabled(WhitelistType whitelistType, boolean enabled) {
186 | enabledMap.put(whitelistType, enabled);
187 | }
188 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/whitelisting/WhitelistType.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.whitelisting;
2 |
3 | import static pl.jakubweg.StringRef.str;
4 |
5 | import pl.jakubweg.SponsorBlockSettings;
6 |
7 | public enum WhitelistType {
8 | ADS("youtube", "vanced_whitelist_ads_enabled"),
9 | SPONSORBLOCK(SponsorBlockSettings.PREFERENCES_NAME, "vanced_whitelist_sb_enabled");
10 |
11 | private final String friendlyName;
12 | private final String preferencesName;
13 | private final String sharedPreferencesName;
14 | private final String preferenceEnabledName;
15 |
16 | WhitelistType(String sharedPreferencesName, String preferenceEnabledName) {
17 | this.friendlyName = str("vanced_whitelisting_" + name().toLowerCase());
18 | this.sharedPreferencesName = sharedPreferencesName;
19 | this.preferencesName = "whitelist_" + name();
20 | this.preferenceEnabledName = preferenceEnabledName;
21 | }
22 |
23 | public String getFriendlyName() {
24 | return friendlyName;
25 | }
26 |
27 | public String getSharedPreferencesName() {
28 | return sharedPreferencesName;
29 | }
30 |
31 | public String getPreferencesName() {
32 | return preferencesName;
33 | }
34 |
35 | public String getPreferenceEnabledName() {
36 | return preferenceEnabledName;
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/whitelisting/requests/WhitelistRequester.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.whitelisting.requests;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
5 | import static fi.vanced.libraries.youtube.ui.AdButton.TAG;
6 | import static fi.vanced.utils.VancedUtils.runOnMainThread;
7 | import static pl.jakubweg.StringRef.str;
8 |
9 | import android.content.Context;
10 | import android.util.Log;
11 | import android.view.View;
12 | import android.widget.ImageView;
13 | import android.widget.Toast;
14 |
15 | import org.json.JSONObject;
16 |
17 | import java.io.IOException;
18 | import java.io.OutputStream;
19 | import java.net.HttpURLConnection;
20 | import java.nio.charset.StandardCharsets;
21 |
22 | import fi.vanced.libraries.youtube.player.ChannelModel;
23 | import fi.vanced.libraries.youtube.whitelisting.Whitelist;
24 | import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
25 | import fi.vanced.utils.VancedUtils;
26 | import fi.vanced.utils.requests.Requester;
27 | import fi.vanced.utils.requests.Route;
28 | import vanced.integrations.BuildConfig;
29 |
30 | public class WhitelistRequester {
31 | private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";
32 |
33 | private WhitelistRequester() {}
34 |
35 | public static void addChannelToWhitelist(WhitelistType whitelistType, View view, ImageView buttonIcon, Context context) {
36 | try {
37 | HttpURLConnection connection = getConnectionFromRoute(WhitelistRoutes.GET_CHANNEL_DETAILS, BuildConfig.YT_API_KEY);
38 | connection.setRequestProperty("Content-Type", "application/json; utf-8");
39 | connection.setRequestProperty("Accept", "application/json");
40 | connection.setDoOutput(true);
41 | connection.setConnectTimeout(2 * 1000);
42 |
43 | String versionName = VancedUtils.getVersionName(context);
44 | String jsonInputString = "{\"context\": {\"client\": { \"clientName\": \"Android\", \"clientVersion\": \"" + versionName + "\" } }, \"videoId\": \"" + currentVideoId + "\"}";
45 | try(OutputStream os = connection.getOutputStream()) {
46 | byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
47 | os.write(input, 0, input.length);
48 | }
49 | int responseCode = connection.getResponseCode();
50 | if (responseCode == 200) {
51 | JSONObject json = getJSONObject(connection);
52 | JSONObject videoInfo = json.getJSONObject("videoDetails");
53 | ChannelModel channelModel = new ChannelModel(videoInfo.getString("author"), videoInfo.getString("channelId"));
54 | String author = channelModel.getAuthor();
55 | if (debug) {
56 | Log.d(TAG, "channelId " + channelModel.getChannelId() + " fetched for author " + author);
57 | }
58 |
59 | boolean success = Whitelist.addToWhitelist(whitelistType, context, channelModel);
60 | String whitelistTypeName = whitelistType.getFriendlyName();
61 | runOnMainThread(() -> {
62 | if (success) {
63 | buttonIcon.setEnabled(whitelistType != WhitelistType.SPONSORBLOCK);
64 | Toast.makeText(context, str("vanced_whitelisting_added", author, whitelistTypeName), Toast.LENGTH_SHORT).show();
65 | }
66 | else {
67 | buttonIcon.setEnabled(whitelistType == WhitelistType.SPONSORBLOCK);
68 | Toast.makeText(context, str("vanced_whitelisting_add_failed", author, whitelistTypeName), Toast.LENGTH_SHORT).show();
69 | }
70 | view.setEnabled(true);
71 | });
72 | }
73 | else {
74 | if (debug) {
75 | Log.d(TAG, "player fetch response was " + responseCode);
76 | }
77 | runOnMainThread(() -> {
78 | Toast.makeText(context, str("vanced_whitelisting_fetch_failed", responseCode), Toast.LENGTH_SHORT).show();
79 | buttonIcon.setEnabled(true);
80 | view.setEnabled(true);
81 | });
82 | }
83 | connection.disconnect();
84 | }
85 | catch (Exception ex) {
86 | Log.e(TAG, "Failed to fetch channelId", ex);
87 | runOnMainThread(() -> view.setEnabled(true));
88 | }
89 | }
90 |
91 | // helpers
92 |
93 | private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
94 | return Requester.getConnectionFromRoute(YT_API_URL, route, params);
95 | }
96 |
97 | private static JSONObject getJSONObject(HttpURLConnection connection) throws Exception {
98 | return Requester.getJSONObject(connection);
99 | }
100 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/libraries/youtube/whitelisting/requests/WhitelistRoutes.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.libraries.youtube.whitelisting.requests;
2 |
3 | import static fi.vanced.utils.requests.Route.Method.POST;
4 |
5 | import fi.vanced.utils.requests.Route;
6 |
7 | public class WhitelistRoutes {
8 | public static final Route GET_CHANNEL_DETAILS = new Route(POST, "player?key={api_key}");
9 |
10 | private WhitelistRoutes() {}
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/utils/ObjectSerializer.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.utils;
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | *
20 | * Modifications copyright (C) 2022 Vanced
21 | */
22 |
23 | import android.util.Log;
24 |
25 | import java.io.ByteArrayInputStream;
26 | import java.io.ByteArrayOutputStream;
27 | import java.io.IOException;
28 | import java.io.ObjectInputStream;
29 | import java.io.ObjectOutputStream;
30 | import java.io.Serializable;
31 |
32 | public class ObjectSerializer {
33 | private static final String TAG = "VI - ObjectSerializer";
34 |
35 | public static String serialize(Serializable obj) throws IOException {
36 | if (obj == null) return "";
37 | try {
38 | ByteArrayOutputStream serialObj = new ByteArrayOutputStream();
39 | ObjectOutputStream objStream = new ObjectOutputStream(serialObj);
40 | objStream.writeObject(obj);
41 | objStream.close();
42 | return encodeBytes(serialObj.toByteArray());
43 | } catch (Exception e) {
44 | Log.e(TAG, "Serialization error: " + e.getMessage(), e);
45 | throw new IOException(e);
46 | }
47 | }
48 |
49 | public static Object deserialize(String str) throws IOException {
50 | if (str == null || str.length() == 0) return null;
51 | try {
52 | ByteArrayInputStream serialObj = new ByteArrayInputStream(decodeBytes(str));
53 | ObjectInputStream objStream = new ObjectInputStream(serialObj);
54 | return objStream.readObject();
55 | } catch (Exception e) {
56 | Log.e(TAG, "Deserialization error: " + e.getMessage(), e);
57 | throw new IOException(e);
58 | }
59 | }
60 |
61 | public static String encodeBytes(byte[] bytes) {
62 | StringBuffer strBuf = new StringBuffer();
63 |
64 | for (int i = 0; i < bytes.length; i++) {
65 | strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
66 | strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
67 | }
68 |
69 | return strBuf.toString();
70 | }
71 |
72 | public static byte[] decodeBytes(String str) {
73 | byte[] bytes = new byte[str.length() / 2];
74 | for (int i = 0; i < str.length(); i+=2) {
75 | char c = str.charAt(i);
76 | bytes[i/2] = (byte) ((c - 'a') << 4);
77 | c = str.charAt(i+1);
78 | bytes[i/2] += (c - 'a');
79 | }
80 | return bytes;
81 | }
82 |
83 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/utils/SharedPrefUtils.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.utils;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | public class SharedPrefUtils {
7 | public static void saveString(Context context, String preferenceName, String key, String value){
8 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
9 | sharedPreferences.edit().putString(key, value).apply();
10 | }
11 | public static void saveBoolean(Context context, String preferenceName, String key, Boolean value){
12 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
13 | sharedPreferences.edit().putBoolean(key, value).apply();
14 | }
15 | public static void saveInt(Context context, String preferenceName, String key, Integer value){
16 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
17 | sharedPreferences.edit().putInt(key, value).apply();
18 | }
19 |
20 | public static String getString(Context context, String preferenceName, String key){
21 | return getString(context, preferenceName, key, null);
22 | }
23 | public static String getString(Context context, String preferenceName, String key, String _default){
24 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
25 | return (sharedPreferences.getString(key, _default));
26 | }
27 |
28 | public static Boolean getBoolean(Context context, String preferenceName, String key){
29 | return getBoolean(context, preferenceName, key, false);
30 | }
31 | public static Boolean getBoolean(Context context, String preferenceName, String key, Boolean _default){
32 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
33 | return (sharedPreferences.getBoolean(key, _default));
34 | }
35 |
36 | public static Integer getInt(Context context, String preferenceName, String key){
37 | return getInt(context, preferenceName, key, -1);
38 | }
39 | public static Integer getInt(Context context, String preferenceName, String key, Integer _default){
40 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
41 | return (sharedPreferences.getInt(key, _default));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/utils/VancedUtils.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.utils;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.pm.PackageInfo;
6 | import android.content.pm.PackageManager;
7 | import android.os.Handler;
8 | import android.os.Looper;
9 |
10 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
11 |
12 | import java.security.SecureRandom;
13 |
14 | public class VancedUtils {
15 |
16 | private VancedUtils() {}
17 |
18 | public static SharedPreferences getPreferences(Context context, String preferencesName) {
19 | if (context == null) return null;
20 | return context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE);
21 | }
22 |
23 | public static int getIdentifier(String name, String defType) {
24 | Context context = YouTubeTikTokRoot_Application.getAppContext();
25 | return context.getResources().getIdentifier(name, defType, context.getPackageName());
26 | }
27 |
28 | // https://stackoverflow.com/a/157202
29 | static final String AB = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
30 | static SecureRandom rnd = new SecureRandom();
31 |
32 | public static String randomString(int len){
33 | StringBuilder sb = new StringBuilder(len);
34 | for(int i = 0; i < len; i++)
35 | sb.append(AB.charAt(rnd.nextInt(AB.length())));
36 | return sb.toString();
37 | }
38 |
39 | public static int countMatches(CharSequence seq, char c) {
40 | int count = 0;
41 | for (int i = 0; i < seq.length(); i++) {
42 | if (seq.charAt(i) == c)
43 | count++;
44 | }
45 | return count;
46 | }
47 |
48 | public static String getVersionName(Context context) {
49 | try {
50 | PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
51 | String version = pInfo.versionName;
52 | return (version);
53 | } catch (PackageManager.NameNotFoundException e) {
54 | e.printStackTrace();
55 | }
56 |
57 | return ("17.03.35");
58 | }
59 |
60 | public static void runOnMainThread(Runnable runnable) {
61 | new Handler(Looper.getMainLooper()).post(runnable);
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/utils/requests/Requester.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.utils.requests;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.InputStreamReader;
10 | import java.net.HttpURLConnection;
11 | import java.net.URL;
12 |
13 | import fi.vanced.libraries.youtube.whitelisting.requests.WhitelistRoutes;
14 |
15 | public class Requester {
16 | private Requester() {}
17 |
18 | public static HttpURLConnection getConnectionFromRoute(String apiUrl, Route route, String... params) throws IOException {
19 | String url = apiUrl + route.compile(params).getCompiledRoute();
20 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
21 | connection.setRequestMethod(route.getMethod().name());
22 | if (route != WhitelistRoutes.GET_CHANNEL_DETAILS) {
23 | connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
24 | }
25 | return connection;
26 | }
27 |
28 | public static String parseJson(HttpURLConnection connection) throws IOException {
29 | return parseJson(connection.getInputStream(), false);
30 | }
31 |
32 | public static String parseJson(InputStream inputStream, boolean isError) throws IOException {
33 | StringBuilder jsonBuilder = new StringBuilder();
34 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
35 | String line;
36 | while ((line = reader.readLine()) != null) {
37 | jsonBuilder.append(line);
38 | if (isError)
39 | jsonBuilder.append("\n");
40 | }
41 | inputStream.close();
42 | return jsonBuilder.toString();
43 | }
44 |
45 | public static String parseErrorJson(HttpURLConnection connection) throws IOException {
46 | return parseJson(connection.getErrorStream(), true);
47 | }
48 |
49 | public static JSONObject getJSONObject(HttpURLConnection connection) throws Exception {
50 | return new JSONObject(parseJsonAndDisconnect(connection));
51 | }
52 |
53 | public static JSONArray getJSONArray(HttpURLConnection connection) throws Exception {
54 | return new JSONArray(parseJsonAndDisconnect(connection));
55 | }
56 |
57 | private static String parseJsonAndDisconnect(HttpURLConnection connection) throws IOException {
58 | String json = parseJson(connection);
59 | connection.disconnect();
60 | return json;
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/fi/vanced/utils/requests/Route.java:
--------------------------------------------------------------------------------
1 | package fi.vanced.utils.requests;
2 |
3 | import fi.vanced.utils.VancedUtils;
4 |
5 | public class Route {
6 | private final String route;
7 | private final Route.Method method;
8 | private final int paramCount;
9 |
10 | public Route(Route.Method method, String route) {
11 | this.method = method;
12 | this.route = route;
13 | this.paramCount = VancedUtils.countMatches(route, '{');
14 |
15 | if (paramCount != VancedUtils.countMatches(route, '}'))
16 | throw new IllegalArgumentException("Not enough parameters");
17 | }
18 |
19 | public Route.Method getMethod() {
20 | return method;
21 | }
22 |
23 | public Route.CompiledRoute compile(String... params) {
24 | if (params.length != paramCount)
25 | throw new IllegalArgumentException("Error compiling route [" + route + "], incorrect amount of parameters provided. " +
26 | "Expected: " + paramCount + ", provided: " + params.length);
27 |
28 | StringBuilder compiledRoute = new StringBuilder(route);
29 | for (int i = 0; i < paramCount; i++) {
30 | int paramStart = compiledRoute.indexOf("{");
31 | int paramEnd = compiledRoute.indexOf("}");
32 | compiledRoute.replace(paramStart, paramEnd + 1, params[i]);
33 | }
34 | return new Route.CompiledRoute(this, compiledRoute.toString());
35 | }
36 |
37 | public static class CompiledRoute {
38 | private final Route baseRoute;
39 | private final String compiledRoute;
40 |
41 | private CompiledRoute(Route baseRoute, String compiledRoute) {
42 | this.baseRoute = baseRoute;
43 | this.compiledRoute = compiledRoute;
44 | }
45 |
46 | public String getCompiledRoute() {
47 | return compiledRoute;
48 | }
49 |
50 | public Route.Method getMethod() {
51 | return baseRoute.method;
52 | }
53 | }
54 |
55 | public enum Method {
56 | GET,
57 | POST
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/InjectedPlugin.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import android.util.Log;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 |
7 | import java.lang.reflect.Field;
8 |
9 | // invoke-static {p0}, Lpl/jakubweg/InjectedPlugin;->inject(Landroid/content/Context;)V
10 | // invoke-static {}, Lpl/jakubweg/InjectedPlugin;->printSomething()V
11 | // InlineTimeBar
12 | public class InjectedPlugin {
13 |
14 | private static final String TAG = "jakubweg.InjectedPlugin";
15 |
16 | public static void printSomething() {
17 | Log.d(TAG, "printSomething called");
18 | }
19 |
20 | public static void printObject(Object o, int recursive) {
21 | if (o == null)
22 | Log.d(TAG, "Printed object is null");
23 | else {
24 | Log.d(TAG, "Printed object ("
25 | + o.getClass().getName()
26 | + ") = " + o.toString());
27 | for (Field field : o.getClass().getDeclaredFields()) {
28 | if (field.getType().isPrimitive())
29 | continue;
30 | field.setAccessible(true);
31 | try {
32 | Object value = field.get(o);
33 | try {
34 | // if ("java.lang.String".equals(field.getType().getName()))
35 | Log.d(TAG, "Field: " + field.toString() + " has value " + value);
36 | } catch (Exception e) {
37 | Log.d(TAG, "Field: " + field.toString() + " has value that thrown an exception in toString method");
38 | }
39 | if (recursive > 0 && value != null && !value.getClass().isPrimitive())
40 | printObject(value, recursive - 1);
41 | } catch (IllegalAccessException e) {
42 | e.printStackTrace();
43 | }
44 | }
45 | }
46 | }
47 |
48 | public static void printObject(Object o) {
49 | printObject(o, 0);
50 | }
51 |
52 | public static void printObject(int o) {
53 | printObject(Integer.valueOf(o));
54 | }
55 |
56 | public static void printObject(float o) {
57 | printObject(Float.valueOf(o));
58 | }
59 |
60 | public static void printObject(long o) {
61 | printObject(Long.valueOf(o));
62 | }
63 |
64 | public static void printStackTrace() {
65 | StackTraceElement[] stackTrace = (new Throwable()).getStackTrace();
66 | Log.d(TAG, "Printing stack trace:");
67 | for (StackTraceElement element : stackTrace) {
68 | Log.d(TAG, element.toString());
69 | }
70 | }
71 |
72 | public static void printViewStack(final View view, int spaces) {
73 | StringBuilder builder = new StringBuilder(spaces);
74 | for (int i = 0; i < spaces; i++) {
75 | builder.append('-');
76 | }
77 | String spacesStr = builder.toString();
78 |
79 | if (view == null) {
80 | Log.i(TAG, spacesStr + "Null view");
81 | return;
82 | }
83 | if (view instanceof ViewGroup) {
84 | ViewGroup group = (ViewGroup) view;
85 | Log.i(TAG, spacesStr + "View group: " + view);
86 | int childCount = group.getChildCount();
87 | Log.i(TAG, spacesStr + "Children count: " + childCount);
88 | for (int i = 0; i < childCount; i++) {
89 | printViewStack(group.getChildAt(i), spaces + 1);
90 | }
91 | } else {
92 | Log.i(TAG, spacesStr + "Normal view: " + view);
93 | }
94 | }
95 | }
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/NewSegmentHelperLayout.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import android.content.Context;
4 |
5 | import static fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView.hideNewSegmentLayout;
6 | import static fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView.showNewSegmentLayout;
7 |
8 | public class NewSegmentHelperLayout {
9 | public static Context context;
10 | private static boolean isShown = false;
11 |
12 | public static void show() {
13 | if (isShown) return;
14 | isShown = true;
15 | showNewSegmentLayout();
16 | }
17 |
18 | public static void hide() {
19 | if (!isShown) return;
20 | isShown = false;
21 | hideNewSegmentLayout();
22 | }
23 |
24 | public static void toggle() {
25 | if (isShown) hide();
26 | else show();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/ShieldButton.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import android.view.View;
6 | import android.view.animation.Animation;
7 | import android.view.animation.AnimationUtils;
8 | import android.widget.ImageView;
9 | import android.widget.RelativeLayout;
10 |
11 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
12 |
13 | import java.lang.ref.WeakReference;
14 |
15 | import static fi.razerman.youtube.XGlobals.debug;
16 | import static pl.jakubweg.PlayerController.getCurrentVideoLength;
17 | import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
18 |
19 | public class ShieldButton {
20 | static String TAG = "SHIELD";
21 | static RelativeLayout _youtubeControlsLayout;
22 | static WeakReference _shieldBtn = new WeakReference<>(null);
23 | static int fadeDurationFast;
24 | static int fadeDurationScheduled;
25 | static Animation fadeIn;
26 | static Animation fadeOut;
27 | static boolean isShowing;
28 |
29 | public static void initialize(Object viewStub) {
30 | try {
31 | if(debug){
32 | Log.d(TAG, "initializing shield button");
33 | }
34 |
35 | _youtubeControlsLayout = (RelativeLayout) viewStub;
36 |
37 | ImageView imageView = (ImageView)_youtubeControlsLayout
38 | .findViewById(getIdentifier("sponsorblock_button", "id"));
39 |
40 | if (debug && imageView == null){
41 | Log.d(TAG, "Couldn't find imageView with tag \"sponsorblock_button\"");
42 | }
43 | if (imageView == null) return;
44 | imageView.setOnClickListener(SponsorBlockUtils.sponsorBlockBtnListener);
45 | _shieldBtn = new WeakReference<>(imageView);
46 |
47 | // Animations
48 | fadeDurationFast = getInteger("fade_duration_fast");
49 | fadeDurationScheduled = getInteger("fade_duration_scheduled");
50 | fadeIn = getAnimation("fade_in");
51 | fadeIn.setDuration(fadeDurationFast);
52 | fadeOut = getAnimation("fade_out");
53 | fadeOut.setDuration(fadeDurationScheduled);
54 | isShowing = true;
55 | changeVisibilityImmediate(false);
56 | }
57 | catch (Exception ex) {
58 | Log.e(TAG, "Unable to set RelativeLayout", ex);
59 | }
60 | }
61 |
62 | public static void changeVisibilityImmediate(boolean visible) {
63 | changeVisibility(visible, true);
64 | }
65 |
66 | public static void changeVisibilityNegatedImmediate(boolean visible) {
67 | changeVisibility(!visible, true);
68 | }
69 |
70 | public static void changeVisibility(boolean visible) {
71 | changeVisibility(visible, false);
72 | }
73 |
74 | public static void changeVisibility(boolean visible, boolean immediate) {
75 | if (isShowing == visible) return;
76 | isShowing = visible;
77 |
78 | ImageView iView = _shieldBtn.get();
79 | if (_youtubeControlsLayout == null || iView == null) return;
80 |
81 | if (visible && shouldBeShown()) {
82 | if (getLastKnownVideoTime() >= getCurrentVideoLength()) {
83 | return;
84 | }
85 | if (debug) {
86 | Log.d(TAG, "Fading in");
87 | }
88 | iView.setVisibility(View.VISIBLE);
89 | if (!immediate)
90 | iView.startAnimation(fadeIn);
91 | return;
92 | }
93 |
94 | if (iView.getVisibility() == View.VISIBLE) {
95 | if (debug) {
96 | Log.d(TAG, "Fading out");
97 | }
98 | if (!immediate)
99 | iView.startAnimation(fadeOut);
100 | iView.setVisibility(shouldBeShown() ? View.INVISIBLE : View.GONE);
101 | }
102 | }
103 |
104 | static boolean shouldBeShown() {
105 | return SponsorBlockUtils.isSettingEnabled(SponsorBlockSettings.isAddNewSegmentEnabled);
106 | }
107 |
108 | //region Helpers
109 | private static int getIdentifier(String name, String defType) {
110 | Context context = YouTubeTikTokRoot_Application.getAppContext();
111 | return context.getResources().getIdentifier(name, defType, context.getPackageName());
112 | }
113 |
114 | private static int getInteger(String name) {
115 | return YouTubeTikTokRoot_Application.getAppContext().getResources().getInteger(getIdentifier(name, "integer"));
116 | }
117 |
118 | private static Animation getAnimation(String name) {
119 | return AnimationUtils.loadAnimation(YouTubeTikTokRoot_Application.getAppContext(), getIdentifier(name, "anim"));
120 | }
121 | //endregion
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/SkipSegmentView.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.util.DisplayMetrics;
6 | import android.util.Log;
7 | import android.widget.Toast;
8 |
9 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
10 |
11 | import pl.jakubweg.objects.SponsorSegment;
12 |
13 | import static fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView.hideSkipButton;
14 | import static fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView.showSkipButton;
15 | import static pl.jakubweg.PlayerController.VERBOSE;
16 |
17 | @SuppressLint({"RtlHardcoded", "SetTextI18n", "LongLogTag", "AppCompatCustomView"})
18 | public class SkipSegmentView {
19 | public static final String TAG = "jakubweg.SkipSegmentView";
20 | private static SponsorSegment lastNotifiedSegment;
21 |
22 | public static void show() {
23 | showSkipButton();
24 | }
25 |
26 | public static void hide() {
27 | hideSkipButton();
28 | }
29 |
30 | public static void notifySkipped(SponsorSegment segment) {
31 | if (segment == lastNotifiedSegment) {
32 | if (VERBOSE)
33 | Log.d(TAG, "notifySkipped; segment == lastNotifiedSegment");
34 | return;
35 | }
36 | lastNotifiedSegment = segment;
37 | String skipMessage = segment.category.skipMessage.toString();
38 | Context context = YouTubeTikTokRoot_Application.getAppContext();
39 | if (VERBOSE)
40 | Log.d(TAG, String.format("notifySkipped; message=%s", skipMessage));
41 |
42 | if (context != null)
43 | Toast.makeText(context, skipMessage, Toast.LENGTH_SHORT).show();
44 | }
45 |
46 | public static float convertDpToPixel(float dp, Context context) {
47 | return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/SponsorBlockPreferenceFragment.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import static fi.razerman.youtube.XGlobals.debug;
4 | import static pl.jakubweg.SponsorBlockSettings.DEFAULT_API_URL;
5 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP;
6 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_API_URL;
7 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_BROWSER_BUTTON;
8 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_COUNT_SKIPS;
9 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_MIN_DURATION;
10 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_NEW_SEGMENT_ENABLED;
11 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS;
12 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP;
13 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
14 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN;
15 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_UUID;
16 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_VOTING_ENABLED;
17 | import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_NAME;
18 | import static pl.jakubweg.SponsorBlockSettings.adjustNewSegmentMillis;
19 | import static pl.jakubweg.SponsorBlockSettings.countSkips;
20 | import static pl.jakubweg.SponsorBlockSettings.minDuration;
21 | import static pl.jakubweg.SponsorBlockSettings.setSeenGuidelines;
22 | import static pl.jakubweg.SponsorBlockSettings.showTimeWithoutSegments;
23 | import static pl.jakubweg.SponsorBlockSettings.showToastWhenSkippedAutomatically;
24 | import static pl.jakubweg.SponsorBlockSettings.uuid;
25 | import static pl.jakubweg.StringRef.str;
26 |
27 | import android.app.Activity;
28 | import android.app.AlertDialog;
29 | import android.content.Context;
30 | import android.content.DialogInterface;
31 | import android.content.Intent;
32 | import android.content.SharedPreferences;
33 | import android.net.Uri;
34 | import android.os.Bundle;
35 | import android.preference.EditTextPreference;
36 | import android.preference.Preference;
37 | import android.preference.PreferenceCategory;
38 | import android.preference.PreferenceFragment;
39 | import android.preference.PreferenceScreen;
40 | import android.preference.SwitchPreference;
41 | import android.text.Editable;
42 | import android.text.Html;
43 | import android.text.InputType;
44 | import android.util.Patterns;
45 | import android.widget.EditText;
46 | import android.widget.Toast;
47 |
48 | import java.lang.ref.WeakReference;
49 | import java.text.DecimalFormat;
50 | import java.util.ArrayList;
51 |
52 | import fi.vanced.libraries.youtube.whitelisting.WhitelistType;
53 | import fi.vanced.utils.SharedPrefUtils;
54 | import pl.jakubweg.objects.EditTextListPreference;
55 | import pl.jakubweg.requests.SBRequester;
56 |
57 | @SuppressWarnings({"unused", "deprecation"}) // injected
58 | public class SponsorBlockPreferenceFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
59 | public static final DecimalFormat FORMATTER = new DecimalFormat("#,###,###");
60 | public static final String SAVED_TEMPLATE = "%dh %.1f %s";
61 | private static final APIURLChangeListener API_URL_CHANGE_LISTENER = new APIURLChangeListener();
62 | private final ArrayList preferencesToDisableWhenSBDisabled = new ArrayList<>();
63 |
64 | @Override
65 | public void onCreate(Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 | getPreferenceManager().setSharedPreferencesName(PREFERENCES_NAME);
68 |
69 | getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
70 |
71 | final Activity context = this.getActivity();
72 |
73 | PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
74 | setPreferenceScreen(preferenceScreen);
75 |
76 | SponsorBlockSettings.update(context);
77 |
78 | {
79 | SwitchPreference preference = new SwitchPreference(context);
80 | preferenceScreen.addPreference(preference);
81 | preference.setKey(PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED);
82 | preference.setDefaultValue(SponsorBlockSettings.isSponsorBlockEnabled);
83 | preference.setChecked(SponsorBlockSettings.isSponsorBlockEnabled);
84 | preference.setTitle(str("enable_sb"));
85 | preference.setSummary(str("enable_sb_sum"));
86 | preference.setOnPreferenceChangeListener((preference1, newValue) -> {
87 | final boolean value = (Boolean) newValue;
88 | enableCategoriesIfNeeded(value);
89 | return true;
90 | });
91 | }
92 |
93 | // Clear hint
94 | if (debug) {
95 | SwitchPreference preference = new SwitchPreference(context);
96 | preferenceScreen.addPreference(preference);
97 | preference.setKey(PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN);
98 | preference.setDefaultValue(false);
99 | preference.setChecked(SharedPrefUtils.getBoolean(context, PREFERENCES_NAME, PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN));
100 | preference.setTitle("Hint debug");
101 | preference.setSummary("Debug toggle for clearing the hint shown preference");
102 | preference.setOnPreferenceChangeListener((pref, newValue) -> true);
103 | }
104 |
105 | {
106 | SwitchPreference preference = new SwitchPreference(context);
107 | preferenceScreen.addPreference(preference);
108 | preference.setKey(PREFERENCES_KEY_NEW_SEGMENT_ENABLED);
109 | preference.setDefaultValue(SponsorBlockSettings.isAddNewSegmentEnabled);
110 | preference.setChecked(SponsorBlockSettings.isAddNewSegmentEnabled);
111 | preference.setTitle(str("enable_segmadding"));
112 | preference.setSummary(str("enable_segmadding_sum"));
113 | preferencesToDisableWhenSBDisabled.add(preference);
114 | preference.setOnPreferenceChangeListener((preference12, o) -> {
115 | final boolean value = (Boolean) o;
116 | if (value && !SponsorBlockSettings.seenGuidelinesPopup) {
117 | new AlertDialog.Builder(preference12.getContext())
118 | .setTitle(str("sb_guidelines_popup_title"))
119 | .setMessage(str("sb_guidelines_popup_content"))
120 | .setNegativeButton(str("sb_guidelines_popup_already_read"), null)
121 | .setPositiveButton(str("sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines())
122 | .show();
123 | }
124 | return true;
125 | });
126 | }
127 |
128 | {
129 | SwitchPreference preference = new SwitchPreference(context);
130 | preferenceScreen.addPreference(preference);
131 | preference.setTitle(str("enable_voting"));
132 | preference.setSummary(str("enable_voting_sum"));
133 | preference.setKey(PREFERENCES_KEY_VOTING_ENABLED);
134 | preference.setDefaultValue(SponsorBlockSettings.isVotingEnabled);
135 | preference.setChecked(SponsorBlockSettings.isVotingEnabled);
136 | preferencesToDisableWhenSBDisabled.add(preference);
137 | }
138 |
139 | addGeneralCategory(context, preferenceScreen);
140 | addSegmentsCategory(context, preferenceScreen);
141 | addStatsCategory(context, preferenceScreen);
142 | addAboutCategory(context, preferenceScreen);
143 |
144 | enableCategoriesIfNeeded(SponsorBlockSettings.isSponsorBlockEnabled);
145 | }
146 |
147 | private void openGuidelines() {
148 | final Context context = getActivity();
149 | setSeenGuidelines(context);
150 |
151 | Intent intent = new Intent(Intent.ACTION_VIEW);
152 | intent.setData(Uri.parse("https://wiki.sponsor.ajay.app/w/Guidelines"));
153 | context.startActivity(intent);
154 | }
155 |
156 | private void enableCategoriesIfNeeded(boolean enabled) {
157 | for (Preference preference : preferencesToDisableWhenSBDisabled)
158 | preference.setEnabled(enabled);
159 | }
160 |
161 | @Override
162 | public void onDestroy() {
163 | super.onDestroy();
164 | getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
165 | }
166 |
167 | private void addSegmentsCategory(Context context, PreferenceScreen screen) {
168 | PreferenceCategory category = new PreferenceCategory(context);
169 | screen.addPreference(category);
170 | preferencesToDisableWhenSBDisabled.add(category);
171 | category.setTitle(str("diff_segments"));
172 |
173 | SponsorBlockSettings.SegmentBehaviour[] segmentBehaviours = SponsorBlockSettings.SegmentBehaviour.values();
174 | String[] entries = new String[segmentBehaviours.length];
175 | String[] entryValues = new String[segmentBehaviours.length];
176 | for (int i = 0, segmentBehavioursLength = segmentBehaviours.length; i < segmentBehavioursLength; i++) {
177 | SponsorBlockSettings.SegmentBehaviour behaviour = segmentBehaviours[i];
178 | entries[i] = behaviour.name.toString();
179 | entryValues[i] = behaviour.key;
180 | }
181 |
182 | SponsorBlockSettings.SegmentInfo[] categories = SponsorBlockSettings.SegmentInfo.valuesWithoutUnsubmitted();
183 |
184 | for (SponsorBlockSettings.SegmentInfo segmentInfo : categories) {
185 | EditTextListPreference preference = new EditTextListPreference(context);
186 | preference.setTitle(segmentInfo.getTitleWithDot());
187 | preference.setSummary(segmentInfo.description.toString());
188 | preference.setKey(segmentInfo.key);
189 | preference.setDefaultValue(segmentInfo.behaviour.key);
190 | preference.setEntries(entries);
191 | preference.setEntryValues(entryValues);
192 |
193 | category.addPreference(preference);
194 | }
195 |
196 | Preference colorPreference = new Preference(context); // TODO remove this after the next major update
197 | screen.addPreference(colorPreference);
198 | colorPreference.setTitle(str("color_change"));
199 | colorPreference.setSummary(str("color_change_sum"));
200 | preferencesToDisableWhenSBDisabled.add(colorPreference);
201 | }
202 |
203 | private void addStatsCategory(Context context, PreferenceScreen screen) {
204 | PreferenceCategory category = new PreferenceCategory(context);
205 | screen.addPreference(category);
206 | category.setTitle(str("stats"));
207 | preferencesToDisableWhenSBDisabled.add(category);
208 |
209 | {
210 | Preference preference = new Preference(context);
211 | category.addPreference(preference);
212 | preference.setTitle(str("stats_loading"));
213 |
214 | SBRequester.retrieveUserStats(category, preference);
215 | }
216 | }
217 |
218 | private void addAboutCategory(Context context, PreferenceScreen screen) {
219 | PreferenceCategory category = new PreferenceCategory(context);
220 | screen.addPreference(category);
221 | category.setTitle(str("about"));
222 |
223 | {
224 | Preference preference = new Preference(context);
225 | screen.addPreference(preference);
226 | preference.setTitle(str("about_api"));
227 | preference.setSummary(str("about_api_sum"));
228 | preference.setOnPreferenceClickListener(preference1 -> {
229 | Intent i = new Intent(Intent.ACTION_VIEW);
230 | i.setData(Uri.parse("https://sponsor.ajay.app"));
231 | preference1.getContext().startActivity(i);
232 | return false;
233 | });
234 | }
235 |
236 | {
237 | Preference preference = new Preference(context);
238 | screen.addPreference(preference);
239 | preference.setTitle(str("about_madeby"));
240 | }
241 |
242 | }
243 |
244 | private void addGeneralCategory(final Context context, PreferenceScreen screen) {
245 | final PreferenceCategory category = new PreferenceCategory(context);
246 | preferencesToDisableWhenSBDisabled.add(category);
247 | screen.addPreference(category);
248 | category.setTitle(str("general"));
249 |
250 | {
251 | Preference preference = new Preference(context);
252 | preference.setTitle(str("sb_guidelines_preference_title"));
253 | preference.setSummary(str("sb_guidelines_preference_sum"));
254 | preference.setOnPreferenceClickListener(preference1 -> {
255 | openGuidelines();
256 | return false;
257 | });
258 | screen.addPreference(preference);
259 | }
260 |
261 | {
262 | Preference preference = new SwitchPreference(context);
263 | preference.setTitle(str("general_skiptoast"));
264 | preference.setSummary(str("general_skiptoast_sum"));
265 | preference.setKey(PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP);
266 | preference.setDefaultValue(showToastWhenSkippedAutomatically);
267 | preference.setOnPreferenceClickListener(preference12 -> {
268 | Toast.makeText(preference12.getContext(), str("skipped_sponsor"), Toast.LENGTH_SHORT).show();
269 | return false;
270 | });
271 | preferencesToDisableWhenSBDisabled.add(preference);
272 | screen.addPreference(preference);
273 | }
274 |
275 | {
276 | Preference preference = new SwitchPreference(context);
277 | preference.setTitle(str("general_skipcount"));
278 | preference.setSummary(str("general_skipcount_sum"));
279 | preference.setKey(PREFERENCES_KEY_COUNT_SKIPS);
280 | preference.setDefaultValue(countSkips);
281 | preferencesToDisableWhenSBDisabled.add(preference);
282 | screen.addPreference(preference);
283 | }
284 |
285 | {
286 | Preference preference = new SwitchPreference(context);
287 | preference.setTitle(str("general_time_without_sb"));
288 | preference.setSummary(str("general_time_without_sb_sum"));
289 | preference.setKey(PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS);
290 | preference.setDefaultValue(showTimeWithoutSegments);
291 | preferencesToDisableWhenSBDisabled.add(preference);
292 | screen.addPreference(preference);
293 | }
294 |
295 | {
296 | Preference preference = new SwitchPreference(context);
297 | preference.setTitle(str("general_whitelisting"));
298 | preference.setSummary(str("general_whitelisting_sum"));
299 | preference.setKey(WhitelistType.SPONSORBLOCK.getPreferenceEnabledName());
300 | preferencesToDisableWhenSBDisabled.add(preference);
301 | screen.addPreference(preference);
302 | }
303 |
304 | {
305 | Preference preference = new SwitchPreference(context);
306 | preference.setTitle(str("general_browser_button"));
307 | preference.setSummary(str("general_browser_button_sum"));
308 | preference.setKey(PREFERENCES_KEY_BROWSER_BUTTON);
309 | preferencesToDisableWhenSBDisabled.add(preference);
310 | screen.addPreference(preference);
311 | }
312 |
313 | {
314 | EditTextPreference preference = new EditTextPreference(context);
315 | preference.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
316 | preference.setTitle(str("general_adjusting"));
317 | preference.setSummary(str("general_adjusting_sum"));
318 | preference.setKey(PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP);
319 | preference.setDefaultValue(String.valueOf(adjustNewSegmentMillis));
320 | screen.addPreference(preference);
321 | preferencesToDisableWhenSBDisabled.add(preference);
322 | }
323 |
324 | {
325 | EditTextPreference preference = new EditTextPreference(context);
326 | preference.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
327 | preference.setTitle(str("general_min_duration"));
328 | preference.setSummary(str("general_min_duration_sum"));
329 | preference.setKey(PREFERENCES_KEY_MIN_DURATION);
330 | preference.setDefaultValue(String.valueOf(minDuration));
331 | screen.addPreference(preference);
332 | preferencesToDisableWhenSBDisabled.add(preference);
333 | }
334 |
335 | {
336 | Preference preference = new EditTextPreference(context);
337 | preference.setTitle(str("general_uuid"));
338 | preference.setSummary(str("general_uuid_sum"));
339 | preference.setKey(PREFERENCES_KEY_UUID);
340 | preference.setDefaultValue(uuid);
341 | screen.addPreference(preference);
342 | preferencesToDisableWhenSBDisabled.add(preference);
343 | }
344 |
345 | {
346 | Preference preference = new Preference(context);
347 | String title = str("general_api_url");
348 | preference.setTitle(title);
349 | preference.setSummary(Html.fromHtml(str("general_api_url_sum")));
350 | preference.setOnPreferenceClickListener(preference1 -> {
351 | EditText editText = new EditText(context);
352 | editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
353 | editText.setText(SponsorBlockSettings.apiUrl);
354 |
355 | API_URL_CHANGE_LISTENER.setEditTextRef(editText);
356 | new AlertDialog.Builder(context)
357 | .setTitle(title)
358 | .setView(editText)
359 | .setNegativeButton(android.R.string.cancel, null)
360 | .setNeutralButton(str("reset"), API_URL_CHANGE_LISTENER)
361 | .setPositiveButton(android.R.string.ok, API_URL_CHANGE_LISTENER)
362 | .show();
363 | return true;
364 | });
365 |
366 | screen.addPreference(preference);
367 | preferencesToDisableWhenSBDisabled.add(preference);
368 | }
369 |
370 | {
371 | EditTextPreference preference = new EditTextPreference(context);
372 | Context applicationContext = context.getApplicationContext();
373 |
374 | preference.setTitle(str("settings_ie"));
375 | preference.setSummary(str("settings_ie_sum"));
376 | preference.setText(SponsorBlockUtils.exportSettings(applicationContext));
377 | preference.setOnPreferenceChangeListener((preference1, newValue) -> {
378 | SponsorBlockUtils.importSettings((String) newValue, applicationContext);
379 | return false;
380 | });
381 | screen.addPreference(preference);
382 | preferencesToDisableWhenSBDisabled.add(preference);
383 | }
384 | }
385 |
386 | private static class APIURLChangeListener implements DialogInterface.OnClickListener {
387 | private WeakReference editTextRef;
388 |
389 | @Override
390 | public void onClick(DialogInterface dialog, int which) {
391 | EditText editText = editTextRef.get();
392 | if (editText == null)
393 | return;
394 | Context context = ((AlertDialog) dialog).getContext();
395 | Context applicationContext = context.getApplicationContext();
396 | SharedPreferences preferences = SponsorBlockSettings.getPreferences(context);
397 |
398 | switch (which) {
399 | case DialogInterface.BUTTON_NEUTRAL:
400 | preferences.edit().putString(PREFERENCES_KEY_API_URL, DEFAULT_API_URL).apply();
401 | Toast.makeText(applicationContext, str("api_url_reset"), Toast.LENGTH_SHORT).show();
402 | break;
403 | case DialogInterface.BUTTON_POSITIVE:
404 | Editable text = editText.getText();
405 | Toast invalidToast = Toast.makeText(applicationContext, str("api_url_invalid"), Toast.LENGTH_SHORT);
406 | if (text == null) {
407 | invalidToast.show();
408 | }
409 | else {
410 | String textAsString = text.toString();
411 | if (textAsString.isEmpty() || !Patterns.WEB_URL.matcher(textAsString).matches()) {
412 | invalidToast.show();
413 | }
414 | else {
415 | preferences.edit().putString(PREFERENCES_KEY_API_URL, textAsString).apply();
416 | Toast.makeText(applicationContext, str("api_url_changed"), Toast.LENGTH_SHORT).show();
417 | }
418 | }
419 | break;
420 | }
421 | }
422 |
423 | public void setEditTextRef(EditText editText) {
424 | editTextRef = new WeakReference<>(editText);
425 | }
426 | }
427 |
428 | @Override
429 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
430 | SponsorBlockSettings.update(getActivity());
431 | }
432 | }
433 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import static pl.jakubweg.StringRef.sf;
4 |
5 | import android.content.Context;
6 | import android.content.SharedPreferences;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.text.Html;
10 | import android.text.TextUtils;
11 | import android.util.Log;
12 |
13 | import java.util.ArrayList;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 | import java.util.UUID;
17 |
18 | public class SponsorBlockSettings {
19 |
20 | public static final String PREFERENCES_NAME = "sponsor-block";
21 | public static final String PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP = "show-toast";
22 | public static final String PREFERENCES_KEY_COUNT_SKIPS = "count-skips";
23 | public static final String PREFERENCES_KEY_UUID = "uuid";
24 | public static final String PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP = "new-segment-step-accuracy";
25 | public static final String PREFERENCES_KEY_MIN_DURATION = "sb-min-duration";
26 | public static final String PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED = "sb-enabled";
27 | public static final String PREFERENCES_KEY_SPONSOR_BLOCK_HINT_SHOWN = "sb_hint_shown";
28 | public static final String PREFERENCES_KEY_SEEN_GUIDELINES = "sb-seen-gl";
29 | public static final String PREFERENCES_KEY_NEW_SEGMENT_ENABLED = "sb-new-segment-enabled";
30 | public static final String PREFERENCES_KEY_VOTING_ENABLED = "sb-voting-enabled";
31 | public static final String PREFERENCES_KEY_SKIPPED_SEGMENTS = "sb-skipped-segments";
32 | public static final String PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME = "sb-skipped-segments-time";
33 | public static final String PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS = "sb-length-without-segments";
34 | public static final String PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX = "_color";
35 | public static final String PREFERENCES_KEY_BROWSER_BUTTON = "sb-browser-button";
36 | public static final String PREFERENCES_KEY_IS_VIP = "sb-is-vip";
37 | public static final String PREFERENCES_KEY_LAST_VIP_CHECK = "sb-last-vip-check";
38 | public static final String PREFERENCES_KEY_API_URL = "sb-api-url";
39 |
40 | public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SKIP_AUTOMATICALLY;
41 | public static final String DEFAULT_SERVER_URL = "https://sponsor.ajay.app";
42 | public static final String DEFAULT_API_URL = DEFAULT_SERVER_URL + "/api/";
43 |
44 | public static boolean isSponsorBlockEnabled = false;
45 | public static boolean seenGuidelinesPopup = false;
46 | public static boolean isAddNewSegmentEnabled = false;
47 | public static boolean isVotingEnabled = true;
48 | public static boolean showToastWhenSkippedAutomatically = true;
49 | public static boolean countSkips = true;
50 | public static boolean showTimeWithoutSegments = true;
51 | public static boolean vip = false;
52 | public static long lastVipCheck = 0;
53 | public static int adjustNewSegmentMillis = 150;
54 | public static float minDuration = 0f;
55 | public static String uuid = "";
56 | public static String apiUrl = DEFAULT_API_URL;
57 | public static String sponsorBlockUrlCategories = "[]";
58 | public static int skippedSegments;
59 | public static long skippedTime;
60 |
61 | @SuppressWarnings("unused")
62 | @Deprecated
63 | public SponsorBlockSettings(Context ignored) {
64 | Log.e("jakubweg.Settings", "Do not call SponsorBlockSettings constructor!");
65 | }
66 |
67 | public static SharedPreferences getPreferences(Context context) {
68 | return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
69 | }
70 |
71 | public static void setSeenGuidelines(Context context) {
72 | SponsorBlockSettings.seenGuidelinesPopup = true;
73 | getPreferences(context).edit().putBoolean(PREFERENCES_KEY_SEEN_GUIDELINES, true).apply();
74 | }
75 |
76 | public static void update(Context context) {
77 | if (context == null) return;
78 |
79 | SharedPreferences preferences = getPreferences(context);
80 | isSponsorBlockEnabled = preferences.getBoolean(PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, isSponsorBlockEnabled);
81 | seenGuidelinesPopup = preferences.getBoolean(PREFERENCES_KEY_SEEN_GUIDELINES, seenGuidelinesPopup);
82 |
83 | if (!isSponsorBlockEnabled) {
84 | SkipSegmentView.hide();
85 | NewSegmentHelperLayout.hide();
86 | SponsorBlockUtils.hideShieldButton();
87 | SponsorBlockUtils.hideVoteButton();
88 | PlayerController.sponsorSegmentsOfCurrentVideo = null;
89 | } else { /*isAddNewSegmentEnabled*/
90 | SponsorBlockUtils.showShieldButton();
91 | }
92 |
93 | isAddNewSegmentEnabled = preferences.getBoolean(PREFERENCES_KEY_NEW_SEGMENT_ENABLED, isAddNewSegmentEnabled);
94 | if (!isAddNewSegmentEnabled) {
95 | NewSegmentHelperLayout.hide();
96 | SponsorBlockUtils.hideShieldButton();
97 | } else {
98 | SponsorBlockUtils.showShieldButton();
99 | }
100 |
101 | isVotingEnabled = preferences.getBoolean(PREFERENCES_KEY_VOTING_ENABLED, isVotingEnabled);
102 | if (!isVotingEnabled)
103 | SponsorBlockUtils.hideVoteButton();
104 | else
105 | SponsorBlockUtils.showVoteButton();
106 |
107 | SegmentBehaviour[] possibleBehaviours = SegmentBehaviour.values();
108 | final ArrayList enabledCategories = new ArrayList<>(possibleBehaviours.length);
109 | for (SegmentInfo segment : SegmentInfo.values()) {
110 | String categoryColor = preferences.getString(segment.key + PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX, SponsorBlockUtils.formatColorString(segment.defaultColor));
111 | segment.setColor(Color.parseColor(categoryColor));
112 |
113 | SegmentBehaviour behaviour = null;
114 | String value = preferences.getString(segment.key, null);
115 | if (value != null) {
116 | for (SegmentBehaviour possibleBehaviour : possibleBehaviours) {
117 | if (possibleBehaviour.key.equals(value)) {
118 | behaviour = possibleBehaviour;
119 | break;
120 | }
121 | }
122 | }
123 | if (behaviour != null) {
124 | segment.behaviour = behaviour;
125 | }
126 | else {
127 | behaviour = segment.behaviour;
128 | }
129 |
130 | if (behaviour.showOnTimeBar && segment != SegmentInfo.UNSUBMITTED)
131 | enabledCategories.add(segment.key);
132 | }
133 |
134 | //"[%22sponsor%22,%22outro%22,%22music_offtopic%22,%22intro%22,%22selfpromo%22,%22interaction%22,%22preview%22]";
135 | if (enabledCategories.isEmpty())
136 | sponsorBlockUrlCategories = "[]";
137 | else
138 | sponsorBlockUrlCategories = "[%22" + TextUtils.join("%22,%22", enabledCategories) + "%22]";
139 |
140 | skippedSegments = preferences.getInt(PREFERENCES_KEY_SKIPPED_SEGMENTS, skippedSegments);
141 | skippedTime = preferences.getLong(PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME, skippedTime);
142 |
143 | showToastWhenSkippedAutomatically = preferences.getBoolean(PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP, showToastWhenSkippedAutomatically);
144 | String tmp1 = preferences.getString(PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP, null);
145 | if (tmp1 != null)
146 | adjustNewSegmentMillis = Integer.parseInt(tmp1);
147 |
148 | String minTmp = preferences.getString(PREFERENCES_KEY_MIN_DURATION, null);
149 | if (minTmp != null)
150 | minDuration = Float.parseFloat(minTmp);
151 |
152 | countSkips = preferences.getBoolean(PREFERENCES_KEY_COUNT_SKIPS, countSkips);
153 | showTimeWithoutSegments = preferences.getBoolean(PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS, showTimeWithoutSegments);
154 | vip = preferences.getBoolean(PREFERENCES_KEY_IS_VIP, false);
155 |
156 | String vipCheckTmp = preferences.getString(PREFERENCES_KEY_LAST_VIP_CHECK, null);
157 | if (vipCheckTmp != null)
158 | lastVipCheck = Long.parseLong(vipCheckTmp);
159 |
160 | apiUrl = preferences.getString(PREFERENCES_KEY_API_URL, DEFAULT_API_URL);
161 |
162 | uuid = preferences.getString(PREFERENCES_KEY_UUID, null);
163 | if (uuid == null) {
164 | uuid = (UUID.randomUUID().toString() +
165 | UUID.randomUUID().toString() +
166 | UUID.randomUUID().toString())
167 | .replace("-", "");
168 | preferences.edit().putString(PREFERENCES_KEY_UUID, uuid).apply();
169 | }
170 | }
171 |
172 | public enum SegmentBehaviour {
173 | SKIP_AUTOMATICALLY("skip", 2, sf("skip_automatically"), true, true),
174 | MANUAL_SKIP("manual-skip", 1, sf("skip_showbutton"), false, true),
175 | IGNORE("ignore", -1, sf("skip_ignore"), false, false);
176 |
177 | public final String key;
178 | public final int desktopKey;
179 | public final StringRef name;
180 | public final boolean skip;
181 | public final boolean showOnTimeBar;
182 |
183 | SegmentBehaviour(String key,
184 | int desktopKey,
185 | StringRef name,
186 | boolean skip,
187 | boolean showOnTimeBar) {
188 | this.key = key;
189 | this.desktopKey = desktopKey;
190 | this.name = name;
191 | this.skip = skip;
192 | this.showOnTimeBar = showOnTimeBar;
193 | }
194 |
195 | public static SegmentBehaviour byDesktopKey(int desktopKey) {
196 | for (SegmentBehaviour behaviour : values()) {
197 | if (behaviour.desktopKey == desktopKey) {
198 | return behaviour;
199 | }
200 | }
201 | return null;
202 | }
203 | }
204 |
205 | public enum SegmentInfo {
206 | SPONSOR("sponsor", sf("segments_sponsor"), sf("skipped_sponsor"), sf("segments_sponsor_sum"), DefaultBehaviour, 0xFF00d400),
207 | INTRO("intro", sf("segments_intermission"), sf("skipped_intermission"), sf("segments_intermission_sum"), DefaultBehaviour, 0xFF00ffff),
208 | OUTRO("outro", sf("segments_endcards"), sf("skipped_endcard"), sf("segments_endcards_sum"), DefaultBehaviour, 0xFF0202ed),
209 | INTERACTION("interaction", sf("segments_subscribe"), sf("skipped_subscribe"), sf("segments_subscribe_sum"), DefaultBehaviour, 0xFFcc00ff),
210 | SELF_PROMO("selfpromo", sf("segments_selfpromo"), sf("skipped_selfpromo"), sf("segments_selfpromo_sum"), DefaultBehaviour, 0xFFffff00),
211 | MUSIC_OFFTOPIC("music_offtopic", sf("segments_nomusic"), sf("skipped_nomusic"), sf("segments_nomusic_sum"), DefaultBehaviour, 0xFFff9900),
212 | PREVIEW("preview", sf("segments_preview"), sf("skipped_preview"), sf("segments_preview_sum"), DefaultBehaviour, 0xFF008fd6),
213 | FILLER("filler", sf("segments_filler"), sf("skipped_filler"), sf("segments_filler_sum"), SegmentBehaviour.IGNORE, 0xFF7300FF),
214 | UNSUBMITTED("unsubmitted", StringRef.empty, sf("skipped_unsubmitted"), StringRef.empty, SegmentBehaviour.SKIP_AUTOMATICALLY, 0xFFFFFFFF);
215 |
216 | private static final SegmentInfo[] mValuesWithoutUnsubmitted = new SegmentInfo[]{
217 | SPONSOR,
218 | INTRO,
219 | OUTRO,
220 | INTERACTION,
221 | SELF_PROMO,
222 | MUSIC_OFFTOPIC,
223 | PREVIEW,
224 | FILLER
225 | };
226 | private static final Map mValuesMap = new HashMap<>(values().length);
227 |
228 | static {
229 | for (SegmentInfo value : valuesWithoutUnsubmitted())
230 | mValuesMap.put(value.key, value);
231 | }
232 |
233 | public final String key;
234 | public final StringRef title;
235 | public final StringRef skipMessage;
236 | public final StringRef description;
237 | public final Paint paint;
238 | public final int defaultColor;
239 | public int color;
240 | public SegmentBehaviour behaviour;
241 |
242 | SegmentInfo(String key,
243 | StringRef title,
244 | StringRef skipMessage,
245 | StringRef description,
246 | SegmentBehaviour behaviour,
247 | int defaultColor) {
248 |
249 | this.key = key;
250 | this.title = title;
251 | this.skipMessage = skipMessage;
252 | this.description = description;
253 | this.behaviour = behaviour;
254 | this.defaultColor = defaultColor;
255 | this.color = defaultColor;
256 | this.paint = new Paint();
257 | }
258 |
259 | public static SegmentInfo[] valuesWithoutUnsubmitted() {
260 | return mValuesWithoutUnsubmitted;
261 | }
262 |
263 | public static SegmentInfo byCategoryKey(String key) {
264 | return mValuesMap.get(key);
265 | }
266 |
267 | public void setColor(int color) {
268 | color = color & 0xFFFFFF;
269 | this.color = color;
270 | paint.setColor(color);
271 | paint.setAlpha(255);
272 | }
273 |
274 | public CharSequence getTitleWithDot() {
275 | return Html.fromHtml(String.format("⬤ %s", color, title));
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/StringRef.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.util.Log;
6 |
7 | import androidx.annotation.NonNull;
8 |
9 | import java.util.HashMap;
10 |
11 | public class StringRef {
12 | public static final String TAG = "jakubweg.StringRef";
13 |
14 | private static Resources resources;
15 | private static String packageName;
16 |
17 | /**
18 | * Called in Application onCreate, should be called as soon as possible when after application startup
19 | * @param context Any context, it will be used to obtain string resources
20 | */
21 | public static void setContext(Context context) {
22 | if (context == null) return;
23 | resources = context.getApplicationContext().getResources();
24 | packageName = context.getPackageName();
25 | }
26 |
27 | private static final HashMap strings = new HashMap<>();
28 |
29 | /**
30 | * Gets strings reference from shared collection or creates if not exists yet,
31 | * this method should be called if you want to get StringRef
32 | * @param id string resource name/id
33 | * @return String reference that'll resolve to excepted string, may be from cache
34 | */
35 | @NonNull
36 | public static StringRef sf(@NonNull String id) {
37 | StringRef ref = strings.get(id);
38 | if (ref == null) {
39 | ref = new StringRef(id);
40 | strings.put(id, ref);
41 | }
42 | return ref;
43 | }
44 |
45 | /**
46 | * Gets string value by string id, shorthand for sf(id).toString()
47 | * @param id string resource name/id
48 | * @return String value from string.xml
49 | */
50 | @NonNull
51 | public static String str(@NonNull String id) {
52 | return sf(id).toString();
53 | }
54 |
55 | /**
56 | * Gets string value by string id, shorthand for sf(id).toString()
and formats the string
57 | * with given args.
58 | * @param id string resource name/id
59 | * @param args the args to format the string with
60 | * @return String value from string.xml formatted with given args
61 | */
62 | @NonNull
63 | public static String str(@NonNull String id, Object... args) {
64 | return String.format(str(id), args);
65 | }
66 |
67 |
68 | /**
69 | * Creates a StringRef object that'll not change it's value
70 | * @param value value which toString() method returns when invoked on returned object
71 | * @return Unique StringRef instance, its value will never change
72 | */
73 | @NonNull
74 | public static StringRef constant(@NonNull String value) {
75 | final StringRef ref = new StringRef(value);
76 | ref.resolved = true;
77 | return ref;
78 | }
79 |
80 | /**
81 | * Shorthand for constant("")
82 | * Its value always resolves to empty string
83 | */
84 | @NonNull
85 | public static final StringRef empty = constant("");
86 |
87 | @NonNull
88 | private String value;
89 | private boolean resolved;
90 |
91 | public StringRef(@NonNull String resName) {
92 | this.value = resName;
93 | }
94 |
95 | @Override
96 | @NonNull
97 | public String toString() {
98 | if (!resolved) {
99 | resolved = true;
100 | if (resources != null) {
101 | final int identifier = resources.getIdentifier(value, "string", packageName);
102 | if (identifier == 0)
103 | Log.e(TAG, "Resource not found: " + value);
104 | else
105 | value = resources.getString(identifier);
106 | }
107 | }
108 | return value;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/VotingButton.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import android.view.View;
6 | import android.view.animation.Animation;
7 | import android.view.animation.AnimationUtils;
8 | import android.widget.ImageView;
9 | import android.widget.RelativeLayout;
10 |
11 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
12 |
13 | import java.lang.ref.WeakReference;
14 |
15 | import static fi.razerman.youtube.XGlobals.debug;
16 | import static pl.jakubweg.PlayerController.getCurrentVideoLength;
17 | import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
18 |
19 | public class VotingButton {
20 | static String TAG = "VOTING";
21 | static RelativeLayout _youtubeControlsLayout;
22 | static WeakReference _votingButton = new WeakReference<>(null);
23 | static int fadeDurationFast;
24 | static int fadeDurationScheduled;
25 | static Animation fadeIn;
26 | static Animation fadeOut;
27 | static boolean isShowing;
28 |
29 | public static void initialize(Object viewStub) {
30 | try {
31 | if(debug){
32 | Log.d(TAG, "initializing voting button");
33 | }
34 |
35 | _youtubeControlsLayout = (RelativeLayout) viewStub;
36 |
37 | ImageView imageView = (ImageView)_youtubeControlsLayout
38 | .findViewById(getIdentifier("voting_button", "id"));
39 |
40 | if (debug && imageView == null){
41 | Log.d(TAG, "Couldn't find imageView with tag \"voting_button\"");
42 | }
43 | if (imageView == null) return;
44 | imageView.setOnClickListener(SponsorBlockUtils.voteButtonListener);
45 | _votingButton = new WeakReference<>(imageView);
46 |
47 | // Animations
48 | fadeDurationFast = getInteger("fade_duration_fast");
49 | fadeDurationScheduled = getInteger("fade_duration_scheduled");
50 | fadeIn = getAnimation("fade_in");
51 | fadeIn.setDuration(fadeDurationFast);
52 | fadeOut = getAnimation("fade_out");
53 | fadeOut.setDuration(fadeDurationScheduled);
54 | isShowing = true;
55 | changeVisibilityImmediate(false);
56 | }
57 | catch (Exception ex) {
58 | Log.e(TAG, "Unable to set RelativeLayout", ex);
59 | }
60 | }
61 |
62 | public static void changeVisibilityImmediate(boolean visible) {
63 | changeVisibility(visible, true);
64 | }
65 |
66 | public static void changeVisibilityNegatedImmediate(boolean visible) {
67 | changeVisibility(!visible, true);
68 | }
69 |
70 | public static void changeVisibility(boolean visible) {
71 | changeVisibility(visible, false);
72 | }
73 |
74 | public static void changeVisibility(boolean visible, boolean immediate) {
75 | if (isShowing == visible) return;
76 | isShowing = visible;
77 |
78 | ImageView iView = _votingButton.get();
79 | if (_youtubeControlsLayout == null || iView == null) return;
80 |
81 | if (visible && shouldBeShown()) {
82 | if (getLastKnownVideoTime() >= getCurrentVideoLength()) {
83 | return;
84 | }
85 | if (debug) {
86 | Log.d(TAG, "Fading in");
87 | }
88 | iView.setVisibility(View.VISIBLE);
89 | if (!immediate)
90 | iView.startAnimation(fadeIn);
91 | return;
92 | }
93 |
94 | if (iView.getVisibility() == View.VISIBLE) {
95 | if (debug) {
96 | Log.d(TAG, "Fading out");
97 | }
98 | if (!immediate)
99 | iView.startAnimation(fadeOut);
100 | iView.setVisibility(shouldBeShown() ? View.INVISIBLE : View.GONE);
101 | }
102 | }
103 |
104 | static boolean shouldBeShown() {
105 | return SponsorBlockUtils.isSettingEnabled(SponsorBlockSettings.isVotingEnabled);
106 | }
107 |
108 | //region Helpers
109 | private static int getIdentifier(String name, String defType) {
110 | Context context = YouTubeTikTokRoot_Application.getAppContext();
111 | return context.getResources().getIdentifier(name, defType, context.getPackageName());
112 | }
113 |
114 | private static int getInteger(String name) {
115 | return YouTubeTikTokRoot_Application.getAppContext().getResources().getInteger(getIdentifier(name, "integer"));
116 | }
117 |
118 | private static Animation getAnimation(String name) {
119 | return AnimationUtils.loadAnimation(YouTubeTikTokRoot_Application.getAppContext(), getIdentifier(name, "anim"));
120 | }
121 | //endregion
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/objects/EditTextListPreference.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg.objects;
2 |
3 | import static pl.jakubweg.SponsorBlockUtils.formatColorString;
4 | import static pl.jakubweg.StringRef.str;
5 |
6 | import android.app.AlertDialog;
7 | import android.content.Context;
8 | import android.content.DialogInterface;
9 | import android.graphics.Color;
10 | import android.preference.ListPreference;
11 | import android.text.Editable;
12 | import android.text.Html;
13 | import android.text.InputType;
14 | import android.text.TextWatcher;
15 | import android.util.AttributeSet;
16 | import android.widget.EditText;
17 | import android.widget.Toast;
18 |
19 | import pl.jakubweg.SponsorBlockSettings;
20 |
21 | @SuppressWarnings("deprecation")
22 | public class EditTextListPreference extends ListPreference {
23 |
24 | private EditText mEditText;
25 | private int mClickedDialogEntryIndex;
26 |
27 | public EditTextListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
28 | super(context, attrs, defStyleAttr, defStyleRes);
29 | }
30 |
31 | public EditTextListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
32 | super(context, attrs, defStyleAttr);
33 | }
34 |
35 | public EditTextListPreference(Context context, AttributeSet attrs) {
36 | super(context, attrs);
37 | }
38 |
39 | public EditTextListPreference(Context context) {
40 | super(context);
41 | }
42 |
43 | @Override
44 | protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
45 | SponsorBlockSettings.SegmentInfo category = getCategoryBySelf();
46 |
47 | mEditText = new EditText(builder.getContext());
48 | mEditText.setInputType(InputType.TYPE_CLASS_TEXT);
49 | mEditText.setText(formatColorString(category.color));
50 | mEditText.addTextChangedListener(new TextWatcher() {
51 | @Override
52 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
53 |
54 | @Override
55 | public void onTextChanged(CharSequence s, int start, int before, int count) {}
56 |
57 | @Override
58 | public void afterTextChanged(Editable s) {
59 | try {
60 | Color.parseColor(s.toString()); // validation
61 | getDialog().setTitle(Html.fromHtml(String.format("⬤ %s", s, category.title)));
62 | }
63 | catch (Exception ex) {}
64 | }
65 | });
66 | builder.setView(mEditText);
67 | builder.setTitle(category.getTitleWithDot());
68 |
69 | builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
70 | EditTextListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
71 | });
72 | builder.setNeutralButton(str("reset"), (dialog, which) -> {
73 | //EditTextListPreference.this.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
74 | int defaultColor = category.defaultColor;
75 | category.setColor(defaultColor);
76 | Toast.makeText(getContext().getApplicationContext(), str("color_reset"), Toast.LENGTH_SHORT).show();
77 | getSharedPreferences().edit().putString(getColorPreferenceKey(), formatColorString(defaultColor)).apply();
78 | reformatTitle();
79 | });
80 | builder.setNegativeButton(android.R.string.cancel, null);
81 |
82 | mClickedDialogEntryIndex = findIndexOfValue(getValue());
83 | builder.setSingleChoiceItems(getEntries(), mClickedDialogEntryIndex, (dialog, which) -> mClickedDialogEntryIndex = which);
84 | }
85 |
86 | @Override
87 | protected void onDialogClosed(boolean positiveResult) {
88 | if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) {
89 | String value = getEntryValues()[mClickedDialogEntryIndex].toString();
90 | if (callChangeListener(value)) {
91 | setValue(value);
92 | }
93 | String colorString = mEditText.getText().toString();
94 | SponsorBlockSettings.SegmentInfo category = getCategoryBySelf();
95 | if (colorString.equals(formatColorString(category.color))) {
96 | return;
97 | }
98 | Context applicationContext = getContext().getApplicationContext();
99 | try {
100 | int color = Color.parseColor(colorString);
101 | category.setColor(color);
102 | Toast.makeText(applicationContext, str("color_changed"), Toast.LENGTH_SHORT).show();
103 | getSharedPreferences().edit().putString(getColorPreferenceKey(), formatColorString(color)).apply();
104 | reformatTitle();
105 | }
106 | catch (Exception ex) {
107 | Toast.makeText(applicationContext, str("color_invalid"), Toast.LENGTH_SHORT).show();
108 | }
109 | }
110 | }
111 |
112 | private SponsorBlockSettings.SegmentInfo getCategoryBySelf() {
113 | return SponsorBlockSettings.SegmentInfo.byCategoryKey(getKey());
114 | }
115 |
116 | private String getColorPreferenceKey() {
117 | return getKey() + SponsorBlockSettings.PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX;
118 | }
119 |
120 | private void reformatTitle() {
121 | this.setTitle(getCategoryBySelf().getTitleWithDot());
122 | }
123 | }
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/objects/SponsorSegment.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg.objects;
2 |
3 | import pl.jakubweg.SponsorBlockSettings;
4 |
5 | public class SponsorSegment implements Comparable {
6 | public final long start;
7 | public final long end;
8 | public final SponsorBlockSettings.SegmentInfo category;
9 | public final String UUID;
10 | public final boolean isLocked;
11 |
12 | public SponsorSegment(long start, long end, SponsorBlockSettings.SegmentInfo category, String UUID, boolean isLocked) {
13 | this.start = start;
14 | this.end = end;
15 | this.category = category;
16 | this.UUID = UUID;
17 | this.isLocked = isLocked;
18 | }
19 |
20 | @Override
21 | public String toString() {
22 | return "SegmentInfo{" +
23 | "start=" + start +
24 | ", end=" + end +
25 | ", category='" + category + '\'' +
26 | ", locked=" + isLocked +
27 | '}';
28 | }
29 |
30 | @Override
31 | public int compareTo(SponsorSegment o) {
32 | return (int) (this.start - o.start);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/objects/UserStats.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg.objects;
2 |
3 | public class UserStats {
4 | private final String userName;
5 | private final double minutesSaved;
6 | private final int segmentCount;
7 | private final int viewCount;
8 |
9 | public UserStats(String userName, double minutesSaved, int segmentCount, int viewCount) {
10 | this.userName = userName;
11 | this.minutesSaved = minutesSaved;
12 | this.segmentCount = segmentCount;
13 | this.viewCount = viewCount;
14 | }
15 |
16 | public String getUserName() {
17 | return userName;
18 | }
19 |
20 | public double getMinutesSaved() {
21 | return minutesSaved;
22 | }
23 |
24 | public int getSegmentCount() {
25 | return segmentCount;
26 | }
27 |
28 | public int getViewCount() {
29 | return viewCount;
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/requests/SBRequester.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg.requests;
2 |
3 | import static android.text.Html.fromHtml;
4 | import static fi.vanced.utils.VancedUtils.runOnMainThread;
5 | import static pl.jakubweg.SponsorBlockUtils.timeWithoutSegments;
6 | import static pl.jakubweg.SponsorBlockUtils.videoHasSegments;
7 | import static pl.jakubweg.StringRef.str;
8 |
9 | import android.content.Context;
10 | import android.content.SharedPreferences;
11 | import android.preference.EditTextPreference;
12 | import android.preference.Preference;
13 | import android.preference.PreferenceCategory;
14 | import android.widget.Toast;
15 |
16 | import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
17 |
18 | import org.json.JSONArray;
19 | import org.json.JSONObject;
20 |
21 | import java.io.IOException;
22 | import java.net.HttpURLConnection;
23 | import java.util.ArrayList;
24 | import java.util.List;
25 | import java.util.Locale;
26 | import java.util.concurrent.TimeUnit;
27 |
28 | import fi.vanced.utils.requests.Requester;
29 | import fi.vanced.utils.requests.Route;
30 | import pl.jakubweg.PlayerController;
31 | import pl.jakubweg.SponsorBlockSettings;
32 | import pl.jakubweg.SponsorBlockUtils;
33 | import pl.jakubweg.SponsorBlockUtils.VoteOption;
34 | import pl.jakubweg.objects.SponsorSegment;
35 | import pl.jakubweg.objects.UserStats;
36 |
37 | public class SBRequester {
38 | private static final String TIME_TEMPLATE = "%.3f";
39 |
40 | private SBRequester() {}
41 |
42 | public static synchronized SponsorSegment[] getSegments(String videoId) {
43 | List segments = new ArrayList<>();
44 | try {
45 | HttpURLConnection connection = getConnectionFromRoute(SBRoutes.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories);
46 | int responseCode = connection.getResponseCode();
47 | runVipCheck();
48 |
49 | if (responseCode == 200) {
50 | JSONArray responseArray = Requester.getJSONArray(connection);
51 | int length = responseArray.length();
52 | for (int i = 0; i < length; i++) {
53 | JSONObject obj = (JSONObject) responseArray.get(i);
54 | JSONArray segment = obj.getJSONArray("segment");
55 | long start = (long) (segment.getDouble(0) * 1000);
56 | long end = (long) (segment.getDouble(1) * 1000);
57 |
58 | long minDuration = (long) (SponsorBlockSettings.minDuration * 1000);
59 | if ((end - start) < minDuration)
60 | continue;
61 |
62 | String category = obj.getString("category");
63 | String uuid = obj.getString("UUID");
64 | boolean locked = obj.getInt("locked") == 1;
65 |
66 | SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category);
67 | if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) {
68 | SponsorSegment sponsorSegment = new SponsorSegment(start, end, segmentCategory, uuid, locked);
69 | segments.add(sponsorSegment);
70 | }
71 | }
72 | if (!segments.isEmpty()) {
73 | videoHasSegments = true;
74 | timeWithoutSegments = SponsorBlockUtils.getTimeWithoutSegments(segments.toArray(new SponsorSegment[0]));
75 | }
76 | }
77 | connection.disconnect();
78 | }
79 | catch (Exception ex) {
80 | ex.printStackTrace();
81 | }
82 | return segments.toArray(new SponsorSegment[0]);
83 | }
84 |
85 | public static void submitSegments(String videoId, String uuid, float startTime, float endTime, String category, Runnable toastRunnable) {
86 | try {
87 | String start = String.format(Locale.US, TIME_TEMPLATE, startTime);
88 | String end = String.format(Locale.US, TIME_TEMPLATE, endTime);
89 | String duration = String.valueOf(PlayerController.getCurrentVideoLength() / 1000);
90 | HttpURLConnection connection = getConnectionFromRoute(SBRoutes.SUBMIT_SEGMENTS, videoId, uuid, start, end, category, duration);
91 | int responseCode = connection.getResponseCode();
92 |
93 | switch (responseCode) {
94 | case 200:
95 | SponsorBlockUtils.messageToToast = str("submit_succeeded");
96 | break;
97 | case 409:
98 | SponsorBlockUtils.messageToToast = str("submit_failed_duplicate");
99 | break;
100 | case 403:
101 | SponsorBlockUtils.messageToToast = str("submit_failed_forbidden", Requester.parseErrorJson(connection));
102 | break;
103 | case 429:
104 | SponsorBlockUtils.messageToToast = str("submit_failed_rate_limit");
105 | break;
106 | default:
107 | SponsorBlockUtils.messageToToast = str("submit_failed_unknown_error", responseCode, connection.getResponseMessage());
108 | break;
109 | }
110 | runOnMainThread(toastRunnable);
111 | connection.disconnect();
112 | }
113 | catch (Exception ex) {
114 | ex.printStackTrace();
115 | }
116 | }
117 |
118 | public static void sendViewCountRequest(SponsorSegment segment) {
119 | try {
120 | HttpURLConnection connection = getConnectionFromRoute(SBRoutes.VIEWED_SEGMENT, segment.UUID);
121 | connection.disconnect();
122 | }
123 | catch (Exception ex) {
124 | ex.printStackTrace();
125 | }
126 | }
127 |
128 | public static void voteForSegment(SponsorSegment segment, VoteOption voteOption, Context context, String... args) {
129 | new Thread(() -> {
130 | try {
131 | String segmentUuid = segment.UUID;
132 | String uuid = SponsorBlockSettings.uuid;
133 | String vote = Integer.toString(voteOption == VoteOption.UPVOTE ? 1 : 0);
134 |
135 | runOnMainThread(() -> Toast.makeText(context, str("vote_started"), Toast.LENGTH_SHORT).show());
136 |
137 | HttpURLConnection connection = voteOption == VoteOption.CATEGORY_CHANGE
138 | ? getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_CATEGORY, segmentUuid, uuid, args[0])
139 | : getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_QUALITY, segmentUuid, uuid, vote);
140 | int responseCode = connection.getResponseCode();
141 |
142 | switch (responseCode) {
143 | case 200:
144 | SponsorBlockUtils.messageToToast = str("vote_succeeded");
145 | break;
146 | case 403:
147 | SponsorBlockUtils.messageToToast = str("vote_failed_forbidden", Requester.parseErrorJson(connection));
148 | break;
149 | default:
150 | SponsorBlockUtils.messageToToast = str("vote_failed_unknown_error", responseCode, connection.getResponseMessage());
151 | break;
152 | }
153 | runOnMainThread(() -> Toast.makeText(context, SponsorBlockUtils.messageToToast, Toast.LENGTH_LONG).show());
154 | connection.disconnect();
155 | }
156 | catch (Exception ex) {
157 | ex.printStackTrace();
158 | }
159 | }).start();
160 | }
161 |
162 | public static void retrieveUserStats(PreferenceCategory category, Preference loadingPreference) {
163 | if (!SponsorBlockSettings.isSponsorBlockEnabled) {
164 | loadingPreference.setTitle(str("stats_sb_disabled"));
165 | return;
166 | }
167 |
168 | new Thread(() -> {
169 | try {
170 | JSONObject json = getJSONObject(SBRoutes.GET_USER_STATS, SponsorBlockSettings.uuid);
171 | UserStats stats = new UserStats(json.getString("userName"), json.getDouble("minutesSaved"), json.getInt("segmentCount"),
172 | json.getInt("viewCount"));
173 | SponsorBlockUtils.addUserStats(category, loadingPreference, stats);
174 | }
175 | catch (Exception ex) {
176 | ex.printStackTrace();
177 | }
178 | }).start();
179 | }
180 |
181 | public static void setUsername(String username, EditTextPreference preference, Runnable toastRunnable) {
182 | new Thread(() -> {
183 | try {
184 | HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.uuid, username);
185 | int responseCode = connection.getResponseCode();
186 |
187 | if (responseCode == 200) {
188 | SponsorBlockUtils.messageToToast = str("stats_username_changed");
189 | runOnMainThread(() -> {
190 | preference.setTitle(fromHtml(str("stats_username", username)));
191 | preference.setText(username);
192 | });
193 | }
194 | else {
195 | SponsorBlockUtils.messageToToast = str("stats_username_change_unknown_error", responseCode, connection.getResponseMessage());
196 | }
197 | runOnMainThread(toastRunnable);
198 | connection.disconnect();
199 | }
200 | catch (Exception ex) {
201 | ex.printStackTrace();
202 | }
203 | }).start();
204 | }
205 |
206 | public static void runVipCheck() {
207 | long now = System.currentTimeMillis();
208 | if (now < (SponsorBlockSettings.lastVipCheck + TimeUnit.DAYS.toMillis(3))) {
209 | return;
210 | }
211 | try {
212 | JSONObject json = getJSONObject(SBRoutes.IS_USER_VIP, SponsorBlockSettings.uuid);
213 | boolean vip = json.getBoolean("vip");
214 | SponsorBlockSettings.vip = vip;
215 | SponsorBlockSettings.lastVipCheck = now;
216 |
217 | SharedPreferences.Editor edit = SponsorBlockSettings.getPreferences(YouTubeTikTokRoot_Application.getAppContext()).edit();
218 | edit.putString(SponsorBlockSettings.PREFERENCES_KEY_LAST_VIP_CHECK, String.valueOf(now));
219 | edit.putBoolean(SponsorBlockSettings.PREFERENCES_KEY_IS_VIP, vip);
220 | edit.apply();
221 | }
222 | catch (Exception ex) {
223 | ex.printStackTrace();
224 | }
225 | }
226 |
227 | // helpers
228 |
229 | private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
230 | return Requester.getConnectionFromRoute(SponsorBlockSettings.apiUrl, route, params);
231 | }
232 |
233 | private static JSONObject getJSONObject(Route route, String... params) throws Exception {
234 | return Requester.getJSONObject(getConnectionFromRoute(route, params));
235 | }
236 | }
--------------------------------------------------------------------------------
/app/src/main/java/pl/jakubweg/requests/SBRoutes.java:
--------------------------------------------------------------------------------
1 | package pl.jakubweg.requests;
2 |
3 | import static fi.vanced.utils.requests.Route.Method.GET;
4 | import static fi.vanced.utils.requests.Route.Method.POST;
5 |
6 | import fi.vanced.utils.requests.Route;
7 |
8 | public class SBRoutes {
9 | public static final Route IS_USER_VIP = new Route(GET, "isUserVIP?userID={user_id}");
10 | public static final Route GET_SEGMENTS = new Route(GET, "skipSegments?videoID={video_id}&categories={categories}");
11 | public static final Route VIEWED_SEGMENT = new Route(POST, "viewedVideoSponsorTime?UUID={segment_id}");
12 | public static final Route GET_USER_STATS = new Route(GET, "userInfo?userID={user_id}&values=[\"userName\", \"minutesSaved\", \"segmentCount\", \"viewCount\"]");
13 | public static final Route CHANGE_USERNAME = new Route(POST, "setUsername?userID={user_id}&username={username}");
14 | 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}");
15 | public static final Route VOTE_ON_SEGMENT_QUALITY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&type={type}");
16 | public static final Route VOTE_ON_SEGMENT_CATEGORY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&category={category}");
17 |
18 | private SBRoutes() {}
19 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_forward_grey600_36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamVanced/Integrations/5ac2fec196a188d86a7171ef2f056a56c205c385/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_forward_grey600_36.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_forward_white_36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamVanced/Integrations/5ac2fec196a188d86a7171ef2f056a56c205c385/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_forward_white_36.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_rewind_grey600_36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamVanced/Integrations/5ac2fec196a188d86a7171ef2f056a56c205c385/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_rewind_grey600_36.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_rewind_white_36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamVanced/Integrations/5ac2fec196a188d86a7171ef2f056a56c205c385/app/src/main/res/drawable-xxxhdpi/quantum_ic_fast_rewind_white_36.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sb_adjust.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sb_compare.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sb_edit.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sb_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sb_publish.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sb_voting.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/player_fast_forward.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/player_fast_rewind.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/inline_sponsor_overlay.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/new_segment.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/skip_sponsor_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamVanced/Integrations/5ac2fec196a188d86a7171ef2f056a56c205c385/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @string/vanced_button_location_entry_none
5 | - @string/vanced_button_location_entry_player
6 | - @string/vanced_button_location_entry_buttoncontainer
7 | - @string/vanced_button_location_entry_both
8 |
9 |
10 | - NONE
11 | - PLAYER
12 | - BUTTON_CONTAINER
13 | - BOTH
14 |
15 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:7.1.1'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamVanced/Integrations/5ac2fec196a188d86a7171ef2f056a56c205c385/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 07 19:51:48 CEST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ]; do
14 | ls=$(ls -ld "$PRG")
15 | link=$(expr "$ls" : '.*-> \(.*\)$')
16 | if expr "$link" : '/.*' >/dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=$(dirname "$PRG")"/$link"
20 | fi
21 | done
22 | SAVED="$(pwd)"
23 | cd "$(dirname \"$PRG\")/" >/dev/null
24 | APP_HOME="$(pwd -P)"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=$(basename "$0")
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn() {
37 | echo "$*"
38 | }
39 |
40 | die() {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "$(uname)" in
53 | CYGWIN*)
54 | cygwin=true
55 | ;;
56 | Darwin*)
57 | darwin=true
58 | ;;
59 | MINGW*)
60 | msys=true
61 | ;;
62 | NONSTOP*)
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ]; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ]; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then
93 | MAX_FD_LIMIT=$(ulimit -H -n)
94 | if [ $? -eq 0 ]; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ]; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin; then
114 | APP_HOME=$(cygpath --path --mixed "$APP_HOME")
115 | CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
116 | JAVACMD=$(cygpath --unix "$JAVACMD")
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
120 | SEP=""
121 | for dir in $ROOTDIRSRAW; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ]; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@"; do
133 | CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -)
134 | CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition
137 | eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
138 | else
139 | eval $(echo args$i)="\"$arg\""
140 | fi
141 | i=$((i + 1))
142 | done
143 | case $i in
144 | 0) set -- ;;
145 | 1) set -- "$args0" ;;
146 | 2) set -- "$args0" "$args1" ;;
147 | 3) set -- "$args0" "$args1" "$args2" ;;
148 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save() {
159 | for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "sb"
--------------------------------------------------------------------------------