diff --git a/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java b/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java index 17446b9..925e28f 100644 --- a/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java +++ b/app/src/main/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/app/src/main/java/pl/jakubweg/PlayerController.java b/app/src/main/java/pl/jakubweg/PlayerController.java index b996415..2afd00a 100644 --- a/app/src/main/java/pl/jakubweg/PlayerController.java +++ b/app/src/main/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; @@ -21,6 +22,11 @@ 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 { @@ -38,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; @@ -122,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) @@ -266,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.Unsubmitted && - 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(); } @@ -372,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); } @@ -386,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); } @@ -456,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); } }); } @@ -509,7 +509,7 @@ public class PlayerController { skipToMillisecond(segment.end + 2); SkipSegmentView.hide(); - if (segment.category == SponsorBlockSettings.SegmentInfo.Unsubmitted) { + if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) { SponsorSegment[] newSegments = new SponsorSegment[sponsorSegmentsOfCurrentVideo.length - 1]; int i = 0; for (SponsorSegment sponsorSegment : sponsorSegmentsOfCurrentVideo) { diff --git a/app/src/main/java/pl/jakubweg/ShieldButton.java b/app/src/main/java/pl/jakubweg/ShieldButton.java index 0acaf4f..6e15540 100644 --- a/app/src/main/java/pl/jakubweg/ShieldButton.java +++ b/app/src/main/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; diff --git a/app/src/main/java/pl/jakubweg/SkipSegmentView.java b/app/src/main/java/pl/jakubweg/SkipSegmentView.java index 5e2a485..0d9fe81 100644 --- a/app/src/main/java/pl/jakubweg/SkipSegmentView.java +++ b/app/src/main/java/pl/jakubweg/SkipSegmentView.java @@ -8,6 +8,8 @@ import android.widget.Toast; import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application; +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; diff --git a/app/src/main/java/pl/jakubweg/SponsorBlockPreferenceFragment.java b/app/src/main/java/pl/jakubweg/SponsorBlockPreferenceFragment.java index d4eabff..0e456ee 100644 --- a/app/src/main/java/pl/jakubweg/SponsorBlockPreferenceFragment.java +++ b/app/src/main/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,8 +17,11 @@ 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; @@ -40,8 +42,9 @@ 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) { @@ -65,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; }); } @@ -84,25 +84,17 @@ 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; }); } @@ -119,6 +111,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment implement addGeneralCategory(context, preferenceScreen); addSegmentsCategory(context, preferenceScreen); + addStatsCategory(context, preferenceScreen); addAboutCategory(context, preferenceScreen); enableCategoriesIfNeeded(SponsorBlockSettings.isSponsorBlockEnabled); @@ -173,6 +166,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); @@ -183,14 +191,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; }); } @@ -212,12 +217,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); } @@ -228,12 +230,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); diff --git a/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java b/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java index 985ef64..b1f0e49 100644 --- a/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java +++ b/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java @@ -25,12 +25,11 @@ public class SponsorBlockSettings { public static final String PREFERENCES_KEY_SEEN_GUIDELINES = "sb-seen-gl"; public static final String PREFERENCES_KEY_NEW_SEGMENT_ENABLED = "sb-new-segment-enabled"; public static final String PREFERENCES_KEY_VOTING_ENABLED = "sb-voting-enabled"; - 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 sponsorBlockVoteUrl = "https://sponsor.ajay.app/api/voteOnSponsorTime"; + 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; @@ -41,7 +40,9 @@ public class SponsorBlockSettings { 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 @@ -49,22 +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 String getSponsorBlockVoteUrl(String uuid, String userId, int type) { - return sponsorBlockVoteUrl + "?UUID=" + uuid + "&userID=" + userId + "&type=" + type; - } - - public static String getSponsorBlockVoteUrl(String uuid, String userId, String category) { - return sponsorBlockVoteUrl + "?UUID=" + uuid + "&userID=" + userId + "&category=" + category; - } - public static SharedPreferences getPreferences(Context context) { return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); } @@ -134,6 +119,8 @@ public class SponsorBlockSettings { 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); @@ -154,9 +141,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; @@ -175,26 +162,25 @@ 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", sf("segments_preview"), sf("skipped_preview"), sf("segments_preview_sum"), null, 0xFF008fd6), - Unsubmitted("unsubmitted", StringRef.empty, sf("skipped_unsubmitted"), StringRef.empty, SegmentBehaviour.SkipAutomatically, 0xFFFFFFFF), - ; + 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[] mValuesWithoutUnsubmitted = new SegmentInfo[]{ - Sponsor, - Intro, - Outro, - Interaction, - SelfPromo, - MusicOfftopic, - Preview + private static final SegmentInfo[] mValuesWithoutUnsubmitted = new SegmentInfo[]{ + SPONSOR, + INTRO, + OUTRO, + INTERACTION, + SELF_PROMO, + MUSIC_OFFTOPIC, + PREVIEW }; - private static Map mValuesMap = new HashMap<>(8); + private static final Map mValuesMap = new HashMap<>(values().length); static { for (SegmentInfo value : valuesWithoutUnsubmitted()) diff --git a/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java b/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java index e5ef0d5..127a07b 100644 --- a/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java +++ b/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java @@ -4,52 +4,46 @@ 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.TextView; import android.widget.Toast; -import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application; - -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.List; -import java.util.Locale; import java.util.Objects; import java.util.TimeZone; -import fi.razerman.youtube.Helpers.XSwipeHelper; -import fi.razerman.youtube.XGlobals; +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.getSponsorBlockVoteUrl; -import static pl.jakubweg.SponsorBlockSettings.sponsorBlockSkipSegmentsUrl; -import static pl.jakubweg.SponsorBlockSettings.uuid; +import static pl.jakubweg.SponsorBlockPreferenceFragment.FORMATTER; +import static pl.jakubweg.SponsorBlockPreferenceFragment.SAVED_TEMPLATE; +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 { @@ -61,26 +55,20 @@ public abstract class SponsorBlockUtils { public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_FORMAT); public static final SimpleDateFormat withoutSegmentsFormatter = new SimpleDateFormat(WITHOUT_SEGMENTS_FORMAT); public static final SimpleDateFormat withoutSegmentsFormatterH = new SimpleDateFormat(WITHOUT_SEGMENTS_FORMAT_H); - private static boolean videoHasSegments = false; - private static String timeWithoutSegments = ""; + 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 = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (debug) { - Log.d(TAG, "Vote button clicked"); - } - SponsorBlockUtils.onVotingClicked(v.getContext()); + 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; @@ -171,134 +159,88 @@ public abstract class SponsorBlockUtils { new Thread(submitRunnable).start(); } }; - 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 final DialogInterface.OnClickListener segmentVoteClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final Context context = ((AlertDialog) dialog).getContext(); - final SponsorSegment segment = sponsorSegmentsOfCurrentVideo[which]; - - final VoteOption[] voteOptions = VoteOption.values(); - String[] items = new String[voteOptions.length]; - - for (int i = 0; i < voteOptions.length; i++) { - items[i] = voteOptions[i].title; - } - - new AlertDialog.Builder(context) - .setItems(items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - appContext = new WeakReference<>(context.getApplicationContext()); - switch (voteOptions[which]) { - case UPVOTE: - voteForSegment(segment, VoteOption.UPVOTE); - break; - case DOWNVOTE: - voteForSegment(segment, VoteOption.DOWNVOTE); - break; - case CATEGORY_CHANGE: - onNewCategorySelect(segment, context); - break; - } - } - }) - .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 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 DialogInterface.OnClickListener segmentVoteClickListener = (dialog, which) -> { + final Context context = ((AlertDialog) dialog).getContext(); + final SponsorSegment segment = sponsorSegmentsOfCurrentVideo[which]; + + final VoteOption[] voteOptions = VoteOption.values(); + String[] items = new String[voteOptions.length]; + + 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 = 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 { - - if (start < 0 || end < 0 || start >= end || segmentType == null || videoId == null || uuid == null) { - Log.e(TAG, "Unable to submit times, invalid parameters"); - return; - } - - 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); + 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; } - - if (videoId != null) - PlayerController.executeDownloadSegments(videoId); + 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 { @@ -344,7 +286,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)) @@ -362,7 +304,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)) @@ -383,7 +325,7 @@ public abstract class SponsorBlockUtils { 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) { + if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) { continue; } @@ -411,12 +353,7 @@ public abstract class SponsorBlockUtils { new AlertDialog.Builder(context) .setTitle(str("new_segment_choose_category")) - .setItems(titles, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - voteForSegment(segment, VoteOption.CATEGORY_CHANGE, values[which].key); - } - }) + .setItems(titles, (dialog, which) -> voteForSegment(segment, VoteOption.CATEGORY_CHANGE, appContext.get(), toastRunnable, values[which].key)) .show(); } @@ -431,7 +368,7 @@ public abstract class SponsorBlockUtils { final SponsorSegment[] segments = original == null ? new SponsorSegment[1] : Arrays.copyOf(original, original.length + 1); segments[segments.length - 1] = new SponsorSegment(newSponsorSegmentStartMillis, newSponsorSegmentEndMillis, - SponsorBlockSettings.SegmentInfo.Unsubmitted, null); + SponsorBlockSettings.SegmentInfo.UNSUBMITTED, null); Arrays.sort(segments); sponsorSegmentsOfCurrentVideo = segments; @@ -461,131 +398,6 @@ public abstract class SponsorBlockUtils { } } - 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(); - videoHasSegments = false; - timeWithoutSegments = ""; - 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"); - - videoHasSegments = true; - break; - } - - connection.disconnect(); - - } catch (Exception e) { - Log.e(TAG, "download segments failed", e); - } - - timeWithoutSegments = getTimeWithoutSegments(sponsorSegments); - - return sponsorSegments.toArray(new SponsorSegment[0]); - } - - private static int getIdentifier(String name, String defType) { - Context context = YouTubeTikTokRoot_Application.getAppContext(); - return context.getResources().getIdentifier(name, defType, context.getPackageName()); - } - - public static void sendViewCountRequest(SponsorSegment segment) { - try { - URL url = new URL(SponsorBlockSettings.getSponsorBlockViewedUrl(segment.UUID)); - - Log.d("sponsorblock", "requesting: " + url.getPath()); - - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.disconnect(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static void voteForSegment(SponsorSegment segment, VoteOption voteOption, String... args) { - messageToToast = null; - try { - String voteUrl = voteOption == VoteOption.CATEGORY_CHANGE - ? getSponsorBlockVoteUrl(segment.UUID, uuid, args[0]) - : getSponsorBlockVoteUrl(segment.UUID, uuid, voteOption == VoteOption.UPVOTE ? 1 : 0); - URL url = new URL(voteUrl); - - Toast.makeText(appContext.get(), str("vote_started"), Toast.LENGTH_SHORT).show(); - Log.d("sponsorblock", "requesting: " + url.getPath()); - - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - - switch (connection.getResponseCode()) { - default: - messageToToast = String.format(str("vote_failed_unknown_error"), connection.getResponseCode(), connection.getResponseMessage()); - break; - case 429: - messageToToast = str("vote_failed_rate_limit"); - break; - case 403: - messageToToast = str("vote_failed_forbidden"); - break; - case 200: - messageToToast = str("vote_succeeded"); - break; - } - - Log.i(TAG, "Voted for segment with status: " + connection.getResponseCode() + ", " + messageToToast); - new Handler(Looper.getMainLooper()).post(toastRunnable); - - connection.disconnect(); - } catch (IOException e) { - e.printStackTrace(); - } - } - public static String appendTimeWithoutSegments(String totalTime) { if (videoHasSegments && SponsorBlockSettings.showTimeWithoutSegments && !TextUtils.isEmpty(totalTime)) { return totalTime + timeWithoutSegments; @@ -594,7 +406,7 @@ public abstract class SponsorBlockUtils { return totalTime; } - public static String getTimeWithoutSegments(ArrayList sponsorSegmentsOfCurrentVideo) { + public static String getTimeWithoutSegments(List sponsorSegmentsOfCurrentVideo) { if (!SponsorBlockSettings.isSponsorBlockEnabled || !SponsorBlockSettings.showTimeWithoutSegments || sponsorSegmentsOfCurrentVideo == null) { return ""; } @@ -610,7 +422,6 @@ public abstract class SponsorBlockUtils { try { if (videoHasSegments && (playerType.equalsIgnoreCase("NONE"))) { PlayerController.setCurrentVideoId(null); - return; } } catch (Exception ex) { @@ -618,7 +429,76 @@ public abstract class SponsorBlockUtils { } } - private enum VoteOption { + 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; + } + + @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) -> { + Requester.setUsername((String) newUsername); + 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 enum VoteOption { UPVOTE(str("vote_upvote")), DOWNVOTE(str("vote_downvote")), CATEGORY_CHANGE(str("vote_category")); diff --git a/app/src/main/java/pl/jakubweg/StringRef.java b/app/src/main/java/pl/jakubweg/StringRef.java index efd55c6..903b3a7 100644 --- a/app/src/main/java/pl/jakubweg/StringRef.java +++ b/app/src/main/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/app/src/main/java/pl/jakubweg/VotingButton.java b/app/src/main/java/pl/jakubweg/VotingButton.java index 9b9f558..7d5d61a 100644 --- a/app/src/main/java/pl/jakubweg/VotingButton.java +++ b/app/src/main/java/pl/jakubweg/VotingButton.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; diff --git a/app/src/main/java/pl/jakubweg/SponsorSegment.java b/app/src/main/java/pl/jakubweg/objects/SponsorSegment.java similarity index 91% rename from app/src/main/java/pl/jakubweg/SponsorSegment.java rename to app/src/main/java/pl/jakubweg/objects/SponsorSegment.java index b6ea2d4..1819bba 100644 --- a/app/src/main/java/pl/jakubweg/SponsorSegment.java +++ b/app/src/main/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; diff --git a/app/src/main/java/pl/jakubweg/objects/UserStats.java b/app/src/main/java/pl/jakubweg/objects/UserStats.java new file mode 100644 index 0000000..f638f61 --- /dev/null +++ b/app/src/main/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/app/src/main/java/pl/jakubweg/requests/Requester.java b/app/src/main/java/pl/jakubweg/requests/Requester.java new file mode 100644 index 0000000..1095518 --- /dev/null +++ b/app/src/main/java/pl/jakubweg/requests/Requester.java @@ -0,0 +1,201 @@ +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 = ""; + + switch (responseCode) { + case 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); + break; + case 404: + break; + } + 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; + } + 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; + case 429: + SponsorBlockUtils.messageToToast = str("vote_failed_rate_limit"); + 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) { + try { + HttpURLConnection connection = getConnectionFromRoute(Route.CHANGE_USERNAME, SponsorBlockSettings.uuid, username); + 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/app/src/main/java/pl/jakubweg/requests/Route.java b/app/src/main/java/pl/jakubweg/requests/Route.java new file mode 100644 index 0000000..bf326c3 --- /dev/null +++ b/app/src/main/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/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d9d3991..e561d0a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -182,6 +182,16 @@ 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 + 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, and see downloads for other platforms at: sponsor.ajay.app diff --git a/build.gradle b/build.gradle index 996b567..ff0783a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,10 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.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() } }