Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(YouTube Music): Add Announcements patch #4066

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions extensions/music/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(libs.annotation)
}
1 change: 1 addition & 0 deletions extensions/music/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package app.revanced.extension.music.announcements;

import android.app.Activity;
import android.os.Build;
import androidx.annotation.RequiresApi;
import app.revanced.extension.shared.announcements.BaseAnnouncementsPatch;

@SuppressWarnings("unused")
public class AnnouncementsPatch extends BaseAnnouncementsPatch {
private static final AnnouncementsPatch INSTANCE = new AnnouncementsPatch();

private AnnouncementsPatch() {
super("music");
}

@RequiresApi(api = Build.VERSION_CODES.O)
public static void showAnnouncement(final Activity context) {
INSTANCE._showAnnouncement(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package app.revanced.extension.shared.announcements;

import static android.text.Html.FROM_HTML_MODE_COMPACT;
import static app.revanced.extension.shared.StringRef.str;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Build;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;

import androidx.annotation.RequiresApi;

import org.json.JSONArray;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.time.LocalDateTime;

import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.announcements.requests.AnnouncementsRoutes;
import app.revanced.extension.shared.settings.BaseSettings;

public abstract class BaseAnnouncementsPatch {
private final AnnouncementsRoutes announcementsRoutes;

public BaseAnnouncementsPatch(String tag) {
this.announcementsRoutes = new AnnouncementsRoutes(tag);
}

@RequiresApi(api = Build.VERSION_CODES.O)
private boolean isLatestAlready() throws IOException {
HttpURLConnection connection =
announcementsRoutes.getAnnouncementsConnectionFromRoute(announcementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS);

Logger.printDebug(() -> "Get latest announcement IDs route connection url: " + connection.getURL());

try {
// Do not show the announcement if the request failed.
if (connection.getResponseCode() != 200) {
if (BaseSettings.ANNOUNCEMENT_LAST_ID.isSetToDefault())
return true;

BaseSettings.ANNOUNCEMENT_LAST_ID.resetToDefault();
Utils.showToastLong(str("revanced_announcements_connection_failed"));

return true;
}
} catch (IOException ex) {
Logger.printException(() -> "Could not connect to announcements provider", ex);
return true;
}

var jsonString = Requester.parseStringAndDisconnect(connection);

// Parse the ID. Fall-back to raw string if it fails.
int id = BaseSettings.ANNOUNCEMENT_LAST_ID.defaultValue;
try {
final var announcementIds = new JSONArray(jsonString);
id = announcementIds.getJSONObject(0).getInt("id");

} catch (Throwable ex) {
Logger.printException(() -> "Failed to parse announcement IDs", ex);
}

// Do not show the announcement, if the last announcement id is the same as the current one.
return BaseSettings.ANNOUNCEMENT_LAST_ID.get() == id;
}

@RequiresApi(api = Build.VERSION_CODES.O)
public void _showAnnouncement(final Activity context) {
if (!BaseSettings.ANNOUNCEMENTS.get()) return;

// Check if there is internet connection
if (!Utils.isNetworkConnected()) return;

Utils.runOnBackgroundThread(() -> {
try {
if (isLatestAlready()) return;

HttpURLConnection connection = announcementsRoutes
.getAnnouncementsConnectionFromRoute(announcementsRoutes.GET_LATEST_ANNOUNCEMENTS);

Logger.printDebug(() -> "Get latest announcements route connection url: " + connection.getURL());

var jsonString = Requester.parseStringAndDisconnect(connection);

// Parse the announcement. Fall-back to raw string if it fails.
int id = BaseSettings.ANNOUNCEMENT_LAST_ID.defaultValue;
String title;
String message;
LocalDateTime archivedAt = LocalDateTime.MAX;
Level level = Level.INFO;
try {
final var announcement = new JSONArray(jsonString).getJSONObject(0);

id = announcement.getInt("id");
title = announcement.getString("title");
message = announcement.getString("content");
if (!announcement.isNull("archived_at")) {
archivedAt = LocalDateTime.parse(announcement.getString("archived_at"));
}
if (!announcement.isNull("level")) {
level = Level.fromInt(announcement.getInt("level"));
}
} catch (Throwable ex) {
Logger.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex);

title = "Announcement";
message = jsonString;
}

// If the announcement is archived, do not show it.
if (archivedAt.isBefore(LocalDateTime.now())) {
BaseSettings.ANNOUNCEMENT_LAST_ID.save(id);
return;
}

int finalId = id;
final var finalTitle = title;
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
final Level finalLevel = level;

Utils.runOnMainThread(() -> {
// Show the announcement.
var alert = new AlertDialog.Builder(context)
.setTitle(finalTitle)
.setMessage(finalMessage)
.setIcon(finalLevel.icon)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
BaseSettings.ANNOUNCEMENT_LAST_ID.save(finalId);
dialog.dismiss();
}).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> {
dialog.dismiss();
})
.setCancelable(false)
.create();

Utils.showDialog(context, alert, false, (AlertDialog dialog) -> {
// Make links clickable.
((TextView) dialog.findViewById(android.R.id.message))
.setMovementMethod(LinkMovementMethod.getInstance());
});
});
} catch (Exception e) {
final var message = "Failed to get announcement";

Logger.printException(() -> message, e);
}
});
}

// 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)];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package app.revanced.extension.shared.announcements.requests;

import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;

import java.io.IOException;
import java.net.HttpURLConnection;

import static app.revanced.extension.shared.requests.Route.Method.GET;

public class AnnouncementsRoutes {
public final Route GET_LATEST_ANNOUNCEMENTS;
public final Route GET_LATEST_ANNOUNCEMENT_IDS;

public AnnouncementsRoutes(String tag) {
this.GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=" + tag);
this.GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/ids?tag=" + tag);
}

public HttpURLConnection getAnnouncementsConnectionFromRoute(Route route, String... params) throws IOException {
return Requester.getConnectionFromRoute("https://api.revanced.app/v4", route, params);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ public class BaseSettings {
public static final BooleanSetting DEBUG_TOAST_ON_ERROR = new BooleanSetting("revanced_debug_toast_on_error", TRUE, "revanced_debug_toast_on_error_user_dialog_message");

public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);

public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
}
Loading