mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-05-01 15:14:27 +02:00
feat(YouTube Music): Add Announcements
patch
This commit is contained in:
parent
6a44e75203
commit
470feb6967
@ -1,9 +1,9 @@
|
|||||||
package app.revanced.extension.youtube.patches.announcements;
|
package app.revanced.extension.shared.announcements;
|
||||||
|
|
||||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
|
import static app.revanced.extension.shared.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
|
||||||
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
|
import static app.revanced.extension.shared.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
@ -23,8 +23,8 @@ import java.time.LocalDateTime;
|
|||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.requests.Requester;
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
import app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes;
|
import app.revanced.extension.shared.announcements.requests.AnnouncementsRoutes;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class AnnouncementsPatch {
|
public final class AnnouncementsPatch {
|
||||||
@ -41,10 +41,10 @@ public final class AnnouncementsPatch {
|
|||||||
try {
|
try {
|
||||||
// Do not show the announcement if the request failed.
|
// Do not show the announcement if the request failed.
|
||||||
if (connection.getResponseCode() != 200) {
|
if (connection.getResponseCode() != 200) {
|
||||||
if (Settings.ANNOUNCEMENT_LAST_ID.isSetToDefault())
|
if (BaseSettings.ANNOUNCEMENT_LAST_ID.isSetToDefault())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
Settings.ANNOUNCEMENT_LAST_ID.resetToDefault();
|
BaseSettings.ANNOUNCEMENT_LAST_ID.resetToDefault();
|
||||||
Utils.showToastLong(str("revanced_announcements_connection_failed"));
|
Utils.showToastLong(str("revanced_announcements_connection_failed"));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -57,7 +57,7 @@ public final class AnnouncementsPatch {
|
|||||||
var jsonString = Requester.parseStringAndDisconnect(connection);
|
var jsonString = Requester.parseStringAndDisconnect(connection);
|
||||||
|
|
||||||
// Parse the ID. Fall-back to raw string if it fails.
|
// Parse the ID. Fall-back to raw string if it fails.
|
||||||
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
int id = BaseSettings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||||
try {
|
try {
|
||||||
final var announcementIds = new JSONArray(jsonString);
|
final var announcementIds = new JSONArray(jsonString);
|
||||||
id = announcementIds.getJSONObject(0).getInt("id");
|
id = announcementIds.getJSONObject(0).getInt("id");
|
||||||
@ -67,12 +67,12 @@ public final class AnnouncementsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do not show the announcement, if the last announcement id is the same as the current one.
|
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||||
return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
|
return BaseSettings.ANNOUNCEMENT_LAST_ID.get() == id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public static void showAnnouncement(final Activity context) {
|
public static void showAnnouncement(final Activity context) {
|
||||||
if (!Settings.ANNOUNCEMENTS.get()) return;
|
if (!BaseSettings.ANNOUNCEMENTS.get()) return;
|
||||||
|
|
||||||
// Check if there is internet connection
|
// Check if there is internet connection
|
||||||
if (!Utils.isNetworkConnected()) return;
|
if (!Utils.isNetworkConnected()) return;
|
||||||
@ -89,7 +89,7 @@ public final class AnnouncementsPatch {
|
|||||||
var jsonString = Requester.parseStringAndDisconnect(connection);
|
var jsonString = Requester.parseStringAndDisconnect(connection);
|
||||||
|
|
||||||
// Parse the announcement. Fall-back to raw string if it fails.
|
// Parse the announcement. Fall-back to raw string if it fails.
|
||||||
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
int id = BaseSettings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||||
String title;
|
String title;
|
||||||
String message;
|
String message;
|
||||||
LocalDateTime archivedAt = LocalDateTime.MAX;
|
LocalDateTime archivedAt = LocalDateTime.MAX;
|
||||||
@ -115,7 +115,7 @@ public final class AnnouncementsPatch {
|
|||||||
|
|
||||||
// If the announcement is archived, do not show it.
|
// If the announcement is archived, do not show it.
|
||||||
if (archivedAt.isBefore(LocalDateTime.now())) {
|
if (archivedAt.isBefore(LocalDateTime.now())) {
|
||||||
Settings.ANNOUNCEMENT_LAST_ID.save(id);
|
BaseSettings.ANNOUNCEMENT_LAST_ID.save(id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ public final class AnnouncementsPatch {
|
|||||||
.setMessage(finalMessage)
|
.setMessage(finalMessage)
|
||||||
.setIcon(finalLevel.icon)
|
.setIcon(finalLevel.icon)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
Settings.ANNOUNCEMENT_LAST_ID.save(finalId);
|
BaseSettings.ANNOUNCEMENT_LAST_ID.save(finalId);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> {
|
}).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.extension.youtube.patches.announcements.requests;
|
package app.revanced.extension.shared.announcements.requests;
|
||||||
|
|
||||||
import app.revanced.extension.shared.requests.Requester;
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
import app.revanced.extension.shared.requests.Route;
|
import app.revanced.extension.shared.requests.Route;
|
||||||
@ -9,9 +9,9 @@ import java.net.HttpURLConnection;
|
|||||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||||
|
|
||||||
public class AnnouncementsRoutes {
|
public class AnnouncementsRoutes {
|
||||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
|
||||||
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=youtube");
|
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=youtube");
|
||||||
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=youtube");
|
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=youtube");
|
||||||
|
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
||||||
|
|
||||||
private AnnouncementsRoutes() {
|
private AnnouncementsRoutes() {
|
||||||
}
|
}
|
@ -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 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 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);
|
||||||
}
|
}
|
||||||
|
@ -271,12 +271,10 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
|
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
|
||||||
"revanced_spoof_device_dimensions_user_dialog_message");
|
"revanced_spoof_device_dimensions_user_dialog_message");
|
||||||
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
||||||
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true,"revanced_spoof_video_streams_user_dialog_message");
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true,"revanced_spoof_video_streams_user_dialog_message");
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
||||||
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofVideoStreamsPatch.ForceiOSAVCAvailability());
|
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofVideoStreamsPatch.ForceiOSAVCAvailability());
|
||||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
||||||
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
|
|
||||||
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
|
||||||
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
||||||
|
|
||||||
|
@ -2,6 +2,11 @@ public final class app/revanced/patches/all/misc/activity/exportall/ExportAllAct
|
|||||||
public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/all/misc/announcements/AnnouncementsPatchKt {
|
||||||
|
public static final fun announcementsPatch (Lapp/revanced/patcher/Fingerprint;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
public static synthetic fun announcementsPatch$default (Lapp/revanced/patcher/Fingerprint;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt {
|
public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt {
|
||||||
public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@ -304,6 +309,10 @@ public final class app/revanced/patches/music/misc/androidauto/BypassCertificate
|
|||||||
public static final fun getBypassCertificateChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getBypassCertificateChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/misc/announcements/AnnouncementsPatchKt {
|
||||||
|
public static final fun getAnnouncementsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt {
|
public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt {
|
||||||
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package app.revanced.patches.all.misc.announcements
|
||||||
|
|
||||||
|
import app.revanced.patcher.Fingerprint
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"Lapp/revanced/extension/shared/announcements/AnnouncementsPatch;"
|
||||||
|
|
||||||
|
fun announcementsPatch(
|
||||||
|
mainActivityOnCreateFingerprint: Fingerprint,
|
||||||
|
block: BytecodePatchBuilder.() -> Unit = {},
|
||||||
|
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||||
|
) = bytecodePatch(
|
||||||
|
name = "Announcements",
|
||||||
|
description = "Adds an option to show announcements from ReVanced on app startup.",
|
||||||
|
) {
|
||||||
|
block()
|
||||||
|
|
||||||
|
dependsOn(addResourcesPatch)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
addResources("shared", "misc.announcements.announcementsPatch")
|
||||||
|
|
||||||
|
mainActivityOnCreateFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->showAnnouncement(Landroid/app/Activity;)V",
|
||||||
|
)
|
||||||
|
|
||||||
|
executeBlock()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.patches.music.misc.announcements
|
||||||
|
|
||||||
|
import app.revanced.patches.all.misc.announcements.announcementsPatch
|
||||||
|
import app.revanced.patches.music.shared.musicActivityOnCreateFingerprint
|
||||||
|
|
||||||
|
val announcementsPatch = announcementsPatch(
|
||||||
|
musicActivityOnCreateFingerprint,
|
||||||
|
{
|
||||||
|
compatibleWith("com.google.android.apps.music")
|
||||||
|
},
|
||||||
|
)
|
@ -4,6 +4,7 @@ import app.revanced.patcher.patch.Option
|
|||||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||||
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||||
|
import app.revanced.patches.music.shared.musicActivityOnCreateFingerprint
|
||||||
import app.revanced.patches.shared.castContextFetchFingerprint
|
import app.revanced.patches.shared.castContextFetchFingerprint
|
||||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||||
import app.revanced.patches.shared.primeMethodFingerprint
|
import app.revanced.patches.shared.primeMethodFingerprint
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.patches.music.misc.gms
|
package app.revanced.patches.music.shared
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
@ -1,51 +1,24 @@
|
|||||||
package app.revanced.patches.youtube.misc.announcements
|
package app.revanced.patches.youtube.misc.announcements
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
import app.revanced.patches.all.misc.announcements.announcementsPatch
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
val announcementsPatch = announcementsPatch(
|
||||||
"Lapp/revanced/extension/youtube/patches/announcements/AnnouncementsPatch;"
|
mainActivityOnCreateFingerprint,
|
||||||
|
{
|
||||||
|
dependsOn(settingsPatch)
|
||||||
|
|
||||||
val announcementsPatch = bytecodePatch(
|
compatibleWith("com.google.android.youtube")
|
||||||
name = "Announcements",
|
},
|
||||||
description = "Adds an option to show announcements from ReVanced on app startup.",
|
{
|
||||||
) {
|
|
||||||
dependsOn(
|
|
||||||
settingsPatch,
|
|
||||||
addResourcesPatch,
|
|
||||||
)
|
|
||||||
|
|
||||||
compatibleWith(
|
|
||||||
"com.google.android.youtube"(
|
|
||||||
"18.38.44",
|
|
||||||
"18.49.37",
|
|
||||||
"19.16.39",
|
|
||||||
"19.25.37",
|
|
||||||
"19.34.42",
|
|
||||||
"19.43.41",
|
|
||||||
"19.45.38",
|
|
||||||
"19.46.42",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
execute {
|
|
||||||
addResources("youtube", "misc.announcements.announcementsPatch")
|
addResources("youtube", "misc.announcements.announcementsPatch")
|
||||||
|
|
||||||
PreferenceScreen.MISC.addPreferences(
|
PreferenceScreen.MISC.addPreferences(
|
||||||
SwitchPreference("revanced_announcements"),
|
SwitchPreference("revanced_announcements"),
|
||||||
)
|
)
|
||||||
|
},
|
||||||
mainActivityOnCreateFingerprint.method.addInstructions(
|
)
|
||||||
// Insert index must be greater than the insert index used by GmsCoreSupport,
|
|
||||||
// as both patch the same method and GmsCore check should be first.
|
|
||||||
1,
|
|
||||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->showAnnouncement(Landroid/app/Activity;)V",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user