mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-01-05 17:45:49 +01:00
RYD and Video ad whitelisting base
This commit is contained in:
parent
739d58d5ae
commit
306457ef84
@ -0,0 +1,25 @@
|
||||
package com.google.android.apps.youtube.app.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class SlimMetadataScrollableButtonContainerLayout extends ViewGroup {
|
||||
|
||||
public SlimMetadataScrollableButtonContainerLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SlimMetadataScrollableButtonContainerLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SlimMetadataScrollableButtonContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
|
||||
|
||||
}
|
||||
}
|
213
app/src/main/java/fi/vanced/libraries/youtube/ads/VideoAds.java
Normal file
213
app/src/main/java/fi/vanced/libraries/youtube/ads/VideoAds.java
Normal file
@ -0,0 +1,213 @@
|
||||
package fi.vanced.libraries.youtube.ads;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.channelName;
|
||||
import static fi.vanced.libraries.youtube.ui.SlimButtonContainer.adBlockButton;
|
||||
import static fi.vanced.utils.VancedUtils.getPreferences;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import fi.razerman.youtube.XGlobals;
|
||||
import fi.vanced.libraries.youtube.player.ChannelModel;
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.utils.ObjectSerializer;
|
||||
|
||||
public class VideoAds {
|
||||
public static final String TAG = "VI - VideoAds";
|
||||
public static final String PREFERENCES_NAME = "channel-whitelist";
|
||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1";
|
||||
private static final String YT_API_KEY = "replaceMeWithTheYouTubeAPIKey";
|
||||
private static ArrayList<ChannelModel> whiteList;
|
||||
private static Thread fetchThread = null;
|
||||
|
||||
static {
|
||||
whiteList = parseWhitelist(YouTubeTikTokRoot_Application.getAppContext());
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void setChannelName(String channelName) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "channel name set to " + channelName);
|
||||
}
|
||||
VideoInformation.channelName = channelName;
|
||||
|
||||
if (adBlockButton != null) {
|
||||
adBlockButton.changeEnabled(getShouldShowAds());
|
||||
}
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code (CURRENTLY NOT USED)
|
||||
public static void newVideoLoaded(String videoId) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "newVideoLoaded - " + videoId);
|
||||
}
|
||||
|
||||
try {
|
||||
if (fetchThread != null && fetchThread.getState() != Thread.State.TERMINATED) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Interrupting the thread. Current state " + fetchThread.getState());
|
||||
}
|
||||
fetchThread.interrupt();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error in the fetch thread", ex);
|
||||
}
|
||||
|
||||
fetchThread = new Thread(() -> {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching channelId for " + videoId);
|
||||
}
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(YT_API_URL + "/player?key=" + YT_API_KEY).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json; utf-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(2 * 1000);
|
||||
|
||||
// TODO: Actually fetch the version
|
||||
String jsonInputString = "{\"context\": {\"client\": { \"clientName\": \"Android\", \"clientVersion\": \"16.49.37\" } }, \"videoId\": \"" + videoId + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
JSONObject videoInfo = json.getJSONObject("videoDetails");
|
||||
ChannelModel channelModel = new ChannelModel(videoInfo.getString("author"), videoInfo.getString("channelId"));
|
||||
if (debug) {
|
||||
Log.d(TAG, "channelId " + channelModel.getChannelId() + " fetched for author " + channelModel.getAuthor());
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "player fetch response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to fetch channelId", ex);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
fetchThread.start();
|
||||
}
|
||||
|
||||
public static boolean getShouldShowAds() {
|
||||
if (channelName == null || channelName.isEmpty() || channelName.trim().isEmpty()) {
|
||||
if (XGlobals.debug) {
|
||||
Log.d(TAG, "getShouldShowAds skipped because channelId was null");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ChannelModel channelModel: whiteList) {
|
||||
if (channelModel.getAuthor().equals(channelName)) {
|
||||
if (XGlobals.debug) {
|
||||
Log.d(TAG, "Video ad whitelist for " + channelName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean addToWhitelist(Context context, String channelName, String channelId) {
|
||||
try {
|
||||
whiteList.add(new ChannelModel(channelName, channelId));
|
||||
updateWhitelist(context);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.d(TAG, "Unable to add " + channelName + " with id " + channelId + " to whitelist");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean removeFromWhitelist(Context context, String channelName) {
|
||||
try {
|
||||
//whiteList.removeIf(x -> x.getAuthor().equals(channelName)); // Requires Android N
|
||||
|
||||
Iterator<ChannelModel> iterator = whiteList.iterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
ChannelModel value = iterator.next();
|
||||
if (value.getAuthor().equals(channelName))
|
||||
{
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateWhitelist(context);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.d(TAG, "Unable to remove " + channelName + " from whitelist");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void updateWhitelist(Context context) {
|
||||
if (context == null) return;
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
|
||||
try {
|
||||
editor.putString("channels", ObjectSerializer.serialize(whiteList));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private static ArrayList<ChannelModel> parseWhitelist(Context context) {
|
||||
if (context == null) return new ArrayList<>();
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
try {
|
||||
String channels = preferences.getString("channels", null);
|
||||
if (channels == null) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "channels string was null for ad whitelisting");
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ArrayList<ChannelModel> channelModels = (ArrayList<ChannelModel>) ObjectSerializer.deserialize(channels);
|
||||
if (debug) {
|
||||
Log.d(TAG, channels);
|
||||
for (ChannelModel channelModel: channelModels) {
|
||||
Log.d(TAG, "Ad whitelisted " + channelModel.getAuthor() + " with id of " + channelModel.getChannelId());
|
||||
}
|
||||
}
|
||||
|
||||
return channelModels;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fi.vanced.libraries.youtube.player;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ChannelModel implements Serializable {
|
||||
private String author;
|
||||
private String channelId;
|
||||
|
||||
public ChannelModel(String author, String channelId) {
|
||||
this.author = author;
|
||||
this.channelId = channelId;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getChannelId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
public void setChannelId(String channelId) {
|
||||
this.channelId = channelId;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package fi.vanced.libraries.youtube.player;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
public class VideoHelpers {
|
||||
public static final String TAG = "VideoHelpers";
|
||||
|
||||
public static void copyVideoUrlToClipboard() {
|
||||
generateVideoUrl(false);
|
||||
}
|
||||
|
||||
public static void copyVideoUrlWithTimeStampToClipboard() {
|
||||
generateVideoUrl(true);
|
||||
}
|
||||
|
||||
private static void generateVideoUrl(boolean appendTimeStamp) {
|
||||
try {
|
||||
String videoId = VideoInformation.currentVideoId;
|
||||
if (videoId == null || videoId.isEmpty()) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "VideoId was empty");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String videoUrl = String.format("https://youtu.be/%s", videoId);
|
||||
if (appendTimeStamp) {
|
||||
long videoTime = VideoInformation.lastKnownVideoTime;
|
||||
videoUrl += String.format("?t=%s", (videoTime / 1000));
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "Video URL: " + videoUrl);
|
||||
}
|
||||
|
||||
setClipboard(YouTubeTikTokRoot_Application.getAppContext(), videoUrl);
|
||||
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), str("share_copy_url_success"), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Couldn't generate video url", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setClipboard(Context context, String text) {
|
||||
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
} else {
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText("link", text);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,45 @@
|
||||
package fi.vanced.libraries.youtube.player;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import fi.vanced.libraries.youtube.ryd.ReturnYouTubeDislikes;
|
||||
|
||||
public class VideoInformation {
|
||||
private static final String TAG = "VI - VideoInfo";
|
||||
|
||||
public static String currentVideoId;
|
||||
public static Integer dislikeCount = null;
|
||||
public static String channelName = null;
|
||||
public static long lastKnownVideoTime = -1L;
|
||||
|
||||
// Call hook in the YT code when the video changes
|
||||
public static void setCurrentVideoId(final String videoId) {
|
||||
if (videoId == null) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "setCurrentVideoId - new id was null - currentVideoId was" + currentVideoId);
|
||||
}
|
||||
currentVideoId = null;
|
||||
dislikeCount = null;
|
||||
channelName = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoId.equals(currentVideoId)) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "setCurrentVideoId - new and current video were equal - " + videoId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "setCurrentVideoId - video id updated from " + currentVideoId + " to " + videoId);
|
||||
}
|
||||
|
||||
currentVideoId = videoId;
|
||||
|
||||
// New video
|
||||
ReturnYouTubeDislikes.newVideoLoaded(videoId);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,149 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.utils.VancedUtils.getPreferences;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
import static fi.vanced.utils.VancedUtils.randomString;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Registration {
|
||||
private static final String TAG = "VI - RYD - Registration";
|
||||
public static final String PREFERENCES_NAME = "ryd";
|
||||
|
||||
private String userId;
|
||||
private Context context;
|
||||
|
||||
public Registration(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId != null ? userId : fetchUserId();
|
||||
}
|
||||
|
||||
private String fetchUserId() {
|
||||
try {
|
||||
if (this.context == null) throw new Exception("Unable to fetch userId because context was null");
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
this.userId = preferences.getString("userId", null);
|
||||
|
||||
if (this.userId == null) {
|
||||
this.userId = register();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to fetch the userId from shared preferences", ex);
|
||||
}
|
||||
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
private void saveUserId(String userId) {
|
||||
try {
|
||||
if (this.context == null) throw new Exception("Unable to save userId because context was null");
|
||||
|
||||
SharedPreferences preferences = getPreferences(context, PREFERENCES_NAME);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString("userId", userId).apply();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to save the userId in shared preferences", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String register() {
|
||||
try {
|
||||
// Generate a new userId
|
||||
String userId = randomString(36);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to register the following userId: " + userId);
|
||||
}
|
||||
|
||||
// Get the registration challenge
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/puzzle/registration?userId=" + userId).openConnection();
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration challenge - " + challenge + " with difficulty of " + difficulty);
|
||||
}
|
||||
|
||||
// Solve the puzzle
|
||||
String solution = Utils.solvePuzzle(challenge, difficulty);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration confirmation solution is " + solution);
|
||||
}
|
||||
|
||||
return confirmRegistration(userId, solution);
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Registration response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to register userId", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String confirmRegistration(String userId, String solution) {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to confirm registration for the following userId: " + userId + " with solution: " + solution);
|
||||
}
|
||||
|
||||
// Confirm registration
|
||||
HttpURLConnection confirmationCon = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/puzzle/registration?userId=" + userId).openConnection();
|
||||
confirmationCon.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
confirmationCon.setRequestMethod("POST");
|
||||
confirmationCon.setRequestProperty("Content-Type", "application/json");
|
||||
confirmationCon.setRequestProperty("Accept", "application/json");
|
||||
confirmationCon.setDoOutput(true);
|
||||
confirmationCon.setConnectTimeout(5 * 1000);
|
||||
|
||||
String jsonInputString = "{\"solution\": \"" + solution + "\"}";
|
||||
try(OutputStream os = confirmationCon.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (confirmationCon.getResponseCode() == 200) {
|
||||
String result = parseJson(confirmationCon);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration confirmation result was " + result);
|
||||
}
|
||||
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
saveUserId(userId);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Registration was successful for user " + userId);
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Registration confirmation response was " + confirmationCon.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to confirm registration", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
|
||||
import static fi.vanced.utils.VancedUtils.getIdentifier;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.dislikeCount;
|
||||
|
||||
public class ReturnYouTubeDislikes {
|
||||
public static final String RYD_API_URL = "https://returnyoutubedislikeapi.com";
|
||||
private static final String TAG = "VI - RYD";
|
||||
private static View _dislikeView = null;
|
||||
private static Thread _dislikeFetchThread = null;
|
||||
private static Thread _votingThread = null;
|
||||
private static Registration registration;
|
||||
private static Voting voting;
|
||||
private static boolean likeActive;
|
||||
private static boolean dislikeActive;
|
||||
private static int votingValue = 0; // 1 = like, -1 = dislike, 0 = no vote
|
||||
|
||||
static {
|
||||
registration = new Registration(YouTubeTikTokRoot_Application.getAppContext());
|
||||
voting = new Voting(YouTubeTikTokRoot_Application.getAppContext(), registration);
|
||||
}
|
||||
|
||||
public static void newVideoLoaded(String videoId) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "newVideoLoaded - " + videoId);
|
||||
}
|
||||
|
||||
try {
|
||||
if (_dislikeFetchThread != null && _dislikeFetchThread.getState() != Thread.State.TERMINATED) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Interrupting the thread. Current state " + _dislikeFetchThread.getState());
|
||||
}
|
||||
_dislikeFetchThread.interrupt();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error in the dislike fetch thread", ex);
|
||||
}
|
||||
|
||||
_dislikeFetchThread = new Thread(() -> {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching dislikes for " + videoId);
|
||||
}
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(RYD_API_URL + "/votes?videoId=" + videoId).openConnection();
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
dislikeCount = json.getInt("dislikes");
|
||||
if (debug) {
|
||||
Log.d(TAG, "dislikes fetched - " + dislikeCount);
|
||||
}
|
||||
|
||||
// Set the dislikes
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable () {
|
||||
@Override
|
||||
public void run () {
|
||||
trySetDislikes(String.valueOf(dislikeCount));
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "dislikes fetch response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
dislikeCount = null;
|
||||
Log.e(TAG, "Failed to fetch dislikes", ex);
|
||||
return;
|
||||
}
|
||||
});
|
||||
_dislikeFetchThread.start();
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void setLikeTag(View view) {
|
||||
setTag(view, "like");
|
||||
}
|
||||
|
||||
public static void setLikeTag(View view, boolean active) {
|
||||
likeActive = active;
|
||||
if (likeActive) {
|
||||
votingValue = 1;
|
||||
}
|
||||
if (debug) {
|
||||
Log.d(TAG, "Like tag active " + likeActive);
|
||||
}
|
||||
setTag(view, "like");
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void setDislikeTag(View view) {
|
||||
_dislikeView = view;
|
||||
setTag(view, "dislike");
|
||||
}
|
||||
|
||||
public static void setDislikeTag(View view, boolean active) {
|
||||
dislikeActive = active;
|
||||
if (dislikeActive) {
|
||||
votingValue = -1;
|
||||
}
|
||||
_dislikeView = view;
|
||||
if (debug) {
|
||||
Log.d(TAG, "Dislike tag active " + dislikeActive);
|
||||
}
|
||||
setTag(view, "dislike");
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static CharSequence onSetText(View view, CharSequence originalText) {
|
||||
return handleOnSetText(view, originalText);
|
||||
}
|
||||
|
||||
// Call to this needs to be injected in YT code
|
||||
public static void onClick(View view, boolean inactive) {
|
||||
handleOnClick(view, inactive);
|
||||
}
|
||||
|
||||
private static CharSequence handleOnSetText(View view, CharSequence originalText) {
|
||||
try {
|
||||
CharSequence tag = (CharSequence) view.getTag();
|
||||
if (debug) {
|
||||
Log.d(TAG, "handleOnSetText - " + tag + " - original text - " + originalText);
|
||||
}
|
||||
if (tag == null) return originalText;
|
||||
|
||||
if (tag == "like") {
|
||||
return originalText;
|
||||
}
|
||||
else if (tag == "dislike") {
|
||||
return dislikeCount != null ? String.valueOf(dislikeCount) : originalText;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error while handling the setText", ex);
|
||||
}
|
||||
|
||||
return originalText;
|
||||
}
|
||||
|
||||
private static void trySetDislikes(String dislikeCount) {
|
||||
try {
|
||||
// Try to set normal video dislike count
|
||||
if (_dislikeView == null) {
|
||||
if (debug) { Log.d(TAG, "_dislikeView was null"); }
|
||||
return;
|
||||
}
|
||||
|
||||
View buttonView = _dislikeView.findViewById(getIdentifier("button_text", "id"));
|
||||
if (buttonView == null) {
|
||||
if (debug) { Log.d(TAG, "buttonView was null"); }
|
||||
return;
|
||||
}
|
||||
TextView button = (TextView) buttonView;
|
||||
button.setText(dislikeCount);
|
||||
if (debug) {
|
||||
Log.d(TAG, "trySetDislikes - " + dislikeCount);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (debug) {
|
||||
Log.e(TAG, "Error while trying to set dislikes text", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleOnClick(View view, boolean previousState) {
|
||||
try {
|
||||
String tag = (String) view.getTag();
|
||||
if (debug) {
|
||||
Log.d(TAG, "handleOnClick - " + tag + " - previousState - " + previousState);
|
||||
}
|
||||
if (tag == null) return;
|
||||
|
||||
// If active status was removed, vote should be none
|
||||
if (previousState) { votingValue = 0; }
|
||||
if (tag == "like") {
|
||||
dislikeActive = false;
|
||||
|
||||
// Like was activated
|
||||
if (!previousState) { votingValue = 1; likeActive = true; }
|
||||
else { likeActive = false; }
|
||||
|
||||
// Like was activated and dislike was previously activated
|
||||
if (!previousState && dislikeActive) { dislikeCount--; trySetDislikes(String.valueOf(dislikeCount)); }
|
||||
}
|
||||
else if (tag == "dislike") {
|
||||
likeActive = false;
|
||||
|
||||
// Dislike was activated
|
||||
if (!previousState) { votingValue = -1; dislikeActive = true; dislikeCount++; }
|
||||
// Dislike was removed
|
||||
else { dislikeActive = false; dislikeCount--; }
|
||||
trySetDislikes(String.valueOf(dislikeCount));
|
||||
}
|
||||
else {
|
||||
// Unknown tag
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "New vote status - " + votingValue);
|
||||
Log.d(TAG, "Like button " + likeActive + " | Dislike button " + dislikeActive);
|
||||
}
|
||||
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), "Voting value: " + votingValue, Toast.LENGTH_SHORT).show();
|
||||
|
||||
sendVote(votingValue);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error while handling the onClick", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendVote(int vote) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "sending vote - " + vote + " for video " + currentVideoId);
|
||||
}
|
||||
|
||||
try {
|
||||
if (_votingThread != null && _votingThread.getState() != Thread.State.TERMINATED) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Interrupting the thread. Current state " + _votingThread.getState());
|
||||
}
|
||||
_votingThread.interrupt();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error in the voting thread", ex);
|
||||
}
|
||||
|
||||
_votingThread = new Thread(() -> {
|
||||
try {
|
||||
boolean result = voting.sendVote(currentVideoId, vote);
|
||||
if (debug) {
|
||||
Log.d(TAG, "sendVote status " + result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to send vote", ex);
|
||||
return;
|
||||
}
|
||||
});
|
||||
_votingThread.start();
|
||||
}
|
||||
|
||||
private static void setTag(View view, String tag) {
|
||||
try {
|
||||
if (view == null) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "View was empty");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Log.d(TAG, "setTag - " + tag);
|
||||
}
|
||||
|
||||
view.setTag(tag);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Error while trying to set tag to view", ex);
|
||||
}
|
||||
}
|
||||
}
|
64
app/src/main/java/fi/vanced/libraries/youtube/ryd/Utils.java
Normal file
64
app/src/main/java/fi/vanced/libraries/youtube/ryd/Utils.java
Normal file
@ -0,0 +1,64 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Base64;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = "VI - RYD - Utils";
|
||||
|
||||
static String solvePuzzle(String challenge, int difficulty) {
|
||||
byte[] decodedChallenge = Base64.decode(challenge, Base64.NO_WRAP);
|
||||
|
||||
byte[] buffer = new byte[20];
|
||||
for (int i = 4; i < 20; i++) {
|
||||
buffer[i] = decodedChallenge[i - 4];
|
||||
}
|
||||
|
||||
try {
|
||||
int maxCount = (int) (Math.pow(2, difficulty + 1) * 5);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
for (int i = 0; i < maxCount; i++) {
|
||||
buffer[0] = (byte)i;
|
||||
buffer[1] = (byte)(i >> 8);
|
||||
buffer[2] = (byte)(i >> 16);
|
||||
buffer[3] = (byte)(i >> 24);
|
||||
byte[] messageDigest = md.digest(buffer);
|
||||
|
||||
if (countLeadingZeroes(messageDigest) >= difficulty) {
|
||||
String encode = Base64.encodeToString(new byte[]{buffer[0], buffer[1], buffer[2], buffer[3]}, Base64.NO_WRAP);
|
||||
return encode;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to solve puzzle", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static int countLeadingZeroes(byte[] uInt8View) {
|
||||
int zeroes = 0;
|
||||
int value = 0;
|
||||
for (int i = 0; i < uInt8View.length; i++) {
|
||||
value = uInt8View[i] & 0xFF;
|
||||
if (value == 0) {
|
||||
zeroes += 8;
|
||||
} else {
|
||||
int count = 1;
|
||||
if (value >>> 4 == 0) {
|
||||
count += 4;
|
||||
value <<= 4;
|
||||
}
|
||||
if (value >>> 6 == 0) {
|
||||
count += 2;
|
||||
value <<= 2;
|
||||
}
|
||||
zeroes += count - (value >>> 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return zeroes;
|
||||
}
|
||||
}
|
120
app/src/main/java/fi/vanced/libraries/youtube/ryd/Voting.java
Normal file
120
app/src/main/java/fi/vanced/libraries/youtube/ryd/Voting.java
Normal file
@ -0,0 +1,120 @@
|
||||
package fi.vanced.libraries.youtube.ryd;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Voting {
|
||||
private static final String TAG = "VI - RYD - Voting";
|
||||
|
||||
private Registration registration;
|
||||
private Context context;
|
||||
|
||||
public Voting(Context context, Registration registration) {
|
||||
this.context = context;
|
||||
this.registration = registration;
|
||||
}
|
||||
|
||||
public boolean sendVote(String videoId, int vote) {
|
||||
try {
|
||||
String userId = registration.getUserId();
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to vote the following video: " + videoId + " with vote " + vote + " and userId: " + userId);
|
||||
}
|
||||
|
||||
// Send the vote
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/interact/vote").openConnection();
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(5 * 1000);
|
||||
String voteJsonString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"value\": \"" + vote + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = voteJsonString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
String challenge = json.getString("challenge");
|
||||
int difficulty = json.getInt("difficulty");
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote challenge - " + challenge + " with difficulty of " + difficulty);
|
||||
}
|
||||
|
||||
// Solve the puzzle
|
||||
String solution = Utils.solvePuzzle(challenge, difficulty);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote confirmation solution is " + solution);
|
||||
}
|
||||
|
||||
// Confirm vote
|
||||
return confirmVote(userId, videoId, solution);
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Vote response was " + connection.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to send vote", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean confirmVote(String userId, String videoId, String solution) {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Trying to confirm vote for video: " + videoId + " with solution " + solution + " and userId: " + userId);
|
||||
}
|
||||
|
||||
// Confirm vote
|
||||
HttpURLConnection confirmationCon = (HttpURLConnection) new URL(ReturnYouTubeDislikes.RYD_API_URL + "/interact/confirmVote").openConnection();
|
||||
confirmationCon.setRequestProperty("User-agent", System.getProperty("http.agent") + ";vanced");
|
||||
confirmationCon.setRequestMethod("POST");
|
||||
confirmationCon.setRequestProperty("Content-Type", "application/json");
|
||||
confirmationCon.setRequestProperty("Accept", "application/json");
|
||||
confirmationCon.setDoOutput(true);
|
||||
confirmationCon.setConnectTimeout(5 * 1000);
|
||||
|
||||
String jsonInputString = "{\"userId\": \"" + userId + "\", \"videoId\": \"" + videoId + "\", \"solution\": \"" + solution + "\"}";
|
||||
try(OutputStream os = confirmationCon.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (confirmationCon.getResponseCode() == 200) {
|
||||
String result = parseJson(confirmationCon);
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote confirmation result was " + result);
|
||||
}
|
||||
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Vote was successful for user " + userId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (debug) {
|
||||
Log.d(TAG, "Vote confirmation response was " + confirmationCon.getResponseCode());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to send vote", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
135
app/src/main/java/fi/vanced/libraries/youtube/ui/AdBlock.java
Normal file
135
app/src/main/java/fi/vanced/libraries/youtube/ui/AdBlock.java
Normal file
@ -0,0 +1,135 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
import static fi.vanced.libraries.youtube.ads.VideoAds.getShouldShowAds;
|
||||
import static fi.vanced.libraries.youtube.player.VideoInformation.currentVideoId;
|
||||
import static fi.vanced.utils.VancedUtils.parseJson;
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import fi.vanced.libraries.youtube.ads.VideoAds;
|
||||
import fi.vanced.libraries.youtube.player.ChannelModel;
|
||||
import fi.vanced.libraries.youtube.player.VideoInformation;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class AdBlock extends SlimButton {
|
||||
private static final String TAG = "VI - AdBlock - Button";
|
||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1";
|
||||
private static final String YT_API_KEY = "replaceMeWithTheYouTubeAPIKey";
|
||||
|
||||
public AdBlock(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, true);
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_ad_button", "drawable"));
|
||||
this.button_text.setText(str("action_ads"));
|
||||
changeEnabled(getShouldShowAds());
|
||||
}
|
||||
|
||||
public void changeEnabled(boolean enabled) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "changeEnabled " + enabled);
|
||||
}
|
||||
this.button_icon.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
this.view.setEnabled(false);
|
||||
if (this.button_icon.isEnabled()) {
|
||||
removeFromWhitelist();
|
||||
return;
|
||||
}
|
||||
//this.button_icon.setEnabled(!this.button_icon.isEnabled());
|
||||
|
||||
addToWhiteList(this.view, this.button_icon);
|
||||
}
|
||||
|
||||
private void removeFromWhitelist() {
|
||||
try {
|
||||
VideoAds.removeFromWhitelist(this.context, VideoInformation.channelName);
|
||||
this.button_icon.setEnabled(false);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to remove from whitelist", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
this.view.setEnabled(true);
|
||||
}
|
||||
|
||||
private void addToWhiteList(View view, ImageView buttonIcon) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Fetching channelId for " + currentVideoId);
|
||||
}
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(YT_API_URL + "/player?key=" + YT_API_KEY).openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json; utf-8");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(2 * 1000);
|
||||
|
||||
// TODO: Actually fetch the version
|
||||
String jsonInputString = "{\"context\": {\"client\": { \"clientName\": \"Android\", \"clientVersion\": \"16.49.37\" } }, \"videoId\": \"" + currentVideoId + "\"}";
|
||||
try(OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
if (connection.getResponseCode() == 200) {
|
||||
JSONObject json = new JSONObject(parseJson(connection));
|
||||
JSONObject videoInfo = json.getJSONObject("videoDetails");
|
||||
ChannelModel channelModel = new ChannelModel(videoInfo.getString("author"), videoInfo.getString("channelId"));
|
||||
if (debug) {
|
||||
Log.d(TAG, "channelId " + channelModel.getChannelId() + " fetched for author " + channelModel.getAuthor());
|
||||
}
|
||||
|
||||
boolean success = VideoAds.addToWhitelist(this.context, channelModel.getAuthor(), channelModel.getChannelId());
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
if (success) {
|
||||
buttonIcon.setEnabled(true);
|
||||
Toast.makeText(context, "Channel " + channelModel.getAuthor() + " whitelisted", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
else {
|
||||
buttonIcon.setEnabled(false);
|
||||
Toast.makeText(context, "Channel " + channelModel.getAuthor() + " failed to whitelist", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
view.setEnabled(true);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (debug) {
|
||||
Log.d(TAG, "player fetch response was " + connection.getResponseCode());
|
||||
}
|
||||
|
||||
buttonIcon.setEnabled(false);
|
||||
this.view.setEnabled(true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Failed to fetch channelId", ex);
|
||||
this.view.setEnabled(true);
|
||||
return;
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoHelpers;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class CopyButton extends SlimButton {
|
||||
public CopyButton(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, SharedPrefUtils.getBoolean(context, "youtube", "pref_copy_video_url_button", false));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_copy_icon", "drawable"));
|
||||
this.button_text.setText(str("action_copy"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
VideoHelpers.copyVideoUrlToClipboard();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoHelpers;
|
||||
import fi.vanced.utils.SharedPrefUtils;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
public class CopyWithTimestamp extends SlimButton {
|
||||
public CopyWithTimestamp(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, SharedPrefUtils.getBoolean(context, "youtube", "pref_copy_video_url_timestamp_button", false));
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_yt_copy_icon_with_time", "drawable"));
|
||||
this.button_text.setText(str("action_tcopy"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
VideoHelpers.copyVideoUrlWithTimeStampToClipboard();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static fi.razerman.youtube.XGlobals.debug;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public abstract class SlimButton implements View.OnClickListener {
|
||||
private static final String TAG = "VI - Slim - Button";
|
||||
public static int SLIM_METADATA_BUTTON_ID;
|
||||
public final View view;
|
||||
public final Context context;
|
||||
private final ViewGroup container;
|
||||
protected final ImageView button_icon;
|
||||
protected final TextView button_text;
|
||||
|
||||
static {
|
||||
SLIM_METADATA_BUTTON_ID = VancedUtils.getIdentifier("slim_metadata_button", "layout");
|
||||
}
|
||||
|
||||
public SlimButton(Context context, ViewGroup container, int id, boolean visible) {
|
||||
if (debug) {
|
||||
Log.d(TAG, "Adding button with id " + id + " and visibility of " + visible);
|
||||
}
|
||||
this.context = context;
|
||||
this.container = container;
|
||||
view = LayoutInflater.from(context).inflate(id, container, false);
|
||||
button_icon = (ImageView)view.findViewById(VancedUtils.getIdentifier("button_icon", "id"));
|
||||
button_text = (TextView)view.findViewById(VancedUtils.getIdentifier("button_text", "id"));
|
||||
|
||||
view.setOnClickListener(this);
|
||||
setVisible(visible);
|
||||
|
||||
container.addView(view);
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
view.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
setContainerVisibility();
|
||||
}
|
||||
|
||||
private void setContainerVisibility() {
|
||||
if (container == null) return;
|
||||
|
||||
for (int i = 0; i < container.getChildCount(); i++) {
|
||||
if (container.getChildAt(i).getVisibility() == View.VISIBLE) {
|
||||
container.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
container.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.google.android.apps.youtube.app.ui.SlimMetadataScrollableButtonContainerLayout;
|
||||
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class SlimButtonContainer extends SlimMetadataScrollableButtonContainerLayout {
|
||||
private static final String TAG = "VI - Slim - Container";
|
||||
private ViewGroup container;
|
||||
private CopyButton copyButton;
|
||||
private CopyWithTimestamp copyWithTimestampButton;
|
||||
public static AdBlock adBlockButton;
|
||||
|
||||
public SlimButtonContainer(Context context) {
|
||||
super(context);
|
||||
this.initialize(context);
|
||||
}
|
||||
|
||||
public SlimButtonContainer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.initialize(context);
|
||||
}
|
||||
|
||||
public SlimButtonContainer(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.initialize(context);
|
||||
}
|
||||
|
||||
public void initialize(Context context) {
|
||||
try {
|
||||
container = this.findViewById(VancedUtils.getIdentifier("button_container_vanced", "id"));
|
||||
if (container == null) throw new Exception("Unable to initialize the button container because the button_container_vanced couldn't be found");
|
||||
|
||||
copyButton = new CopyButton(context, this);
|
||||
copyWithTimestampButton = new CopyWithTimestamp(context, this);
|
||||
adBlockButton = new AdBlock(context, this);
|
||||
new SponsorBlock(context, this);
|
||||
new SponsorBlockVoting(context, this);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.e(TAG, "Unable to initialize the button container", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import static pl.jakubweg.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import fi.vanced.libraries.youtube.player.VideoHelpers;
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class SponsorBlock extends SlimButton {
|
||||
public SponsorBlock(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, false);
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_sb_logo", "drawable"));
|
||||
this.button_text.setText("SB");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), "Nothing atm", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package fi.vanced.libraries.youtube.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import fi.vanced.utils.VancedUtils;
|
||||
|
||||
public class SponsorBlockVoting extends SlimButton {
|
||||
public SponsorBlockVoting(Context context, ViewGroup container) {
|
||||
super(context, container, SlimButton.SLIM_METADATA_BUTTON_ID, false);
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
this.button_icon.setImageResource(VancedUtils.getIdentifier("vanced_sb_voting", "drawable"));
|
||||
this.button_text.setText("SB Voting");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Toast.makeText(YouTubeTikTokRoot_Application.getAppContext(), "Nothing atm", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
83
app/src/main/java/fi/vanced/utils/ObjectSerializer.java
Normal file
83
app/src/main/java/fi/vanced/utils/ObjectSerializer.java
Normal file
@ -0,0 +1,83 @@
|
||||
package fi.vanced.utils;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Modifications copyright (C) 2022 Vanced
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ObjectSerializer {
|
||||
private static final String TAG = "VI - ObjectSerializer";
|
||||
|
||||
public static String serialize(Serializable obj) throws IOException {
|
||||
if (obj == null) return "";
|
||||
try {
|
||||
ByteArrayOutputStream serialObj = new ByteArrayOutputStream();
|
||||
ObjectOutputStream objStream = new ObjectOutputStream(serialObj);
|
||||
objStream.writeObject(obj);
|
||||
objStream.close();
|
||||
return encodeBytes(serialObj.toByteArray());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Serialization error: " + e.getMessage(), e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object deserialize(String str) throws IOException {
|
||||
if (str == null || str.length() == 0) return null;
|
||||
try {
|
||||
ByteArrayInputStream serialObj = new ByteArrayInputStream(decodeBytes(str));
|
||||
ObjectInputStream objStream = new ObjectInputStream(serialObj);
|
||||
return objStream.readObject();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Deserialization error: " + e.getMessage(), e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String encodeBytes(byte[] bytes) {
|
||||
StringBuffer strBuf = new StringBuffer();
|
||||
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
|
||||
strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
|
||||
}
|
||||
|
||||
return strBuf.toString();
|
||||
}
|
||||
|
||||
public static byte[] decodeBytes(String str) {
|
||||
byte[] bytes = new byte[str.length() / 2];
|
||||
for (int i = 0; i < str.length(); i+=2) {
|
||||
char c = str.charAt(i);
|
||||
bytes[i/2] = (byte) ((c - 'a') << 4);
|
||||
c = str.charAt(i+1);
|
||||
bytes[i/2] += (c - 'a');
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
43
app/src/main/java/fi/vanced/utils/SharedPrefUtils.java
Normal file
43
app/src/main/java/fi/vanced/utils/SharedPrefUtils.java
Normal file
@ -0,0 +1,43 @@
|
||||
package fi.vanced.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class SharedPrefUtils {
|
||||
public static void saveString(Context context, String preferenceName, String key, String value){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
sharedPreferences.edit().putString(key, value).apply();
|
||||
}
|
||||
public static void saveBoolean(Context context, String preferenceName, String key, Boolean value){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
sharedPreferences.edit().putBoolean(key, value).apply();
|
||||
}
|
||||
public static void saveInt(Context context, String preferenceName, String key, Integer value){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
sharedPreferences.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
public static String getString(Context context, String preferenceName, String key){
|
||||
return getString(context, preferenceName, key, null);
|
||||
}
|
||||
public static String getString(Context context, String preferenceName, String key, String _default){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
return (sharedPreferences.getString(key, _default));
|
||||
}
|
||||
|
||||
public static Boolean getBoolean(Context context, String preferenceName, String key){
|
||||
return getBoolean(context, preferenceName, key, false);
|
||||
}
|
||||
public static Boolean getBoolean(Context context, String preferenceName, String key, Boolean _default){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
return (sharedPreferences.getBoolean(key, _default));
|
||||
}
|
||||
|
||||
public static Integer getInt(Context context, String preferenceName, String key){
|
||||
return getInt(context, preferenceName, key, -1);
|
||||
}
|
||||
public static Integer getInt(Context context, String preferenceName, String key, Integer _default){
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE);
|
||||
return (sharedPreferences.getInt(key, _default));
|
||||
}
|
||||
}
|
49
app/src/main/java/fi/vanced/utils/VancedUtils.java
Normal file
49
app/src/main/java/fi/vanced/utils/VancedUtils.java
Normal file
@ -0,0 +1,49 @@
|
||||
package fi.vanced.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.google.android.apps.youtube.app.YouTubeTikTokRoot_Application;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class VancedUtils {
|
||||
|
||||
public static SharedPreferences getPreferences(Context context, String preferencesName) {
|
||||
if (context == null) return null;
|
||||
return context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public 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();
|
||||
}
|
||||
|
||||
public static int getIdentifier(String name, String defType) {
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
return context.getResources().getIdentifier(name, defType, context.getPackageName());
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/157202
|
||||
static final String AB = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
static SecureRandom rnd = new SecureRandom();
|
||||
|
||||
public static String randomString(int len){
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
for(int i = 0; i < len; i++)
|
||||
sb.append(AB.charAt(rnd.nextInt(AB.length())));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -64,8 +64,6 @@ public class PlayerController {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoInformation.currentVideoId = videoId;
|
||||
|
||||
Context context = YouTubeTikTokRoot_Application.getAppContext();
|
||||
if(context == null){
|
||||
Log.e(TAG, "context is null");
|
||||
|
@ -333,4 +333,8 @@
|
||||
<string name="color_invalid">Invalid hex code</string>
|
||||
<string name="change">Change</string>
|
||||
<string name="reset">Reset</string>
|
||||
|
||||
<string name="action_copy">Copy</string>
|
||||
<string name="action_tcopy">TCopy</string>
|
||||
<string name="action_ads">Ads</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user