VancedIntegrations/app/src/main/java/fi/vanced/libraries/youtube/ads/VideoAds.java

238 lines
8.7 KiB
Java

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;
import fi.vanced.utils.SharedPrefUtils;
public class VideoAds {
public static final String TAG = "VI - VideoAds";
public static final String PREFERENCES_NAME = "channel-whitelist";
public static boolean isEnabled;
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());
isEnabled = SharedPrefUtils.getBoolean(YouTubeTikTokRoot_Application.getAppContext(), "youtube", "vanced_videoadwhitelisting_enabled", false);
}
// 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 (!isEnabled) return;
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 (!isEnabled) return false;
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 {
// Check that the channel doesn't exist already (can happen if for example the channel changes the name)
// If it exists, remove it
Iterator<ChannelModel> iterator = whiteList.iterator();
while(iterator.hasNext())
{
ChannelModel value = iterator.next();
if (value.getChannelId().equals(channelId))
{
if (XGlobals.debug) {
Log.d(TAG, String.format("Tried whitelisting an existing channel again. Old info (%1$s | %2$s) - New info (%3$s | %4$s)",
value.getAuthor(), value.getChannelId(), channelName, channelId));
}
iterator.remove();
break;
}
}
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<>();
}
}