diff --git a/app/src/main/java/pl/jakubweg/PlayerController.java b/app/src/main/java/pl/jakubweg/PlayerController.java index 9dc1a2f..808a964 100644 --- a/app/src/main/java/pl/jakubweg/PlayerController.java +++ b/app/src/main/java/pl/jakubweg/PlayerController.java @@ -21,6 +21,8 @@ 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; @SuppressLint({"LongLogTag"}) public class PlayerController { @@ -38,12 +40,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; @@ -121,7 +120,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) @@ -265,15 +264,12 @@ 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); - } + 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(); } @@ -371,13 +367,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); } @@ -385,13 +378,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); } @@ -455,18 +445,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); } }); } 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/SponsorBlockSettings.java b/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java index 0210966..165d5ff 100644 --- a/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java +++ b/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java @@ -25,10 +25,6 @@ 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 SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SkipAutomatically; @@ -48,22 +44,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); } diff --git a/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java b/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java index 299992f..aa9399b 100644 --- a/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java +++ b/app/src/main/java/pl/jakubweg/SponsorBlockUtils.java @@ -32,6 +32,9 @@ import java.util.Locale; import java.util.Objects; import java.util.TimeZone; +import pl.jakubweg.objects.SponsorSegment; +import pl.jakubweg.requests.Requester; + import static android.view.View.GONE; import static android.view.View.VISIBLE; import static fi.razerman.youtube.XGlobals.debug; @@ -39,10 +42,9 @@ 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.StringRef.str; +import static pl.jakubweg.requests.Requester.voteForSegment; @SuppressWarnings({"LongLogTag"}) public abstract class SponsorBlockUtils { @@ -51,23 +53,17 @@ public abstract class SponsorBlockUtils { @SuppressLint("SimpleDateFormat") public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_FORMAT); 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; @@ -158,134 +154,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 { @@ -398,12 +348,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(); } @@ -448,121 +393,7 @@ 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(); - 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; - } - - connection.disconnect(); - - } catch (Exception e) { - Log.e(TAG, "download segments failed", e); - } - - return sponsorSegments.toArray(new SponsorSegment[0]); - } - - 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(); - } - } - - private enum VoteOption { + public enum VoteOption { UPVOTE(str("vote_upvote")), DOWNVOTE(str("vote_downvote")), CATEGORY_CHANGE(str("vote_category")); @@ -606,4 +437,14 @@ public abstract class SponsorBlockUtils { } } } + + public static int countMatches(CharSequence seq, char c) + { + int count = 0; + for (int i = 0; i < seq.length(); i++) { + if (seq.charAt(i) == c) + count++; + } + return count; + } } 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..af9a3fc --- /dev/null +++ b/app/src/main/java/pl/jakubweg/requests/Requester.java @@ -0,0 +1,176 @@ +package pl.jakubweg.requests; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +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.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); + int responseCode = connection.getResponseCode(); + + 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); + } + } + 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 = String.format(str("vote_failed_unknown_error"), responseCode, connection.getResponseMessage()); + break; + } + new Handler(Looper.getMainLooper()).post(toastRunnable); + connection.disconnect(); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + + public static UserStats getUserStats() { + try { + HttpURLConnection connection = getConnectionFromRoute(Route.GET_USER_STATS, SponsorBlockSettings.uuid); + JSONObject json = new JSONObject(parseJson(connection)); + connection.disconnect(); + return new UserStats(json.getString("userName"), json.getDouble("minutesSaved"), json.getInt("segmentCount"), + json.getInt("viewCount")); + } + catch (Exception ex) { + ex.printStackTrace(); + } + return new UserStats("", 0, 0, 0); + } + + 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..5cbf44e --- /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"); + 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