Merge remote-tracking branch 'upstream/master' into feature/configurable-colors

# Conflicts:
#	app/src/main/java/pl/jakubweg/SponsorBlockPreferenceFragment.java
This commit is contained in:
caneleex 2021-07-26 20:11:18 +02:00
commit 4fc6d6630e
25 changed files with 1040 additions and 401 deletions

View File

@ -1,8 +1,10 @@
# SponsorBlock YouTube Vanced Implementation
In order to use this in YouTube/Vanced you must first apply the smali mods outlined in smali.md (if you mod vanced directly it is not required)
* First make your edits in android studio and then compile the code to a debug apk
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)
* First make your edits in android studio
* Change the string "replaceMeWithsetMillisecondMethod" on PlayerController.java to the method name of YouTube package
* Compile debug apk
* Decompile this apk using apktool https://github.com/iBotPeaches/Apktool
* Take this decompiled folder and look for a folder labeled pl in one of your dex class folders (usually the second one)
* 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))
* Copy the pl folder from earlier into the 4th dex class folder (remove any existing one completely first)
* Take this decompiled folder and look for a folder labeled pl in one of your dex class folders
* 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))
* Copy the pl folder from earlier into the dex class folder (remove any existing one completely first)
* Recompile your modded YouTube/Vanced using apktool and sign it + all splits required for your device using the same key

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "pl.jakubweg"
@ -10,8 +10,7 @@ android {
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled false
}
buildTypes {
@ -20,13 +19,13 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.2.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.annotation:annotation:1.2.0'
}
}

View File

@ -1,12 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="pl.jakubweg">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
tools:ignore="AllowBackup" />
</manifest>

View File

@ -4,7 +4,7 @@ import android.app.Application;
import android.content.Context;
import android.os.Bundle;
public class YouTubeApplication extends Application {
public class YouTubeTikTokRoot_Application extends Application {
protected void onCreate(final Bundle bundle) {
super.onCreate();
}

View File

@ -0,0 +1,8 @@
package fi.razerman.youtube.Helpers;
import android.view.ViewGroup;
public class XSwipeHelper {
// Implementation in another repo
public static ViewGroup nextGenWatchLayout;
}

View File

@ -1,9 +1,11 @@
package fi.vanced.libraries.youtube.player;
import fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView;
import pl.jakubweg.SponsorBlockUtils;
public class PlayerType {
public static void playerTypeChanged(String playerType) {
SponsorBlockView.playerTypeChanged(playerType);
SponsorBlockUtils.playerTypeChanged(playerType);
}
}

View File

@ -1,20 +1,16 @@
package fi.vanced.libraries.youtube.sponsors.player.ui;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

View File

@ -7,10 +7,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import com.google.android.apps.youtube.app.YouTubeApplication;
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
import java.lang.ref.WeakReference;
import fi.razerman.youtube.Helpers.XSwipeHelper;
import static fi.razerman.youtube.XGlobals.debug;
public class SponsorBlockView {
@ -69,9 +71,9 @@ public class SponsorBlockView {
}
private static void addView() {
inlineSponsorOverlay = new RelativeLayout(YouTubeApplication.getAppContext());
inlineSponsorOverlay = new RelativeLayout(YouTubeTikTokRoot_Application.getAppContext());
setLayoutParams(inlineSponsorOverlay);
LayoutInflater.from(YouTubeApplication.getAppContext()).inflate(getIdentifier("inline_sponsor_overlay", "layout"), inlineSponsorOverlay);
LayoutInflater.from(YouTubeTikTokRoot_Application.getAppContext()).inflate(getIdentifier("inline_sponsor_overlay", "layout"), inlineSponsorOverlay);
_youtubeOverlaysLayout.addView(inlineSponsorOverlay, _youtubeOverlaysLayout.getChildCount() - 2);
@ -145,13 +147,31 @@ public class SponsorBlockView {
}
private static void bringLayoutToFront() {
checkLayout();
inlineSponsorOverlay.bringToFront();
inlineSponsorOverlay.requestLayout();
inlineSponsorOverlay.invalidate();
}
private static void checkLayout() {
if (inlineSponsorOverlay.getHeight() == 0) {
View layout = XSwipeHelper.nextGenWatchLayout.findViewById(getIdentifier("player_overlays", "id"));
if (layout != null) {
initialize(layout);
if (debug){
Log.d("XGlobals", "player_overlays refreshed for SB");
}
}
else if (debug){
Log.d("XGlobals", "player_overlays was not found for SB");
}
}
}
private static int getIdentifier(String name, String defType) {
Context context = YouTubeApplication.getAppContext();
Context context = YouTubeTikTokRoot_Application.getAppContext();
return context.getResources().getIdentifier(name, defType, context.getPackageName());
}
}

View File

@ -92,7 +92,6 @@ public class InjectedPlugin {
Log.i(TAG, spacesStr + "Normal view: " + view);
}
}
}

View File

@ -3,6 +3,7 @@ package pl.jakubweg;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
@ -10,15 +11,22 @@ import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import fi.vanced.libraries.youtube.player.VideoInformation;
import pl.jakubweg.objects.SponsorSegment;
import pl.jakubweg.requests.Requester;
import static pl.jakubweg.SponsorBlockSettings.skippedSegments;
import static pl.jakubweg.SponsorBlockSettings.skippedTime;
@SuppressLint({"LongLogTag"})
public class PlayerController {
@ -36,12 +44,9 @@ public class PlayerController {
private static String currentVideoId;
private static long currentVideoLength = 1L;
private static long lastKnownVideoTime = -1L;
private static final Runnable findAndSkipSegmentRunnable = new Runnable() {
@Override
public void run() {
private static final Runnable findAndSkipSegmentRunnable = () -> {
// Log.d(TAG, "findAndSkipSegmentRunnable");
findAndSkipSegment(false);
}
findAndSkipSegment(false);
};
private static float sponsorBarLeft = 1f;
private static float sponsorBarRight = 1f;
@ -54,20 +59,25 @@ public class PlayerController {
public static void setCurrentVideoId(final String videoId) {
if (videoId == null) {
Log.d(TAG, "setCurrentVideoId: videoId is null");
currentVideoId = null;
sponsorSegmentsOfCurrentVideo = null;
return;
}
VideoInformation.currentVideoId = videoId;
Context context = YouTubeTikTokRoot_Application.getAppContext();
if(context == null){
Log.e(TAG, "context is null");
return;
}
SponsorBlockSettings.update(context);
if (!SponsorBlockSettings.isSponsorBlockEnabled) {
currentVideoId = null;
return;
}
if (Looper.myLooper() != Looper.getMainLooper()) // check if thread is not main
return;
if (videoId.equals(currentVideoId))
return;
@ -115,7 +125,7 @@ public class PlayerController {
}
public static void executeDownloadSegments(String videoId) {
SponsorSegment[] segments = SponsorBlockUtils.getSegmentsForVideo(videoId);
SponsorSegment[] segments = Requester.getSegments(videoId);
Arrays.sort(segments);
if (VERBOSE)
@ -202,6 +212,12 @@ public class PlayerController {
if (millis <= 0) return;
//findAndSkipSegment(false);
if (millis == currentVideoLength) {
SponsorBlockUtils.hideShieldButton();
SponsorBlockUtils.hideVoteButton();
return;
}
SponsorSegment[] segments = sponsorSegmentsOfCurrentVideo;
if (segments == null || segments.length == 0) return;
@ -253,15 +269,21 @@ public class PlayerController {
}
private static void sendViewRequestAsync(final long millis, final SponsorSegment segment) {
new Thread(new Runnable() {
@Override
public void run() {
if (SponsorBlockSettings.countSkips &&
segment.category != SponsorBlockSettings.SegmentInfo.Preview &&
millis - segment.start < 2000) {
// Only skips from the start should count as a view
SponsorBlockUtils.sendViewCountRequest(segment);
}
if (segment.category != SponsorBlockSettings.SegmentInfo.UNSUBMITTED) {
Context context = YouTubeTikTokRoot_Application.getAppContext();
if (context != null) {
SharedPreferences preferences = SponsorBlockSettings.getPreferences(context);
long newSkippedTime = skippedTime + (segment.end - segment.start);
preferences.edit().putInt(SponsorBlockSettings.PREFERENCES_KEY_SKIPPED_SEGMENTS, skippedSegments + 1).apply();
preferences.edit().putLong(SponsorBlockSettings.PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME, newSkippedTime).apply();
}
}
new Thread(() -> {
if (SponsorBlockSettings.countSkips &&
segment.category != SponsorBlockSettings.SegmentInfo.UNSUBMITTED &&
millis - segment.start < 2000) {
// Only skips from the start should count as a view
Requester.sendViewCountRequest(segment);
}
}).start();
}
@ -270,6 +292,10 @@ public class PlayerController {
* Called very high frequency (once every about 100ms), also in background. It sometimes triggers when a video is paused (couple times in the row with the same value)
*/
public static void setCurrentVideoTimeHighPrecision(final long millis) {
if ((millis < lastKnownVideoTime && lastKnownVideoTime >= currentVideoLength) || millis == 0) {
SponsorBlockUtils.showShieldButton(); // skipping from end to the video will show the buttons again
SponsorBlockUtils.showVoteButton();
}
if (lastKnownVideoTime > 0) {
lastKnownVideoTime = millis;
VideoInformation.lastKnownVideoTime = lastKnownVideoTime;
@ -278,6 +304,10 @@ public class PlayerController {
setCurrentVideoTime(millis);
}
public static long getCurrentVideoLength() {
return currentVideoLength;
}
public static long getLastKnownVideoTime() {
return lastKnownVideoTime;
}
@ -305,7 +335,9 @@ public class PlayerController {
public static void setSponsorBarRect(final Object self) {
try {
Rect rect = ((Rect) self.getClass().getField("e").get(self));
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
field.setAccessible(true);
Rect rect = (Rect) field.get(self);
if (rect != null) {
setSponsorBarAbsoluteLeft(rect.left);
setSponsorBarAbsoluteRight(rect.right);
@ -349,13 +381,10 @@ public class PlayerController {
if (VERBOSE)
Log.d(TAG, "addSkipSponsorView15: view=" + view.toString());
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
final ViewGroup viewGroup = (ViewGroup) ((ViewGroup) view).getChildAt(2);
Activity context = ((Activity) viewGroup.getContext());
NewSegmentHelperLayout.context = context;
}
new Handler(Looper.getMainLooper()).postDelayed(() -> {
final ViewGroup viewGroup = (ViewGroup) ((ViewGroup) view).getChildAt(2);
Activity context = ((Activity) viewGroup.getContext());
NewSegmentHelperLayout.context = context;
}, 500);
}
@ -363,13 +392,10 @@ public class PlayerController {
playerActivity = new WeakReference<>((Activity) view.getContext());
if (VERBOSE)
Log.d(TAG, "addSkipSponsorView14: view=" + view.toString());
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
final ViewGroup viewGroup = (ViewGroup) view.getParent();
Activity activity = (Activity) viewGroup.getContext();
NewSegmentHelperLayout.context = activity;
}
new Handler(Looper.getMainLooper()).postDelayed(() -> {
final ViewGroup viewGroup = (ViewGroup) view.getParent();
Activity activity = (Activity) viewGroup.getContext();
NewSegmentHelperLayout.context = activity;
}, 500);
}
@ -433,18 +459,15 @@ public class PlayerController {
Log.d(TAG, String.format("Requesting skip to millis=%d on thread %s", millisecond, Thread.currentThread().toString()));
final long finalMillisecond = millisecond;
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
try {
if (VERBOSE)
Log.i(TAG, "Skipping to millis=" + finalMillisecond);
lastKnownVideoTime = finalMillisecond;
VideoInformation.lastKnownVideoTime = lastKnownVideoTime;
setMillisecondMethod.invoke(currentObj, finalMillisecond);
} catch (Exception e) {
Log.e(TAG, "Cannot skip to millisecond", e);
}
new Handler(Looper.getMainLooper()).post(() -> {
try {
if (VERBOSE)
Log.i(TAG, "Skipping to millis=" + finalMillisecond);
lastKnownVideoTime = finalMillisecond;
VideoInformation.lastKnownVideoTime = lastKnownVideoTime;
setMillisecondMethod.invoke(currentObj, finalMillisecond);
} catch (Exception e) {
Log.e(TAG, "Cannot skip to millisecond", e);
}
});
}
@ -486,7 +509,7 @@ public class PlayerController {
skipToMillisecond(segment.end + 2);
SkipSegmentView.hide();
if (segment.category == SponsorBlockSettings.SegmentInfo.Preview) {
if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) {
SponsorSegment[] newSegments = new SponsorSegment[sponsorSegmentsOfCurrentVideo.length - 1];
int i = 0;
for (SponsorSegment sponsorSegment : sponsorSegmentsOfCurrentVideo) {

View File

@ -1,7 +1,6 @@
package pl.jakubweg;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
@ -9,11 +8,13 @@ import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.google.android.apps.youtube.app.YouTubeApplication;
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
import java.lang.ref.WeakReference;
import static fi.razerman.youtube.XGlobals.debug;
import static pl.jakubweg.PlayerController.getCurrentVideoLength;
import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
public class ShieldButton {
static String TAG = "SHIELD";
@ -32,7 +33,6 @@ public class ShieldButton {
}
_youtubeControlsLayout = (RelativeLayout) viewStub;
initButtonVisibilitySettings();
ImageView imageView = (ImageView)_youtubeControlsLayout
.findViewById(getIdentifier("sponsorblock_button", "id"));
@ -79,6 +79,9 @@ public class ShieldButton {
if (_youtubeControlsLayout == null || iView == null) return;
if (visible && shouldBeShown()) {
if (getLastKnownVideoTime() >= getCurrentVideoLength()) {
return;
}
if (debug) {
Log.d(TAG, "Fading in");
}
@ -98,36 +101,22 @@ public class ShieldButton {
}
}
private static boolean shouldBeShown() {
return SponsorBlockSettings.isSponsorBlockEnabled && SponsorBlockSettings.isAddNewSegmentEnabled;
}
private static void initButtonVisibilitySettings() {
Context context = YouTubeApplication.getAppContext();
if(context == null){
Log.e(TAG, "context is null");
SponsorBlockSettings.isSponsorBlockEnabled = false;
SponsorBlockSettings.isAddNewSegmentEnabled = false;
return;
}
SharedPreferences sharedPreferences = context.getSharedPreferences(SponsorBlockSettings.PREFERENCES_NAME, Context.MODE_PRIVATE);
SponsorBlockSettings.isSponsorBlockEnabled = sharedPreferences.getBoolean(SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED, false);
SponsorBlockSettings.isAddNewSegmentEnabled = sharedPreferences.getBoolean(SponsorBlockSettings.PREFERENCES_KEY_NEW_SEGMENT_ENABLED, false);
static boolean shouldBeShown() {
return SponsorBlockUtils.isSettingEnabled(SponsorBlockSettings.isAddNewSegmentEnabled);
}
//region Helpers
private static int getIdentifier(String name, String defType) {
Context context = YouTubeApplication.getAppContext();
Context context = YouTubeTikTokRoot_Application.getAppContext();
return context.getResources().getIdentifier(name, defType, context.getPackageName());
}
private static int getInteger(String name) {
return YouTubeApplication.getAppContext().getResources().getInteger(getIdentifier(name, "integer"));
return YouTubeTikTokRoot_Application.getAppContext().getResources().getInteger(getIdentifier(name, "integer"));
}
private static Animation getAnimation(String name) {
return AnimationUtils.loadAnimation(YouTubeApplication.getAppContext(), getIdentifier(name, "anim"));
return AnimationUtils.loadAnimation(YouTubeTikTokRoot_Application.getAppContext(), getIdentifier(name, "anim"));
}
//endregion
}

View File

@ -6,14 +6,13 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.Toast;
import com.google.android.apps.youtube.app.YouTubeApplication;
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
import java.lang.ref.WeakReference;
import pl.jakubweg.objects.SponsorSegment;
import static fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView.hideSkipButton;
import static fi.vanced.libraries.youtube.sponsors.player.ui.SponsorBlockView.showSkipButton;
import static pl.jakubweg.PlayerController.VERBOSE;
import static pl.jakubweg.StringRef.str;
@SuppressLint({"RtlHardcoded", "SetTextI18n", "LongLogTag", "AppCompatCustomView"})
public class SkipSegmentView {
@ -36,7 +35,7 @@ public class SkipSegmentView {
}
lastNotifiedSegment = segment;
String skipMessage = segment.category.skipMessage.toString();
Context context = YouTubeApplication.getAppContext();
Context context = YouTubeTikTokRoot_Application.getAppContext();
if (VERBOSE)
Log.d(TAG, String.format("notifySkipped; message=%s", skipMessage));

View File

@ -3,7 +3,6 @@ package pl.jakubweg;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
@ -18,28 +17,34 @@ import android.preference.SwitchPreference;
import android.text.InputType;
import android.widget.Toast;
import java.text.DecimalFormat;
import java.util.ArrayList;
import pl.jakubweg.requests.Requester;
import static pl.jakubweg.SponsorBlockSettings.DefaultBehaviour;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_COUNT_SKIPS;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_NEW_SEGMENT_ENABLED;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_UUID;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_KEY_VOTING_ENABLED;
import static pl.jakubweg.SponsorBlockSettings.PREFERENCES_NAME;
import static pl.jakubweg.SponsorBlockSettings.adjustNewSegmentMillis;
import static pl.jakubweg.SponsorBlockSettings.countSkips;
import static pl.jakubweg.SponsorBlockSettings.setSeenGuidelines;
import static pl.jakubweg.SponsorBlockSettings.showTimeWithoutSegments;
import static pl.jakubweg.SponsorBlockSettings.showToastWhenSkippedAutomatically;
import static pl.jakubweg.SponsorBlockSettings.uuid;
import static pl.jakubweg.StringRef.str;
@SuppressWarnings({"unused", "deprecation"}) // injected
public class SponsorBlockPreferenceFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
private ArrayList<Preference> preferencesToDisableWhenSBDisabled = new ArrayList<>();
public static final DecimalFormat FORMATTER = new DecimalFormat("#,###,###");
public static final String SAVED_TEMPLATE = "%dh %.1f minutes";
private final ArrayList<Preference> preferencesToDisableWhenSBDisabled = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
@ -53,6 +58,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
setPreferenceScreen(preferenceScreen);
SponsorBlockSettings.update(context);
{
SwitchPreference preference = new SwitchPreference(context);
preferenceScreen.addPreference(preference);
@ -61,13 +68,10 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
preference.setChecked(SponsorBlockSettings.isSponsorBlockEnabled);
preference.setTitle(str("enable_sb"));
preference.setSummary(str("enable_sb_sum"));
preference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean value = (Boolean) newValue;
enableCategoriesIfNeeded(value);
return true;
}
preference.setOnPreferenceChangeListener((preference1, newValue) -> {
final boolean value = (Boolean) newValue;
enableCategoriesIfNeeded(value);
return true;
});
}
@ -80,30 +84,34 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
preference.setTitle(str("enable_segmadding"));
preference.setSummary(str("enable_segmadding_sum"));
preferencesToDisableWhenSBDisabled.add(preference);
preference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
final boolean value = (Boolean) o;
if (value && !SponsorBlockSettings.seenGuidelinesPopup) {
new AlertDialog.Builder(preference.getContext())
.setTitle(str("sb_guidelines_popup_title"))
.setMessage(str("sb_guidelines_popup_content"))
.setNegativeButton(str("sb_guidelines_popup_already_read"), null)
.setPositiveButton(str("sb_guidelines_popup_open"), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
openGuidelines();
}
})
.show();
}
return true;
preference.setOnPreferenceChangeListener((preference12, o) -> {
final boolean value = (Boolean) o;
if (value && !SponsorBlockSettings.seenGuidelinesPopup) {
new AlertDialog.Builder(preference12.getContext())
.setTitle(str("sb_guidelines_popup_title"))
.setMessage(str("sb_guidelines_popup_content"))
.setNegativeButton(str("sb_guidelines_popup_already_read"), null)
.setPositiveButton(str("sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines())
.show();
}
return true;
});
}
{
SwitchPreference preference = new SwitchPreference(context);
preferenceScreen.addPreference(preference);
preference.setTitle(str("enable_voting"));
preference.setSummary(str("enable_voting_sum"));
preference.setKey(PREFERENCES_KEY_VOTING_ENABLED);
preference.setDefaultValue(SponsorBlockSettings.isVotingEnabled);
preference.setChecked(SponsorBlockSettings.isVotingEnabled);
preferencesToDisableWhenSBDisabled.add(preference);
}
addGeneralCategory(context, preferenceScreen);
addSegmentsCategory(context, preferenceScreen);
addStatsCategory(context, preferenceScreen);
addAboutCategory(context, preferenceScreen);
enableCategoriesIfNeeded(SponsorBlockSettings.isSponsorBlockEnabled);
@ -145,7 +153,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
entryValues[i] = behaviour.key;
}
for (SponsorBlockSettings.SegmentInfo segmentInfo : SponsorBlockSettings.SegmentInfo.valuesWithoutPreview()) {
for (SponsorBlockSettings.SegmentInfo segmentInfo : SponsorBlockSettings.SegmentInfo.valuesWithoutUnsubmitted()) {
ListPreference preference = new ListPreference(context);
preference.setTitle(segmentInfo.getTitleWithDot());
preference.setSummary(segmentInfo.description.toString());
@ -167,6 +175,21 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
}
private void addStatsCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("stats"));
preferencesToDisableWhenSBDisabled.add(category);
{
Preference preference = new Preference(context);
category.addPreference(preference);
preference.setTitle(str("stats_loading"));
Requester.retrieveUserStats(category, preference);
}
}
private void addAboutCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
@ -177,14 +200,11 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
screen.addPreference(preference);
preference.setTitle(str("about_api"));
preference.setSummary(str("about_api_sum"));
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://sponsor.ajay.app"));
preference.getContext().startActivity(i);
return false;
}
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sponsor.ajay.app"));
preference1.getContext().startActivity(i);
return false;
});
}
@ -206,12 +226,9 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
Preference preference = new Preference(context);
preference.setTitle(str("sb_guidelines_preference_title"));
preference.setSummary(str("sb_guidelines_preference_sum"));
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
openGuidelines();
return false;
}
preference.setOnPreferenceClickListener(preference1 -> {
openGuidelines();
return false;
});
screen.addPreference(preference);
}
@ -222,12 +239,9 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
preference.setSummary(str("general_skiptoast_sum"));
preference.setKey(PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP);
preference.setDefaultValue(showToastWhenSkippedAutomatically);
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Toast.makeText(preference.getContext(), str("skipped_sponsor"), Toast.LENGTH_SHORT).show();
return false;
}
preference.setOnPreferenceClickListener(preference12 -> {
Toast.makeText(preference12.getContext(), str("skipped_sponsor"), Toast.LENGTH_SHORT).show();
return false;
});
preferencesToDisableWhenSBDisabled.add(preference);
screen.addPreference(preference);
@ -243,6 +257,16 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement
screen.addPreference(preference);
}
{
Preference preference = new SwitchPreference(context);
preference.setTitle(str("general_time_without_sb"));
preference.setSummary(str("general_time_without_sb_sum"));
preference.setKey(PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS);
preference.setDefaultValue(showTimeWithoutSegments);
preferencesToDisableWhenSBDisabled.add(preference);
screen.addPreference(preference);
}
{
EditTextPreference preference = new EditTextPreference(context);
preference.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);

View File

@ -24,20 +24,25 @@ public class SponsorBlockSettings {
public static final String PREFERENCES_KEY_SPONSOR_BLOCK_ENABLED = "sb-enabled";
public static final String PREFERENCES_KEY_SEEN_GUIDELINES = "sb-seen-gl";
public static final String PREFERENCES_KEY_NEW_SEGMENT_ENABLED = "sb-new-segment-enabled";
public static final String sponsorBlockSkipSegmentsUrl = "https://sponsor.ajay.app/api/skipSegments";
public static final String sponsorBlockViewedUrl = "https://sponsor.ajay.app/api/viewedVideoSponsorTime";
public static final String PREFERENCES_KEY_VOTING_ENABLED = "sb-voting-enabled";
public static final String PREFERENCES_KEY_SKIPPED_SEGMENTS = "sb-skipped-segments";
public static final String PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME = "sb-skipped-segments-time";
public static final String PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS = "sb-length-without-segments";
public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SkipAutomatically;
public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SKIP_AUTOMATICALLY;
public static boolean isSponsorBlockEnabled = false;
public static boolean seenGuidelinesPopup = false;
public static boolean isAddNewSegmentEnabled = false;
public static boolean isVotingEnabled = true;
public static boolean showToastWhenSkippedAutomatically = true;
public static boolean countSkips = true;
public static boolean showTimeWithoutSegments = true;
public static int adjustNewSegmentMillis = 150;
public static String uuid = "<invalid>";
private static String sponsorBlockUrlCategories = "[]";
public static String sponsorBlockUrlCategories = "[]";
public static int skippedSegments;
public static long skippedTime;
@SuppressWarnings("unused")
@Deprecated
@ -45,14 +50,6 @@ public class SponsorBlockSettings {
Log.e("jakubweg.Settings", "Do not call SponsorBlockSettings constructor!");
}
public static String getSponsorBlockUrlWithCategories(String videoId) {
return sponsorBlockSkipSegmentsUrl + "?videoID=" + videoId + "&categories=" + sponsorBlockUrlCategories;
}
public static String getSponsorBlockViewedUrl(String UUID) {
return sponsorBlockViewedUrl + "?UUID=" + UUID;
}
public static SharedPreferences getPreferences(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@ -72,23 +69,30 @@ public class SponsorBlockSettings {
if (!isSponsorBlockEnabled) {
SkipSegmentView.hide();
NewSegmentHelperLayout.hide();
SponsorBlockUtils.hideButton();
SponsorBlockUtils.hideShieldButton();
SponsorBlockUtils.hideVoteButton();
PlayerController.sponsorSegmentsOfCurrentVideo = null;
} else if (/*isAddNewSegmentEnabled*/false) {
SponsorBlockUtils.showButton();
} else { /*isAddNewSegmentEnabled*/
SponsorBlockUtils.showShieldButton();
}
isAddNewSegmentEnabled = preferences.getBoolean(PREFERENCES_KEY_NEW_SEGMENT_ENABLED, isAddNewSegmentEnabled);
if (!/*isAddNewSegmentEnabled*/false) {
if (!isAddNewSegmentEnabled) {
NewSegmentHelperLayout.hide();
SponsorBlockUtils.hideButton();
SponsorBlockUtils.hideShieldButton();
} else {
SponsorBlockUtils.showButton();
SponsorBlockUtils.showShieldButton();
}
isVotingEnabled = preferences.getBoolean(PREFERENCES_KEY_VOTING_ENABLED, isVotingEnabled);
if (!isVotingEnabled)
SponsorBlockUtils.hideVoteButton();
else
SponsorBlockUtils.showVoteButton();
SegmentBehaviour[] possibleBehaviours = SegmentBehaviour.values();
final ArrayList<String> enabledCategories = new ArrayList<>(possibleBehaviours.length);
for (SegmentInfo segment : SegmentInfo.valuesWithoutPreview()) {
for (SegmentInfo segment : SegmentInfo.valuesWithoutUnsubmitted()) {
SegmentBehaviour behaviour = null;
String value = preferences.getString(segment.key, null);
if (value == null)
@ -112,18 +116,22 @@ public class SponsorBlockSettings {
segment.setColor(Integer.parseInt(tmp));
}
//"[%22sponsor%22,%22outro%22,%22music_offtopic%22,%22intro%22,%22selfpromo%22,%22interaction%22]";
//"[%22sponsor%22,%22outro%22,%22music_offtopic%22,%22intro%22,%22selfpromo%22,%22interaction%22,%22preview%22]";
if (enabledCategories.size() == 0)
sponsorBlockUrlCategories = "[]";
else
sponsorBlockUrlCategories = "[%22" + TextUtils.join("%22,%22", enabledCategories) + "%22]";
skippedSegments = preferences.getInt(PREFERENCES_KEY_SKIPPED_SEGMENTS, skippedSegments);
skippedTime = preferences.getLong(PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME, skippedTime);
showToastWhenSkippedAutomatically = preferences.getBoolean(PREFERENCES_KEY_SHOW_TOAST_WHEN_SKIP, showToastWhenSkippedAutomatically);
String tmp1 = preferences.getString(PREFERENCES_KEY_ADJUST_NEW_SEGMENT_STEP, null);
if (tmp1 != null)
adjustNewSegmentMillis = Integer.parseInt(tmp1);
countSkips = preferences.getBoolean(PREFERENCES_KEY_COUNT_SKIPS, countSkips);
showTimeWithoutSegments = preferences.getBoolean(PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS, showTimeWithoutSegments);
uuid = preferences.getString(PREFERENCES_KEY_UUID, null);
if (uuid == null) {
@ -136,9 +144,9 @@ public class SponsorBlockSettings {
}
public enum SegmentBehaviour {
SkipAutomatically("skip", sf("skip_automatically"), true, true),
ManualSkip("manual-skip", sf("skip_showbutton"), false, true),
Ignore("ignore", sf("skip_ignore"), false, false);
SKIP_AUTOMATICALLY("skip", sf("skip_automatically"), true, true),
MANUAL_SKIP("manual-skip", sf("skip_showbutton"), false, true),
IGNORE("ignore", sf("skip_ignore"), false, false);
public final String key;
public final StringRef name;
@ -157,27 +165,28 @@ public class SponsorBlockSettings {
}
public enum SegmentInfo {
Sponsor("sponsor", sf("segments_sponsor"), sf("skipped_sponsor"), sf("segments_sponsor_sum"), null, 0xFF00d400),
Intro("intro", sf("segments_intermission"), sf("skipped_intermission"), sf("segments_intermission_sum"), null, 0xFF00ffff),
Outro("outro", sf("segments_endcards"), sf("skipped_endcard"), sf("segments_endcards_sum"), null, 0xFF0202ed),
Interaction("interaction", sf("segments_subscribe"), sf("skipped_subscribe"), sf("segments_subscribe_sum"), null, 0xFFcc00ff),
SelfPromo("selfpromo", sf("segments_selfpromo"), sf("skipped_selfpromo"), sf("segments_selfpromo_sum"), null, 0xFFffff00),
MusicOfftopic("music_offtopic", sf("segments_nomusic"), sf("skipped_nomusic"), sf("segments_nomusic_sum"), null, 0xFFff9900),
Preview("preview", StringRef.empty, sf("skipped_preview"), StringRef.empty, SegmentBehaviour.SkipAutomatically, 0xFF000000),
;
SPONSOR("sponsor", sf("segments_sponsor"), sf("skipped_sponsor"), sf("segments_sponsor_sum"), null, 0xFF00d400),
INTRO("intro", sf("segments_intermission"), sf("skipped_intermission"), sf("segments_intermission_sum"), null, 0xFF00ffff),
OUTRO("outro", sf("segments_endcards"), sf("skipped_endcard"), sf("segments_endcards_sum"), null, 0xFF0202ed),
INTERACTION("interaction", sf("segments_subscribe"), sf("skipped_subscribe"), sf("segments_subscribe_sum"), null, 0xFFcc00ff),
SELF_PROMO("selfpromo", sf("segments_selfpromo"), sf("skipped_selfpromo"), sf("segments_selfpromo_sum"), null, 0xFFffff00),
MUSIC_OFFTOPIC("music_offtopic", sf("segments_nomusic"), sf("skipped_nomusic"), sf("segments_nomusic_sum"), null, 0xFFff9900),
PREVIEW("preview", sf("segments_preview"), sf("skipped_preview"), sf("segments_preview_sum"), null, 0xFF008fd6),
UNSUBMITTED("unsubmitted", StringRef.empty, sf("skipped_unsubmitted"), StringRef.empty, SegmentBehaviour.SKIP_AUTOMATICALLY, 0xFFFFFFFF);
private static SegmentInfo[] mValuesWithoutPreview = new SegmentInfo[]{
Sponsor,
Intro,
Outro,
Interaction,
SelfPromo,
MusicOfftopic
private static final SegmentInfo[] mValuesWithoutUnsubmitted = new SegmentInfo[]{
SPONSOR,
INTRO,
OUTRO,
INTERACTION,
SELF_PROMO,
MUSIC_OFFTOPIC,
PREVIEW
};
private static Map<String, SegmentInfo> mValuesMap = new HashMap<>(7);
private static final Map<String, SegmentInfo> mValuesMap = new HashMap<>(values().length);
static {
for (SegmentInfo value : valuesWithoutPreview())
for (SegmentInfo value : valuesWithoutUnsubmitted())
mValuesMap.put(value.key, value);
}
@ -206,8 +215,8 @@ public class SponsorBlockSettings {
this.paint = new Paint();
}
public static SegmentInfo[] valuesWithoutPreview() {
return mValuesWithoutPreview;
public static SegmentInfo[] valuesWithoutUnsubmitted() {
return mValuesWithoutUnsubmitted;
}
public static SegmentInfo byCategoryKey(String key) {

View File

@ -4,41 +4,48 @@ import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Looper;
import android.content.Intent;
import android.net.Uri;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;
import pl.jakubweg.objects.SponsorSegment;
import pl.jakubweg.objects.UserStats;
import pl.jakubweg.requests.Requester;
import static android.text.Html.fromHtml;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static fi.razerman.youtube.XGlobals.debug;
import static pl.jakubweg.PlayerController.VERBOSE;
import static pl.jakubweg.PlayerController.getCurrentVideoId;
import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
import static pl.jakubweg.PlayerController.sponsorSegmentsOfCurrentVideo;
import static pl.jakubweg.SponsorBlockSettings.sponsorBlockSkipSegmentsUrl;
import static pl.jakubweg.SponsorBlockPreferenceFragment.FORMATTER;
import static pl.jakubweg.SponsorBlockPreferenceFragment.SAVED_TEMPLATE;
import static pl.jakubweg.SponsorBlockSettings.isSponsorBlockEnabled;
import static pl.jakubweg.SponsorBlockSettings.showTimeWithoutSegments;
import static pl.jakubweg.SponsorBlockSettings.skippedSegments;
import static pl.jakubweg.SponsorBlockSettings.skippedTime;
import static pl.jakubweg.StringRef.str;
import static pl.jakubweg.requests.Requester.voteForSegment;
@SuppressWarnings({"LongLogTag"})
public abstract class SponsorBlockUtils {
@ -46,15 +53,20 @@ public abstract class SponsorBlockUtils {
public static final String DATE_FORMAT = "HH:mm:ss.SSS";
@SuppressLint("SimpleDateFormat")
public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_FORMAT);
public static boolean videoHasSegments = false;
public static String timeWithoutSegments = "";
private static final int sponsorBtnId = 1234;
public static final View.OnClickListener sponsorBlockBtnListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (debug) {
Log.d(TAG, "Shield button clicked");
}
NewSegmentHelperLayout.toggle();
public static final View.OnClickListener sponsorBlockBtnListener = v -> {
if (debug) {
Log.d(TAG, "Shield button clicked");
}
NewSegmentHelperLayout.toggle();
};
public static final View.OnClickListener voteButtonListener = v -> {
if (debug) {
Log.d(TAG, "Vote button clicked");
}
SponsorBlockUtils.onVotingClicked(v.getContext());
};
private static int shareBtnId = -1;
private static long newSponsorSegmentDialogShownMillis;
@ -84,7 +96,7 @@ public abstract class SponsorBlockUtils {
private static final DialogInterface.OnClickListener segmentTypeListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SponsorBlockSettings.SegmentInfo segmentType = SponsorBlockSettings.SegmentInfo.valuesWithoutPreview()[which];
SponsorBlockSettings.SegmentInfo segmentType = SponsorBlockSettings.SegmentInfo.valuesWithoutUnsubmitted()[which];
boolean enableButton;
if (!segmentType.behaviour.showOnTimeBar) {
Toast.makeText(
@ -114,7 +126,7 @@ public abstract class SponsorBlockUtils {
Context context = ((AlertDialog) dialog).getContext();
dialog.dismiss();
SponsorBlockSettings.SegmentInfo[] values = SponsorBlockSettings.SegmentInfo.valuesWithoutPreview();
SponsorBlockSettings.SegmentInfo[] values = SponsorBlockSettings.SegmentInfo.valuesWithoutUnsubmitted();
CharSequence[] titles = new CharSequence[values.length];
for (int i = 0; i < values.length; i++) {
// titles[i] = values[i].title;
@ -145,102 +157,88 @@ public abstract class SponsorBlockUtils {
new Thread(submitRunnable).start();
}
};
private static boolean isShown = false;
private static WeakReference<ImageView> sponsorBlockBtn = new WeakReference<>(null);
private static String messageToToast = "";
private static EditByHandSaveDialogListener editByHandSaveDialogListener = new EditByHandSaveDialogListener();
private static final DialogInterface.OnClickListener editByHandDialogListener = new DialogInterface.OnClickListener() {
@SuppressLint("DefaultLocale")
@Override
public void onClick(DialogInterface dialog, int which) {
Context context = ((AlertDialog) dialog).getContext();
public static String messageToToast = "";
private static final EditByHandSaveDialogListener editByHandSaveDialogListener = new EditByHandSaveDialogListener();
private static final DialogInterface.OnClickListener editByHandDialogListener = (dialog, which) -> {
Context context = ((AlertDialog) dialog).getContext();
final boolean isStart = DialogInterface.BUTTON_NEGATIVE == which;
final boolean isStart = DialogInterface.BUTTON_NEGATIVE == which;
final EditText textView = new EditText(context);
textView.setHint(DATE_FORMAT);
if (isStart) {
if (newSponsorSegmentStartMillis >= 0)
textView.setText(dateFormatter.format(new Date(newSponsorSegmentStartMillis)));
} else {
if (newSponsorSegmentEndMillis >= 0)
textView.setText(dateFormatter.format(new Date(newSponsorSegmentEndMillis)));
}
editByHandSaveDialogListener.settingStart = isStart;
editByHandSaveDialogListener.editText = new WeakReference<>(textView);
new AlertDialog.Builder(context)
.setTitle(str(isStart ? "new_segment_time_start" : "new_segment_time_end"))
.setView(textView)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(str("new_segment_now"), editByHandSaveDialogListener)
.setPositiveButton(android.R.string.ok, editByHandSaveDialogListener)
.show();
dialog.dismiss();
final EditText textView = new EditText(context);
textView.setHint(DATE_FORMAT);
if (isStart) {
if (newSponsorSegmentStartMillis >= 0)
textView.setText(dateFormatter.format(new Date(newSponsorSegmentStartMillis)));
} else {
if (newSponsorSegmentEndMillis >= 0)
textView.setText(dateFormatter.format(new Date(newSponsorSegmentEndMillis)));
}
editByHandSaveDialogListener.settingStart = isStart;
editByHandSaveDialogListener.editText = new WeakReference<>(textView);
new AlertDialog.Builder(context)
.setTitle(str(isStart ? "new_segment_time_start" : "new_segment_time_end"))
.setView(textView)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(str("new_segment_now"), editByHandSaveDialogListener)
.setPositiveButton(android.R.string.ok, editByHandSaveDialogListener)
.show();
dialog.dismiss();
};
private static Runnable toastRunnable = new Runnable() {
@Override
public void run() {
Context context = appContext.get();
if (context != null && messageToToast != null)
Toast.makeText(context, messageToToast, Toast.LENGTH_LONG).show();
}
private static final Runnable toastRunnable = () -> {
Context context = appContext.get();
if (context != null && messageToToast != null)
Toast.makeText(context, messageToToast, Toast.LENGTH_LONG).show();
};
private static final Runnable submitRunnable = new Runnable() {
@Override
public void run() {
messageToToast = null;
final String uuid = SponsorBlockSettings.uuid;
final long start = newSponsorSegmentStartMillis;
final long end = newSponsorSegmentEndMillis;
final String videoId = getCurrentVideoId();
final SponsorBlockSettings.SegmentInfo segmentType = SponsorBlockUtils.newSponsorBlockSegmentType;
try {
private static final DialogInterface.OnClickListener segmentVoteClickListener = (dialog, which) -> {
final Context context = ((AlertDialog) dialog).getContext();
final SponsorSegment segment = sponsorSegmentsOfCurrentVideo[which];
if (start < 0 || end < 0 || start >= end || segmentType == null || videoId == null || uuid == null) {
Log.e(TAG, "Unable to submit times, invalid parameters");
return;
}
final VoteOption[] voteOptions = VoteOption.values();
String[] items = new String[voteOptions.length];
URL url = new URL(String.format(Locale.US,
sponsorBlockSkipSegmentsUrl + "?videoID=%s&userID=%s&startTime=%.3f&endTime=%.3f&category=%s",
videoId, uuid, ((float) start) / 1000f, ((float) end) / 1000f, segmentType.key));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
switch (connection.getResponseCode()) {
default:
messageToToast = String.format(str("submit_failed_unknown_error"), connection.getResponseCode(), connection.getResponseMessage());
break;
case 429:
messageToToast = str("submit_failed_rate_limit");
break;
case 403:
messageToToast = str("submit_failed_forbidden");
break;
case 409:
messageToToast = str("submit_failed_duplicate");
break;
case 200:
messageToToast = str("submit_succeeded");
break;
}
Log.i(TAG, "Segment submitted with status: " + connection.getResponseCode() + ", " + messageToToast);
new Handler(Looper.getMainLooper()).post(toastRunnable);
connection.disconnect();
newSponsorSegmentEndMillis = newSponsorSegmentStartMillis = -1;
} catch (Exception e) {
Log.e(TAG, "Unable to submit segment", e);
}
if (videoId != null)
PlayerController.executeDownloadSegments(videoId);
for (int i = 0; i < voteOptions.length; i++) {
items[i] = voteOptions[i].title;
}
new AlertDialog.Builder(context)
.setItems(items, (dialog1, which1) -> {
appContext = new WeakReference<>(context.getApplicationContext());
switch (voteOptions[which1]) {
case UPVOTE:
voteForSegment(segment, VoteOption.UPVOTE, appContext.get(), toastRunnable);
break;
case DOWNVOTE:
voteForSegment(segment, VoteOption.DOWNVOTE, appContext.get(), toastRunnable);
break;
case CATEGORY_CHANGE:
onNewCategorySelect(segment, context);
break;
}
})
.show();
};
private static final Runnable submitRunnable = () -> {
messageToToast = null;
final String uuid = SponsorBlockSettings.uuid;
final long start = newSponsorSegmentStartMillis;
final long end = newSponsorSegmentEndMillis;
final String videoId = getCurrentVideoId();
final SponsorBlockSettings.SegmentInfo segmentType = SponsorBlockUtils.newSponsorBlockSegmentType;
try {
if (start < 0 || end < 0 || start >= end || segmentType == null || videoId == null || uuid == null) {
Log.e(TAG, "Unable to submit times, invalid parameters");
return;
}
Requester.submitSegments(videoId, uuid, ((float) start) / 1000f, ((float) end) / 1000f, segmentType.key, toastRunnable);
newSponsorSegmentEndMillis = newSponsorSegmentStartMillis = -1;
} catch (Exception e) {
Log.e(TAG, "Unable to submit segment", e);
}
if (videoId != null)
PlayerController.executeDownloadSegments(videoId);
};
static {
@ -250,21 +248,32 @@ public abstract class SponsorBlockUtils {
private SponsorBlockUtils() {
}
public static void showButton() {
if (isShown) return;
isShown = true;
View i = sponsorBlockBtn.get();
if (i == null) return;
public static void showShieldButton() {
View i = ShieldButton._shieldBtn.get();
if (i == null || !ShieldButton.shouldBeShown()) return;
i.setVisibility(VISIBLE);
i.bringToFront();
i.requestLayout();
i.invalidate();
}
public static void hideButton() {
if (!isShown) return;
isShown = false;
View i = sponsorBlockBtn.get();
public static void hideShieldButton() {
View i = ShieldButton._shieldBtn.get();
if (i != null)
i.setVisibility(GONE);
}
public static void showVoteButton() {
View i = VotingButton._votingButton.get();
if (i == null || !VotingButton.shouldBeShown()) return;
i.setVisibility(VISIBLE);
i.bringToFront();
i.requestLayout();
i.invalidate();
}
public static void hideVoteButton() {
View i = VotingButton._votingButton.get();
if (i != null)
i.setVisibility(GONE);
}
@ -275,7 +284,7 @@ public abstract class SponsorBlockUtils {
new AlertDialog.Builder(context)
.setTitle(str("new_segment_title"))
.setMessage(String.format(str("new_segment_mark_time_as_question"),
.setMessage(str("new_segment_mark_time_as_question",
newSponsorSegmentDialogShownMillis / 60000,
newSponsorSegmentDialogShownMillis / 1000 % 60,
newSponsorSegmentDialogShownMillis % 1000))
@ -293,7 +302,7 @@ public abstract class SponsorBlockUtils {
long end = (newSponsorSegmentEndMillis) / 1000;
new AlertDialog.Builder(context)
.setTitle(str("new_segment_confirm_title"))
.setMessage(String.format(str("new_segment_confirm_content"),
.setMessage(str("new_segment_confirm_content",
start / 60, start % 60,
end / 60, end % 60,
length / 60, length % 60))
@ -301,10 +310,51 @@ public abstract class SponsorBlockUtils {
.setPositiveButton(android.R.string.yes, segmentReadyDialogButtonListener)
.show();
} else {
Toast.makeText(context, "Mark two locations on the time bar first", Toast.LENGTH_SHORT).show();
Toast.makeText(context, str("new_segment_mark_locations_first"), Toast.LENGTH_SHORT).show();
}
}
public static void onVotingClicked(final Context context) {
if (sponsorSegmentsOfCurrentVideo == null || sponsorSegmentsOfCurrentVideo.length == 0) {
Toast.makeText(context.getApplicationContext(), str("vote_no_segments"), Toast.LENGTH_SHORT).show();
return;
}
int segmentAmount = sponsorSegmentsOfCurrentVideo.length;
List<CharSequence> titles = new ArrayList<>(segmentAmount); // I've replaced an array with a list to prevent null elements in the array as unsubmitted segments get filtered out
for (int i = 0; i < segmentAmount; i++) {
SponsorSegment segment = sponsorSegmentsOfCurrentVideo[i];
if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) {
continue;
}
String start = dateFormatter.format(new Date(segment.start));
String end = dateFormatter.format(new Date(segment.end));
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br> %s to %s",
segment.category.color, segment.category.title, start, end));
if (i + 1 != segmentAmount) // prevents trailing new line after last segment
htmlBuilder.append("<br>");
titles.add(Html.fromHtml(htmlBuilder.toString()));
}
new AlertDialog.Builder(context)
.setItems(titles.toArray(new CharSequence[0]), segmentVoteClickListener)
.show();
}
private static void onNewCategorySelect(final SponsorSegment segment, Context context) {
final SponsorBlockSettings.SegmentInfo[] values = SponsorBlockSettings.SegmentInfo.valuesWithoutUnsubmitted();
CharSequence[] titles = new CharSequence[values.length];
for (int i = 0; i < values.length; i++) {
titles[i] = values[i].getTitleWithDot();
}
new AlertDialog.Builder(context)
.setTitle(str("new_segment_choose_category"))
.setItems(titles, (dialog, which) -> voteForSegment(segment, VoteOption.CATEGORY_CHANGE, appContext.get(), toastRunnable, values[which].key))
.show();
}
@SuppressLint("DefaultLocale")
public static void onPreviewClicked(Context context) {
if (newSponsorSegmentStartMillis >= 0 && newSponsorSegmentStartMillis < newSponsorSegmentEndMillis) {
@ -316,7 +366,7 @@ public abstract class SponsorBlockUtils {
final SponsorSegment[] segments = original == null ? new SponsorSegment[1] : Arrays.copyOf(original, original.length + 1);
segments[segments.length - 1] = new SponsorSegment(newSponsorSegmentStartMillis, newSponsorSegmentEndMillis,
SponsorBlockSettings.SegmentInfo.Preview, null);
SponsorBlockSettings.SegmentInfo.UNSUBMITTED, null);
Arrays.sort(segments);
sponsorSegmentsOfCurrentVideo = segments;
@ -340,86 +390,134 @@ public abstract class SponsorBlockUtils {
if (v.getId() != shareBtnId || !/*SponsorBlockSettings.isAddNewSegmentEnabled*/false) return;
// if (VERBOSE)
// Log.d(TAG, "VISIBILITY CHANGED of view " + v);
ImageView sponsorBtn = sponsorBlockBtn.get();
ImageView sponsorBtn = ShieldButton._shieldBtn.get();
if (sponsorBtn != null) {
sponsorBtn.setVisibility(v.getVisibility());
}
}
public synchronized static SponsorSegment[] getSegmentsForVideo(String videoId) {
newSponsorSegmentEndMillis = newSponsorSegmentStartMillis = -1;
ArrayList<SponsorSegment> sponsorSegments = new ArrayList<>();
try {
if (VERBOSE)
Log.i(TAG, "Trying to download segments for videoId=" + videoId);
URL url = new URL(SponsorBlockSettings.getSponsorBlockUrlWithCategories(videoId));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
switch (connection.getResponseCode()) {
default:
Log.e(TAG, "Unable to download segments: Status: " + connection.getResponseCode() + " " + connection.getResponseMessage());
break;
case 404:
Log.w(TAG, "No segments for this video (ERR404)");
break;
case 200:
if (VERBOSE)
Log.i(TAG, "Received status 200 OK, parsing response...");
StringBuilder stringBuilder = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
connection.getInputStream().close();
JSONArray responseArray = new JSONArray(stringBuilder.toString());
int length = responseArray.length();
for (int i = 0; i < length; i++) {
JSONObject obj = ((JSONObject) responseArray.get(i));
JSONArray segments = obj.getJSONArray("segment");
long start = (long) (segments.getDouble(0) * 1000);
long end = (long) (segments.getDouble(1) * 1000);
String category = obj.getString("category");
String UUID = obj.getString("UUID");
SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category);
if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) {
SponsorSegment segment = new SponsorSegment(start, end, segmentCategory, UUID);
sponsorSegments.add(segment);
}
}
if (VERBOSE)
Log.v(TAG, "Parsing done");
break;
public static String appendTimeWithoutSegments(String totalTime) {
if (videoHasSegments && isSettingEnabled(showTimeWithoutSegments) && !TextUtils.isEmpty(totalTime)) {
if (timeWithoutSegments.isEmpty()) {
timeWithoutSegments = getTimeWithoutSegments(sponsorSegmentsOfCurrentVideo);
}
connection.disconnect();
} catch (Exception e) {
Log.e(TAG, "download segments failed", e);
return totalTime + timeWithoutSegments;
}
return sponsorSegments.toArray(new SponsorSegment[0]);
return totalTime;
}
public static void sendViewCountRequest(SponsorSegment segment) {
public static String getTimeWithoutSegments(SponsorSegment[] sponsorSegmentsOfCurrentVideo) {
long currentVideoLength = PlayerController.getCurrentVideoLength();
if (!isSettingEnabled(showTimeWithoutSegments) || sponsorSegmentsOfCurrentVideo == null || currentVideoLength == 1) {
return "";
}
long timeWithoutSegments = currentVideoLength + 500; // YouTube:tm:
for (SponsorSegment segment : sponsorSegmentsOfCurrentVideo) {
timeWithoutSegments -= segment.end - segment.start;
}
long hours = timeWithoutSegments / 3600000;
long minutes = (timeWithoutSegments / 60000) % 60;
long seconds = (timeWithoutSegments / 1000) % 60;
String format = (hours > 0 ? "%d:%02" : "%") + "d:%02d"; // mmLul
String formatted = hours > 0 ? String.format(format, hours, minutes, seconds) : String.format(format, minutes, seconds);
return String.format(" (%s)", formatted);
}
public static void playerTypeChanged(String playerType) {
try {
URL url = new URL(SponsorBlockSettings.getSponsorBlockViewedUrl(segment.UUID));
if (videoHasSegments && (playerType.equalsIgnoreCase("NONE"))) {
PlayerController.setCurrentVideoId(null);
}
}
catch (Exception ex) {
Log.e(TAG, "Player type changed caused a crash.", ex);
}
}
Log.d("sponsorblock", "requesting: " + url.getPath());
public static int countMatches(CharSequence seq, char c) {
int count = 0;
for (int i = 0; i < seq.length(); i++) {
if (seq.charAt(i) == c)
count++;
}
return count;
}
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.getInputStream().close();
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
@SuppressWarnings("deprecation")
public static void addUserStats(PreferenceCategory category, Preference loadingPreference, UserStats stats) {
category.removePreference(loadingPreference);
Context context = category.getContext();
{
EditTextPreference preference = new EditTextPreference(context);
category.addPreference(preference);
String userName = stats.getUserName();
preference.setTitle(fromHtml(str("stats_username", userName)));
preference.setSummary(str("stats_username_change"));
preference.setText(userName);
preference.setOnPreferenceChangeListener((preference1, newUsername) -> {
appContext = new WeakReference<>(context.getApplicationContext());
Requester.setUsername((String) newUsername, toastRunnable);
return false;
});
}
{
Preference preference = new Preference(context);
category.addPreference(preference);
String formatted = FORMATTER.format(stats.getSegmentCount());
preference.setTitle(fromHtml(str("stats_submissions", formatted)));
}
{
Preference preference = new Preference(context);
category.addPreference(preference);
String formatted = FORMATTER.format(stats.getViewCount());
double saved = stats.getMinutesSaved();
int hoursSaved = (int) (saved / 60);
double minutesSaved = saved % 60;
String formattedSaved = String.format(SAVED_TEMPLATE, hoursSaved, minutesSaved);
preference.setTitle(fromHtml(str("stats_saved", formatted)));
preference.setSummary(fromHtml(str("stats_saved_sum", formattedSaved)));
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sponsor.ajay.app/stats/"));
preference1.getContext().startActivity(i);
return false;
});
}
{
Preference preference = new Preference(context);
category.addPreference(preference);
String formatted = FORMATTER.format(skippedSegments);
long hoursSaved = skippedTime / 3600000;
double minutesSaved = (skippedTime / 60000d) % 60;
String formattedSaved = String.format(SAVED_TEMPLATE, hoursSaved, minutesSaved);
preference.setTitle(fromHtml(str("stats_self_saved", formatted)));
preference.setSummary(fromHtml(str("stats_self_saved_sum", formattedSaved)));
}
}
public static boolean isSettingEnabled(boolean setting) {
return isSponsorBlockEnabled && setting;
}
public enum VoteOption {
UPVOTE(str("vote_upvote")),
DOWNVOTE(str("vote_downvote")),
CATEGORY_CHANGE(str("vote_category"));
public final String title;
VoteOption(String title) {
this.title = title;
}
}
@ -455,5 +553,4 @@ public abstract class SponsorBlockUtils {
}
}
}
}

View File

@ -24,7 +24,7 @@ public class StringRef {
packageName = context.getPackageName();
}
private static HashMap<String, StringRef> strings = new HashMap<>();
private static final HashMap<String, StringRef> strings = new HashMap<>();
/**
* Gets strings reference from shared collection or creates if not exists yet,
@ -52,6 +52,18 @@ public class StringRef {
return sf(id).toString();
}
/**
* Gets string value by string id, shorthand for <code>sf(id).toString()</code> and formats the string
* with given args.
* @param id string resource name/id
* @param args the args to format the string with
* @return String value from string.xml formatted with given args
*/
@NonNull
public static String str(@NonNull String id, Object... args) {
return String.format(str(id), args);
}
/**
* Creates a StringRef object that'll not change it's value

View File

@ -0,0 +1,122 @@
package pl.jakubweg;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
import java.lang.ref.WeakReference;
import static fi.razerman.youtube.XGlobals.debug;
import static pl.jakubweg.PlayerController.getCurrentVideoLength;
import static pl.jakubweg.PlayerController.getLastKnownVideoTime;
public class VotingButton {
static String TAG = "VOTING";
static RelativeLayout _youtubeControlsLayout;
static WeakReference<ImageView> _votingButton = new WeakReference<>(null);
static int fadeDurationFast;
static int fadeDurationScheduled;
static Animation fadeIn;
static Animation fadeOut;
static boolean isShowing;
public static void initialize(Object viewStub) {
try {
if(debug){
Log.d(TAG, "initializing voting button");
}
_youtubeControlsLayout = (RelativeLayout) viewStub;
ImageView imageView = (ImageView)_youtubeControlsLayout
.findViewById(getIdentifier("voting_button", "id"));
if (debug && imageView == null){
Log.d(TAG, "Couldn't find imageView with tag \"voting_button\"");
}
if (imageView == null) return;
imageView.setOnClickListener(SponsorBlockUtils.voteButtonListener);
_votingButton = new WeakReference<>(imageView);
// Animations
fadeDurationFast = getInteger("fade_duration_fast");
fadeDurationScheduled = getInteger("fade_duration_scheduled");
fadeIn = getAnimation("fade_in");
fadeIn.setDuration(fadeDurationFast);
fadeOut = getAnimation("fade_out");
fadeOut.setDuration(fadeDurationScheduled);
isShowing = true;
changeVisibilityImmediate(false);
}
catch (Exception ex) {
Log.e(TAG, "Unable to set RelativeLayout", ex);
}
}
public static void changeVisibilityImmediate(boolean visible) {
changeVisibility(visible, true);
}
public static void changeVisibilityNegatedImmediate(boolean visible) {
changeVisibility(!visible, true);
}
public static void changeVisibility(boolean visible) {
changeVisibility(visible, false);
}
public static void changeVisibility(boolean visible, boolean immediate) {
if (isShowing == visible) return;
isShowing = visible;
ImageView iView = _votingButton.get();
if (_youtubeControlsLayout == null || iView == null) return;
if (visible && shouldBeShown()) {
if (getLastKnownVideoTime() >= getCurrentVideoLength()) {
return;
}
if (debug) {
Log.d(TAG, "Fading in");
}
iView.setVisibility(View.VISIBLE);
if (!immediate)
iView.startAnimation(fadeIn);
return;
}
if (iView.getVisibility() == View.VISIBLE) {
if (debug) {
Log.d(TAG, "Fading out");
}
if (!immediate)
iView.startAnimation(fadeOut);
iView.setVisibility(shouldBeShown() ? View.INVISIBLE : View.GONE);
}
}
static boolean shouldBeShown() {
return SponsorBlockUtils.isSettingEnabled(SponsorBlockSettings.isVotingEnabled);
}
//region Helpers
private static int getIdentifier(String name, String defType) {
Context context = YouTubeTikTokRoot_Application.getAppContext();
return context.getResources().getIdentifier(name, defType, context.getPackageName());
}
private static int getInteger(String name) {
return YouTubeTikTokRoot_Application.getAppContext().getResources().getInteger(getIdentifier(name, "integer"));
}
private static Animation getAnimation(String name) {
return AnimationUtils.loadAnimation(YouTubeTikTokRoot_Application.getAppContext(), getIdentifier(name, "anim"));
}
//endregion
}

View File

@ -1,4 +1,6 @@
package pl.jakubweg;
package pl.jakubweg.objects;
import pl.jakubweg.SponsorBlockSettings;
public class SponsorSegment implements Comparable<SponsorSegment> {
public final long start;
@ -26,5 +28,4 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
public int compareTo(SponsorSegment o) {
return (int) (this.start - o.start);
}
}

View File

@ -0,0 +1,31 @@
package pl.jakubweg.objects;
public class UserStats {
private final String userName;
private final double minutesSaved;
private final int segmentCount;
private final int viewCount;
public UserStats(String userName, double minutesSaved, int segmentCount, int viewCount) {
this.userName = userName;
this.minutesSaved = minutesSaved;
this.segmentCount = segmentCount;
this.viewCount = viewCount;
}
public String getUserName() {
return userName;
}
public double getMinutesSaved() {
return minutesSaved;
}
public int getSegmentCount() {
return segmentCount;
}
public int getViewCount() {
return viewCount;
}
}

View File

@ -0,0 +1,206 @@
package pl.jakubweg.requests;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import pl.jakubweg.SponsorBlockSettings;
import pl.jakubweg.SponsorBlockUtils;
import pl.jakubweg.SponsorBlockUtils.VoteOption;
import pl.jakubweg.objects.SponsorSegment;
import pl.jakubweg.objects.UserStats;
import static pl.jakubweg.SponsorBlockUtils.timeWithoutSegments;
import static pl.jakubweg.SponsorBlockUtils.videoHasSegments;
import static pl.jakubweg.StringRef.str;
public class Requester {
private static final String SPONSORBLOCK_API_URL = "https://sponsor.ajay.app/api/";
private static final String TIME_TEMPLATE = "%.3f";
private Requester() {}
public static synchronized SponsorSegment[] getSegments(String videoId) {
List<SponsorSegment> segments = new ArrayList<>();
try {
HttpURLConnection connection = getConnectionFromRoute(Route.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories);
int responseCode = connection.getResponseCode();
videoHasSegments = false;
timeWithoutSegments = "";
if (responseCode == 200) {
JSONArray responseArray = new JSONArray(parseJson(connection));
int length = responseArray.length();
for (int i = 0; i < length; i++) {
JSONObject obj = ((JSONObject) responseArray.get(i));
JSONArray segment = obj.getJSONArray("segment");
long start = (long) (segment.getDouble(0) * 1000);
long end = (long) (segment.getDouble(1) * 1000);
String category = obj.getString("category");
String uuid = obj.getString("UUID");
SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category);
if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) {
SponsorSegment sponsorSegment = new SponsorSegment(start, end, segmentCategory, uuid);
segments.add(sponsorSegment);
}
}
videoHasSegments = true;
timeWithoutSegments = SponsorBlockUtils.getTimeWithoutSegments(segments.toArray(new SponsorSegment[0]));
}
connection.disconnect();
}
catch (Exception ex) {
ex.printStackTrace();
}
return segments.toArray(new SponsorSegment[0]);
}
public static void submitSegments(String videoId, String uuid, float startTime, float endTime, String category, Runnable toastRunnable) {
try {
String start = String.format(TIME_TEMPLATE, startTime);
String end = String.format(TIME_TEMPLATE, endTime);
HttpURLConnection connection = getConnectionFromRoute(Route.SUBMIT_SEGMENTS, videoId, uuid, start, end, category);
int responseCode = connection.getResponseCode();
switch (responseCode) {
case 200:
SponsorBlockUtils.messageToToast = str("submit_succeeded");
break;
case 409:
SponsorBlockUtils.messageToToast = str("submit_failed_duplicate");
break;
case 403:
SponsorBlockUtils.messageToToast = str("submit_failed_forbidden");
break;
case 429:
SponsorBlockUtils.messageToToast = str("submit_failed_rate_limit");
break;
default:
SponsorBlockUtils.messageToToast = str("submit_failed_unknown_error", responseCode, connection.getResponseMessage());
break;
}
new Handler(Looper.getMainLooper()).post(toastRunnable);
connection.disconnect();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void sendViewCountRequest(SponsorSegment segment) {
try {
HttpURLConnection connection = getConnectionFromRoute(Route.VIEWED_SEGMENT, segment.UUID);
connection.disconnect();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void voteForSegment(SponsorSegment segment, VoteOption voteOption, Context context, Runnable toastRunnable, String... args) {
try {
String segmentUuid = segment.UUID;
String uuid = SponsorBlockSettings.uuid;
String vote = Integer.toString(voteOption == VoteOption.UPVOTE ? 1 : 0);
Toast.makeText(context, str("vote_started"), Toast.LENGTH_SHORT).show();
HttpURLConnection connection = voteOption == VoteOption.CATEGORY_CHANGE
? getConnectionFromRoute(Route.VOTE_ON_SEGMENT_CATEGORY, segmentUuid, uuid, args[0])
: getConnectionFromRoute(Route.VOTE_ON_SEGMENT_QUALITY, segmentUuid, uuid, vote);
int responseCode = connection.getResponseCode();
switch (responseCode) {
case 200:
SponsorBlockUtils.messageToToast = str("vote_succeeded");
break;
case 403:
SponsorBlockUtils.messageToToast = str("vote_failed_forbidden");
break;
default:
SponsorBlockUtils.messageToToast = str("vote_failed_unknown_error", responseCode, connection.getResponseMessage());
break;
}
new Handler(Looper.getMainLooper()).post(toastRunnable);
connection.disconnect();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void retrieveUserStats(PreferenceCategory category, Preference loadingPreference) {
if (!SponsorBlockSettings.isSponsorBlockEnabled) {
loadingPreference.setTitle(str("stats_sb_disabled"));
return;
}
new Thread(() -> {
try {
HttpURLConnection connection = getConnectionFromRoute(Route.GET_USER_STATS, SponsorBlockSettings.uuid);
JSONObject json = new JSONObject(parseJson(connection));
connection.disconnect();
UserStats stats = new UserStats(json.getString("userName"), json.getDouble("minutesSaved"), json.getInt("segmentCount"),
json.getInt("viewCount"));
SponsorBlockUtils.addUserStats(category, loadingPreference, stats);
}
catch (Exception ex) {
ex.printStackTrace();
}
}).start();
}
public static void setUsername(String username, Runnable toastRunnable) {
try {
HttpURLConnection connection = getConnectionFromRoute(Route.CHANGE_USERNAME, SponsorBlockSettings.uuid, username);
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
SponsorBlockUtils.messageToToast = str("stats_username_changed");
}
else {
SponsorBlockUtils.messageToToast = str("stats_username_change_unknown_error", responseCode, connection.getResponseMessage());
}
new Handler(Looper.getMainLooper()).post(toastRunnable);
connection.disconnect();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
String url = SPONSORBLOCK_API_URL + route.compile(params).getCompiledRoute();
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod(route.getMethod().name());
return connection;
}
private static String parseJson(HttpURLConnection connection) throws IOException {
StringBuilder jsonBuilder = new StringBuilder();
InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
jsonBuilder.append(line);
}
inputStream.close();
return jsonBuilder.toString();
}
}

View File

@ -0,0 +1,73 @@
package pl.jakubweg.requests;
import pl.jakubweg.SponsorBlockUtils;
import static pl.jakubweg.requests.Route.Method.*;
public class Route {
public static final Route GET_SEGMENTS = new Route(GET, "skipSegments?videoID={video_id}&categories={categories}");
public static final Route VIEWED_SEGMENT = new Route(POST, "viewedVideoSponsorTime?UUID={segment_id}");
public static final Route GET_USER_STATS = new Route(GET, "userInfo?userID={user_id}&values=[\"userName\", \"minutesSaved\", \"segmentCount\", \"viewCount\"]");
public static final Route CHANGE_USERNAME = new Route(POST, "setUsername?userID={user_id}&username={username}");
public static final Route SUBMIT_SEGMENTS = new Route(POST, "skipSegments?videoID={video_id}&userID={user_id}&startTime={start_time}&endTime={end_time}&category={category}");
public static final Route VOTE_ON_SEGMENT_QUALITY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&type={type}");
public static final Route VOTE_ON_SEGMENT_CATEGORY = new Route(POST, "voteOnSponsorTime?UUID={segment_id}&userID={user_id}&category={category}");
private final String route;
private final Method method;
private final int paramCount;
private Route(Method method, String route) {
this.method = method;
this.route = route;
this.paramCount = SponsorBlockUtils.countMatches(route, '{');
if (paramCount != SponsorBlockUtils.countMatches(route, '}'))
throw new IllegalArgumentException("Not enough parameters");
}
public Method getMethod() {
return method;
}
public CompiledRoute compile(String... params) {
if (params.length != paramCount)
throw new IllegalArgumentException("Error compiling route [" + route + "], incorrect amount of parameters provided. " +
"Expected: " + paramCount + ", provided: " + params.length);
StringBuilder compiledRoute = new StringBuilder(route);
for (int i = 0; i < paramCount; i++) {
int paramStart = compiledRoute.indexOf("{");
int paramEnd = compiledRoute.indexOf("}");
compiledRoute.replace(paramStart, paramEnd + 1, params[i]);
}
return new CompiledRoute(this, compiledRoute.toString());
}
public static class CompiledRoute {
private final Route baseRoute;
private final String compiledRoute;
private CompiledRoute(Route baseRoute, String compiledRoute) {
this.baseRoute = baseRoute;
this.compiledRoute = compiledRoute;
}
public Route getBaseRoute() {
return baseRoute;
}
public String getCompiledRoute() {
return compiledRoute;
}
public Method getMethod() {
return baseRoute.method;
}
}
public enum Method {
GET,
POST
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,6c0,-0.55 -0.45,-1 -1,-1L5.82,5l0.66,-3.18 0.02,-0.23c0,-0.31 -0.13,-0.59 -0.33,-0.8L5.38,0 0.44,4.94C0.17,5.21 0,5.59 0,6v6.5c0,0.83 0.67,1.5 1.5,1.5h6.75c0.62,0 1.15,-0.38 1.38,-0.91l2.26,-5.29c0.07,-0.17 0.11,-0.36 0.11,-0.55L12,6zM22.5,10h-6.75c-0.62,0 -1.15,0.38 -1.38,0.91l-2.26,5.29c-0.07,0.17 -0.11,0.36 -0.11,0.55L12,18c0,0.55 0.45,1 1,1h5.18l-0.66,3.18 -0.02,0.24c0,0.31 0.13,0.59 0.33,0.8l0.79,0.78 4.94,-4.94c0.27,-0.27 0.44,-0.65 0.44,-1.06v-6.5c0,-0.83 -0.67,-1.5 -1.5,-1.5z"/>
</vector>

View File

@ -143,12 +143,16 @@
<string name="enable_sb_sum">Switch this on for very cool sponsor segments skipping</string>
<string name="enable_segmadding">Enable new segment adding</string>
<string name="enable_segmadding_sum">Switch this on to enable experimental segment adding (has button visibility issues).</string>
<string name="enable_voting">Enable voting</string>
<string name="enable_voting_sum">Switch this on to enable voting.</string>
<string name="diff_segments">What to do with different segments</string>
<string name="general">General</string>
<string name="general_skiptoast">Show a toast when skipping segment automatically</string>
<string name="general_skiptoast_sum">Click to see an example toast</string>
<string name="general_skipcount">Skip count tracking</string>
<string name="general_skipcount_sum">This lets SponsorBlock leaderboard system know how much time people have saved. The extension sends a message to the server each time you skip a segment.</string>
<string name="general_time_without_sb">Show time without segments</string>
<string name="general_time_without_sb_sum">This time appears in brackets next to the current time. This shows the total video duration minus any segments.</string>
<string name="general_adjusting">Adjusting new segment step</string>
<string name="general_adjusting_sum">This is the number of milliseconds you can move when you use the time adjustment buttons while adding new segment</string>
<string name="general_uuid">Your unique user id</string>
@ -165,6 +169,8 @@
<string name="segments_selfpromo_sum">Similar to "sponsor" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with</string>
<string name="segments_nomusic">Music: Non-Music Section</string>
<string name="segments_nomusic_sum">Only for use in music videos. This includes introductions or outros in music videos</string>
<string name="segments_preview">Preview/Recap</string>
<string name="segments_preview_sum">Quick recap of previous episodes, or a preview of what\'s coming up later in the current video. Meant for edited together clips, not for spoken summaries.</string>
<string name="skipped_sponsor">Skipped sponsor</string>
<string name="skipped_intermission">Skipped intro</string>
<string name="skipped_endcard">Skipped outro</string>
@ -172,12 +178,27 @@
<string name="skipped_selfpromo">Skipped self promotion</string>
<string name="skipped_nomusic">Skipped silence</string>
<string name="skipped_preview">Skipped preview</string>
<string name="skipped_unsubmitted">Skipped unsubmitted segment</string>
<string name="skip_automatically">Skip automatically</string>
<string name="skip_showbutton">Show a skip button</string>
<string name="skip_ignore">Don\'t do anything</string>
<string name="stats">Stats</string>
<string name="stats_loading">Loading..</string>
<string name="stats_sb_disabled">SponsorBlock is disabled</string>
<string name="stats_username" formatted="false">Your username: &lt;b&gt;%s&lt;/b&gt;</string>
<string name="stats_username_change">Click to change your username</string>
<string name="stats_username_change_unknown_error" formatted="false">Unable to change username: Status: %d %s</string>
<string name="stats_username_changed">Username changed successfully</string>
<string name="stats_submissions" formatted="false">Submissions: &lt;b&gt;%s&lt;/b&gt;</string>
<string name="stats_saved" formatted="false">You\'ve saved people from &lt;b&gt;%s&lt;/b&gt; segments.</string>
<string name="stats_saved_sum" formatted="false">That\'s &lt;b&gt;%s&lt;/b&gt; of their lives. Click to see the leaderboard</string>
<string name="stats_self_saved" formatted="false">You\'ve skipped &lt;b&gt;%s&lt;/b&gt; segments.</string>
<string name="stats_self_saved_sum" formatted="false">That\'s &lt;b&gt;%s&lt;/b&gt;.</string>
<string name="about">About</string>
<string name="about_api">This app uses the API from Sponsor Block</string>
<string name="about_api_sum">Tap to learn more at: sponsor.ajay.app</string>
<string name="about_api">This app uses the API from SponsorBlock</string>
<string name="about_api_sum">Tap to learn more, and see downloads for other platforms at: sponsor.ajay.app</string>
<string name="about_madeby">Integration made by JakubWeg</string>
<string name="tap_skip">Tap to skip</string>
@ -188,6 +209,15 @@
<string name="submit_succeeded">Segment submitted successfully</string>
<string name="submit_started">Submitting segment…</string>
<string name="vote_failed_unknown_error" formatted="false">Unable to vote for segment: Status: %d %s</string>
<string name="vote_failed_forbidden">Can\'t vote for segment.\nA moderator has decided that this segment is correct</string>
<string name="vote_succeeded">Voted successfully</string>
<string name="vote_started">Voting for segment…</string>
<string name="vote_upvote">Upvote</string>
<string name="vote_downvote">Downvote</string>
<string name="vote_category">Change category</string>
<string name="vote_no_segments">There are no segments to vote for</string>
<string name="new_segment_choose_category">Choose the segment category</string>
<string name="new_segment_disabled_category">You\'ve disabled this category in the settings, enable it to be able to submit</string>
<string name="new_segment_title">New Sponsor Block segment</string>

View File

@ -2,10 +2,10 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath 'com.android.tools.build:gradle:4.2.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -15,7 +15,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -1,6 +1,6 @@
#Tue Aug 18 22:56:28 EEST 2020
#Mon Jun 07 19:51:48 CEST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME