mirror of
https://github.com/revanced/revanced-integrations
synced 2024-11-24 20:07:14 +01:00
feat(YouTube): Add Announcements
patch (#503)
This commit is contained in:
parent
cd6ba256a5
commit
59687f1a39
@ -0,0 +1,151 @@
|
||||
package app.revanced.integrations.patches.announcements;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import app.revanced.integrations.patches.announcements.requests.AnnouncementsRoutes;
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.UUID;
|
||||
|
||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||
import static app.revanced.integrations.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT;
|
||||
|
||||
public final class AnnouncementsPatch {
|
||||
private final static String CONSUMER = getOrSetConsumer();
|
||||
|
||||
private AnnouncementsPatch() {
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void showAnnouncement(final Activity context) {
|
||||
if (!SettingsEnum.ANNOUNCEMENTS.getBoolean()) return;
|
||||
|
||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT, CONSUMER);
|
||||
|
||||
LogHelper.printDebug(() -> "Get latest announcement route connection url: " + connection.getURL().toString());
|
||||
|
||||
try {
|
||||
// Do not show the announcement if the request failed.
|
||||
if (connection.getResponseCode() != 200) {
|
||||
if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return;
|
||||
|
||||
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue("");
|
||||
ReVancedUtils.showToastLong("Failed to get announcement");
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
final var message = "Failed connecting to announcements provider";
|
||||
|
||||
LogHelper.printException(() -> message, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonString = Requester.parseInputStreamAndClose(connection.getInputStream(), false);
|
||||
|
||||
// Do not show the announcement if it is older or the same as the last one.
|
||||
final byte[] hashBytes = MessageDigest.getInstance("SHA-256").digest(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
final var hash = java.util.Base64.getEncoder().encodeToString(hashBytes);
|
||||
if (hash.equals(SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString())) return;
|
||||
|
||||
// Parse the announcement. Fall-back to raw string if it fails.
|
||||
String title;
|
||||
String message;
|
||||
Level level = Level.INFO;
|
||||
try {
|
||||
final var announcement = new JSONObject(jsonString);
|
||||
|
||||
title = announcement.getString("title");
|
||||
message = announcement.getJSONObject("content").getString("message");
|
||||
|
||||
if (!announcement.isNull("level")) level = Level.fromInt(announcement.getInt("level"));
|
||||
} catch (Throwable ex) {
|
||||
LogHelper.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex);
|
||||
|
||||
title = "Announcement";
|
||||
message = jsonString;
|
||||
}
|
||||
|
||||
final var finalTitle = title;
|
||||
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
|
||||
final Level finalLevel = level;
|
||||
|
||||
ReVancedUtils.runOnMainThread(() -> {
|
||||
// Show the announcement.
|
||||
var alertDialog = new android.app.AlertDialog.Builder(context)
|
||||
.setTitle(finalTitle)
|
||||
.setMessage(finalMessage)
|
||||
.setIcon(finalLevel.icon)
|
||||
.setPositiveButton("Ok", (dialog, which) -> {
|
||||
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue(hash);
|
||||
dialog.dismiss();
|
||||
}).setNegativeButton("Dismiss", (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
|
||||
// Make links clickable.
|
||||
((TextView)alertDialog.findViewById(android.R.id.message))
|
||||
.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
final var message = "Failed to get announcement";
|
||||
|
||||
LogHelper.printException(() -> message, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the last announcement hash if it is not empty.
|
||||
*
|
||||
* @return true if the last announcement hash was empty.
|
||||
*/
|
||||
private static boolean emptyLastAnnouncementHash() {
|
||||
if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return true;
|
||||
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue("");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getOrSetConsumer() {
|
||||
final var consumer = SettingsEnum.ANNOUNCEMENT_CONSUMER.getString();
|
||||
if (!consumer.isEmpty()) return consumer;
|
||||
|
||||
final var uuid = UUID.randomUUID().toString();
|
||||
SettingsEnum.ANNOUNCEMENT_CONSUMER.saveValue(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
// TODO: Use better icons.
|
||||
private enum Level {
|
||||
INFO(android.R.drawable.ic_dialog_info),
|
||||
WARNING(android.R.drawable.ic_dialog_alert),
|
||||
SEVERE(android.R.drawable.ic_dialog_alert);
|
||||
|
||||
public final int icon;
|
||||
|
||||
Level(int icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public static Level fromInt(int value) {
|
||||
return values()[Math.min(value, values().length - 1)];
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.integrations.patches.announcements.requests;
|
||||
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.requests.Route;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import static app.revanced.integrations.requests.Route.Method.GET;
|
||||
|
||||
public class AnnouncementsRoutes {
|
||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v2";
|
||||
|
||||
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?consumer={consumer}");
|
||||
|
||||
private AnnouncementsRoutes() {
|
||||
}
|
||||
|
||||
public static HttpURLConnection getAnnouncementsConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(ANNOUNCEMENTS_PROVIDER, route, params);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package app.revanced.integrations.patches.spoof.requests;
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.requests.Route;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@ -75,7 +76,12 @@ final class PlayerRoutes {
|
||||
/** @noinspection SameParameterValue*/
|
||||
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route) throws IOException {
|
||||
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);
|
||||
connection.setRequestProperty("User-Agent", "com.google.android.youtube/18.37.36 (Linux; U; Android 12; GB) gzip");
|
||||
|
||||
connection.setRequestProperty(
|
||||
"User-Agent", "com.google.android.youtube/" +
|
||||
ReVancedUtils.getVersionName() +
|
||||
" (Linux; U; Android 12; GB) gzip"
|
||||
);
|
||||
connection.setRequestProperty("X-Goog-Api-Format-Version", "2");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package app.revanced.integrations.requests;
|
||||
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -23,7 +24,7 @@ public class Requester {
|
||||
String url = apiUrl + route.getCompiledRoute();
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setRequestMethod(route.getMethod().name());
|
||||
connection.setRequestProperty("User-agent", System.getProperty("http.agent") + ";revanced");
|
||||
connection.setRequestProperty("User-Agent", System.getProperty("http.agent") + "; ReVanced/" + ReVancedUtils.getVersionName());
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
@ -10,10 +10,7 @@ import app.revanced.integrations.utils.StringRef;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.*;
|
||||
import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE;
|
||||
@ -179,6 +176,9 @@ public enum SettingsEnum {
|
||||
parents(SPOOF_SIGNATURE)),
|
||||
SPOOF_DEVICE_DIMENSIONS("revanced_spoof_device_dimensions", BOOLEAN, FALSE, true),
|
||||
BYPASS_URL_REDIRECTS("revanced_bypass_url_redirects", BOOLEAN, TRUE),
|
||||
ANNOUNCEMENTS("revanced_announcements", BOOLEAN, TRUE),
|
||||
ANNOUNCEMENT_CONSUMER("revanced_announcement_consumer", STRING, ""),
|
||||
ANNOUNCEMENT_LAST_HASH("revanced_announcement_last_hash", STRING, ""),
|
||||
|
||||
// Swipe controls
|
||||
SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE),
|
||||
@ -555,6 +555,7 @@ public enum SettingsEnum {
|
||||
private boolean includeWithImportExport() {
|
||||
switch (this) {
|
||||
case RYD_USER_ID: // Not useful to export, no reason to include it.
|
||||
case ANNOUNCEMENT_CONSUMER: // Not useful to export, no reason to include it.
|
||||
case SB_LAST_VIP_CHECK:
|
||||
case SB_HIDE_EXPORT_WARNING:
|
||||
case SB_SEEN_GUIDELINES:
|
||||
|
@ -2,8 +2,11 @@ package app.revanced.integrations.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
@ -25,9 +28,36 @@ public class ReVancedUtils {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public static Context context;
|
||||
|
||||
private static String versionName;
|
||||
|
||||
private ReVancedUtils() {
|
||||
} // utility class
|
||||
|
||||
public static String getVersionName() {
|
||||
if (versionName != null) return versionName;
|
||||
|
||||
PackageInfo packageInfo;
|
||||
try {
|
||||
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
packageInfo = context.getPackageManager().getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.PackageInfoFlags.of(0)
|
||||
);
|
||||
else
|
||||
packageInfo = context.getPackageManager().getPackageInfo(
|
||||
packageName,
|
||||
0
|
||||
);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogHelper.printException(() -> "Failed to get package info", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return versionName = packageInfo.versionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a view by setting its layout height and width to 1dp.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user