diff --git a/integrations/AndroidManifest.xml b/integrations/AndroidManifest.xml index 80c110cdc..430ba65c7 100644 --- a/integrations/AndroidManifest.xml +++ b/integrations/AndroidManifest.xml @@ -1,12 +1,4 @@ - - - \ No newline at end of file diff --git a/integrations/java/com/google/android/apps/youtube/app/YouTubeApplication.java b/integrations/java/com/google/android/apps/youtube/app/YouTubeTikTokRoot_Application.java similarity index 81% rename from integrations/java/com/google/android/apps/youtube/app/YouTubeApplication.java rename to integrations/java/com/google/android/apps/youtube/app/YouTubeTikTokRoot_Application.java index ef8400e42..2c321f2a5 100644 --- a/integrations/java/com/google/android/apps/youtube/app/YouTubeApplication.java +++ b/integrations/java/com/google/android/apps/youtube/app/YouTubeTikTokRoot_Application.java @@ -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(); } diff --git a/integrations/java/fi/razerman/youtube/Helpers/XSwipeHelper.java b/integrations/java/fi/razerman/youtube/Helpers/XSwipeHelper.java new file mode 100644 index 000000000..7817e2b52 --- /dev/null +++ b/integrations/java/fi/razerman/youtube/Helpers/XSwipeHelper.java @@ -0,0 +1,8 @@ +package fi.razerman.youtube.Helpers; + +import android.view.ViewGroup; + +public class XSwipeHelper { + // Implementation in another repo + public static ViewGroup nextGenWatchLayout; +} \ No newline at end of file diff --git a/integrations/java/fi/vanced/libraries/youtube/player/PlayerType.java b/integrations/java/fi/vanced/libraries/youtube/player/PlayerType.java index 448ebe0c6..84eabe454 100644 --- a/integrations/java/fi/vanced/libraries/youtube/player/PlayerType.java +++ b/integrations/java/fi/vanced/libraries/youtube/player/PlayerType.java @@ -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); } } diff --git a/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java b/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java index 17446b90e..925e28f84 100644 --- a/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java +++ b/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java @@ -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; diff --git a/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java b/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java index af1d011f4..f2cedf983 100644 --- a/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java +++ b/integrations/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java @@ -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()); } } diff --git a/integrations/java/pl/jakubweg/InjectedPlugin.java b/integrations/java/pl/jakubweg/InjectedPlugin.java index 021038f68..bc6519a70 100644 --- a/integrations/java/pl/jakubweg/InjectedPlugin.java +++ b/integrations/java/pl/jakubweg/InjectedPlugin.java @@ -92,7 +92,6 @@ public class InjectedPlugin { Log.i(TAG, spacesStr + "Normal view: " + view); } } - } diff --git a/integrations/java/pl/jakubweg/PlayerController.java b/integrations/java/pl/jakubweg/PlayerController.java index 4019d4562..e884f575e 100644 --- a/integrations/java/pl/jakubweg/PlayerController.java +++ b/integrations/java/pl/jakubweg/PlayerController.java @@ -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) { diff --git a/integrations/java/pl/jakubweg/ShieldButton.java b/integrations/java/pl/jakubweg/ShieldButton.java index fb9e7c3f8..9cc0cd4cd 100644 --- a/integrations/java/pl/jakubweg/ShieldButton.java +++ b/integrations/java/pl/jakubweg/ShieldButton.java @@ -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 } diff --git a/integrations/java/pl/jakubweg/SkipSegmentView.java b/integrations/java/pl/jakubweg/SkipSegmentView.java index cd1a891b3..0d9fe81fe 100644 --- a/integrations/java/pl/jakubweg/SkipSegmentView.java +++ b/integrations/java/pl/jakubweg/SkipSegmentView.java @@ -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)); diff --git a/integrations/java/pl/jakubweg/SponsorBlockPreferenceFragment.java b/integrations/java/pl/jakubweg/SponsorBlockPreferenceFragment.java index 47b62e324..fe59048c1 100644 --- a/integrations/java/pl/jakubweg/SponsorBlockPreferenceFragment.java +++ b/integrations/java/pl/jakubweg/SponsorBlockPreferenceFragment.java @@ -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 preferencesToDisableWhenSBDisabled = new ArrayList<>(); + public static final DecimalFormat FORMATTER = new DecimalFormat("#,###,###"); + public static final String SAVED_TEMPLATE = "%dh %.1f minutes"; + private final ArrayList 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); diff --git a/integrations/java/pl/jakubweg/SponsorBlockSettings.java b/integrations/java/pl/jakubweg/SponsorBlockSettings.java index 72a5a3022..f644f95be 100644 --- a/integrations/java/pl/jakubweg/SponsorBlockSettings.java +++ b/integrations/java/pl/jakubweg/SponsorBlockSettings.java @@ -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 = ""; - 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 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 mValuesMap = new HashMap<>(7); + private static final Map 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) { diff --git a/integrations/java/pl/jakubweg/SponsorBlockUtils.java b/integrations/java/pl/jakubweg/SponsorBlockUtils.java index fea969bf4..190faf4a9 100644 --- a/integrations/java/pl/jakubweg/SponsorBlockUtils.java +++ b/integrations/java/pl/jakubweg/SponsorBlockUtils.java @@ -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 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 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(" %s
%s to %s", + segment.category.color, segment.category.title, start, end)); + if (i + 1 != segmentAmount) // prevents trailing new line after last segment + htmlBuilder.append("
"); + 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 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 { } } } - } diff --git a/integrations/java/pl/jakubweg/StringRef.java b/integrations/java/pl/jakubweg/StringRef.java index efd55c63d..903b3a718 100644 --- a/integrations/java/pl/jakubweg/StringRef.java +++ b/integrations/java/pl/jakubweg/StringRef.java @@ -24,7 +24,7 @@ public class StringRef { packageName = context.getPackageName(); } - private static HashMap strings = new HashMap<>(); + private static final HashMap 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 sf(id).toString() 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 diff --git a/integrations/java/pl/jakubweg/VotingButton.java b/integrations/java/pl/jakubweg/VotingButton.java new file mode 100644 index 000000000..f585f9683 --- /dev/null +++ b/integrations/java/pl/jakubweg/VotingButton.java @@ -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 _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 +} diff --git a/integrations/java/pl/jakubweg/SponsorSegment.java b/integrations/java/pl/jakubweg/objects/SponsorSegment.java similarity index 91% rename from integrations/java/pl/jakubweg/SponsorSegment.java rename to integrations/java/pl/jakubweg/objects/SponsorSegment.java index f96117d0d..1819bba71 100644 --- a/integrations/java/pl/jakubweg/SponsorSegment.java +++ b/integrations/java/pl/jakubweg/objects/SponsorSegment.java @@ -1,4 +1,6 @@ -package pl.jakubweg; +package pl.jakubweg.objects; + +import pl.jakubweg.SponsorBlockSettings; public class SponsorSegment implements Comparable { public final long start; @@ -26,5 +28,4 @@ public class SponsorSegment implements Comparable { public int compareTo(SponsorSegment o) { return (int) (this.start - o.start); } - } diff --git a/integrations/java/pl/jakubweg/objects/UserStats.java b/integrations/java/pl/jakubweg/objects/UserStats.java new file mode 100644 index 000000000..f638f610e --- /dev/null +++ b/integrations/java/pl/jakubweg/objects/UserStats.java @@ -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; + } +} \ No newline at end of file diff --git a/integrations/java/pl/jakubweg/requests/Requester.java b/integrations/java/pl/jakubweg/requests/Requester.java new file mode 100644 index 000000000..7531b0ad7 --- /dev/null +++ b/integrations/java/pl/jakubweg/requests/Requester.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/integrations/java/pl/jakubweg/requests/Route.java b/integrations/java/pl/jakubweg/requests/Route.java new file mode 100644 index 000000000..bf326c390 --- /dev/null +++ b/integrations/java/pl/jakubweg/requests/Route.java @@ -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 + } +} \ No newline at end of file diff --git a/integrations/res/drawable/ic_sb_voting.xml b/integrations/res/drawable/ic_sb_voting.xml new file mode 100644 index 000000000..616380350 --- /dev/null +++ b/integrations/res/drawable/ic_sb_voting.xml @@ -0,0 +1,5 @@ + + + diff --git a/integrations/res/values/strings.xml b/integrations/res/values/strings.xml index c5e342c80..2e68d5ecc 100644 --- a/integrations/res/values/strings.xml +++ b/integrations/res/values/strings.xml @@ -143,12 +143,16 @@ Switch this on for very cool sponsor segments skipping Enable new segment adding Switch this on to enable experimental segment adding (has button visibility issues). + Enable voting + Switch this on to enable voting. What to do with different segments General Show a toast when skipping segment automatically Click to see an example toast Skip count tracking 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. + Show time without segments + This time appears in brackets next to the current time. This shows the total video duration minus any segments. Adjusting new segment step This is the number of milliseconds you can move when you use the time adjustment buttons while adding new segment Your unique user id @@ -165,6 +169,8 @@ Similar to "sponsor" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated with Music: Non-Music Section Only for use in music videos. This includes introductions or outros in music videos + Preview/Recap + 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. Skipped sponsor Skipped intro Skipped outro @@ -172,12 +178,27 @@ Skipped self promotion Skipped silence Skipped preview + Skipped unsubmitted segment Skip automatically Show a skip button Don\'t do anything + + Stats + Loading.. + SponsorBlock is disabled + Your username: <b>%s</b> + Click to change your username + Unable to change username: Status: %d %s + Username changed successfully + Submissions: <b>%s</b> + You\'ve saved people from <b>%s</b> segments. + That\'s <b>%s</b> of their lives. Click to see the leaderboard + You\'ve skipped <b>%s</b> segments. + That\'s <b>%s</b>. + About - This app uses the API from Sponsor Block - Tap to learn more at: sponsor.ajay.app + This app uses the API from SponsorBlock + Tap to learn more, and see downloads for other platforms at: sponsor.ajay.app Integration made by JakubWeg Tap to skip @@ -188,6 +209,15 @@ Segment submitted successfully Submitting segment… + Unable to vote for segment: Status: %d %s + Can\'t vote for segment.\nA moderator has decided that this segment is correct + Voted successfully + Voting for segment… + Upvote + Downvote + Change category + There are no segments to vote for + Choose the segment category You\'ve disabled this category in the settings, enable it to be able to submit New Sponsor Block segment