diff --git a/build-logic/src/main/kotlin/com/aliucord/gradle/repo/Authors.kt b/build-logic/src/main/kotlin/com/aliucord/gradle/repo/Authors.kt index ddfbabd..7b60936 100644 --- a/build-logic/src/main/kotlin/com/aliucord/gradle/repo/Authors.kt +++ b/build-logic/src/main/kotlin/com/aliucord/gradle/repo/Authors.kt @@ -53,6 +53,7 @@ object Authors { val Accelerator = Author("Accelerator", 150234173024501762L) val Derlan = Author("Derlan", 821545900807028757L) val Link = Author("Link", 725923756555501658L) + val Wing = Author("Wing", 298295889720770563L) val ArjixWasTaken = Author("ArjixWasTaken", 674710789138939916L) } diff --git a/plugins/FuckOffDiscord/build.gradle.kts b/plugins/FuckOffDiscord/build.gradle.kts new file mode 100644 index 0000000..d14b0f3 --- /dev/null +++ b/plugins/FuckOffDiscord/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.1" +description = "Fuck off Discord" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/FuckOffDiscord/src/main/java/com/aliucord/plugins/FuckOffDiscord.kt b/plugins/FuckOffDiscord/src/main/java/com/aliucord/plugins/FuckOffDiscord.kt new file mode 100644 index 0000000..5d27dac --- /dev/null +++ b/plugins/FuckOffDiscord/src/main/java/com/aliucord/plugins/FuckOffDiscord.kt @@ -0,0 +1,42 @@ +package com.aliucord.plugins + +import android.content.Context +import android.view.View +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import kotlin.Throws +import com.discord.widgets.user.Badge +import com.aliucord.patcher.after +import com.aliucord.patcher.instead +import com.aliucord.plugins.ReflectionExtensions.binding +import com.discord.models.guild.Guild +import com.discord.widgets.emoji.WidgetEmojiSheet +import com.discord.widgets.stickers.WidgetGuildStickerSheet +import com.google.android.material.button.MaterialButton + +@AliucordPlugin +class FuckOffDiscord : Plugin() { + @Throws(NoSuchMethodException::class) + override fun start(context: Context) { + ReflectionExtensions.init(); + + patcher.instead("getShowPremiumUpSell") { false } + + patcher.after("configureButtons", Boolean::class.java, Boolean::class.java, Guild::class.java) { + binding.root.findViewById(Utils.getResId("premium_btn", "id")).apply { + visibility = View.GONE + } + } + + patcher.after("configureButtons", Boolean::class.java, Boolean::class.java, Guild::class.java) { + binding.root.findViewById(Utils.getResId("guild_sticker_sheet_premium_btn", "id")).apply { + visibility = View.GONE + } + } + } + + override fun stop(context: Context) { + patcher.unpatchAll() + } +} \ No newline at end of file diff --git a/plugins/FuckOffDiscord/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt b/plugins/FuckOffDiscord/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt new file mode 100644 index 0000000..90e2dca --- /dev/null +++ b/plugins/FuckOffDiscord/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt @@ -0,0 +1,24 @@ +package com.aliucord.plugins + +import com.discord.databinding.WidgetEmojiSheetBinding +import com.discord.databinding.WidgetGuildStickerSheetBinding +import com.discord.widgets.emoji.WidgetEmojiSheet +import com.discord.widgets.stickers.WidgetGuildStickerSheet +import java.lang.reflect.Field +import java.lang.reflect.Method + +object ReflectionExtensions { + private lateinit var guildStickerBinding: Method + private lateinit var emojiBinding: Method + + fun init() { + guildStickerBinding = WidgetGuildStickerSheet::class.java.getDeclaredMethod("getBinding").apply { isAccessible = true } + emojiBinding = WidgetEmojiSheet::class.java.getDeclaredMethod("getBinding").apply { isAccessible = true } + } + + val WidgetGuildStickerSheet.binding: WidgetGuildStickerSheetBinding + get() = guildStickerBinding.invoke(this) as WidgetGuildStickerSheetBinding + + val WidgetEmojiSheet.binding: WidgetEmojiSheetBinding + get() = emojiBinding.invoke(this) as WidgetEmojiSheetBinding +} \ No newline at end of file diff --git a/plugins/FullBio/build.gradle.kts b/plugins/FullBio/build.gradle.kts new file mode 100644 index 0000000..cfc0a2a --- /dev/null +++ b/plugins/FullBio/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.2" +description = "Shows Full bio in user profiles" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/FullBio/src/main/java/com/aliucord/plugins/FullBio.kt b/plugins/FullBio/src/main/java/com/aliucord/plugins/FullBio.kt new file mode 100644 index 0000000..520ad5e --- /dev/null +++ b/plugins/FullBio/src/main/java/com/aliucord/plugins/FullBio.kt @@ -0,0 +1,45 @@ +package com.aliucord.plugins + +import android.content.Context +import android.widget.TextView +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.Hook +import com.aliucord.plugins.ReflectionExtensions.binding +import com.discord.utilities.view.text.LinkifiedTextView +import com.discord.widgets.settings.profile.SettingsUserProfileViewModel +import com.discord.widgets.settings.profile.WidgetEditUserOrGuildMemberProfile +import com.discord.widgets.user.usersheet.WidgetUserSheet +import com.discord.widgets.user.usersheet.WidgetUserSheetViewModel + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@AliucordPlugin +class FullBio : Plugin() { + @Throws(NoSuchMethodException::class) // Called when your plugin is started. This is the place to register command, add patches, etc + override fun start(context: Context) { + ReflectionExtensions.init() + + patcher.patch(WidgetEditUserOrGuildMemberProfile::class.java.getDeclaredMethod("configureBio", SettingsUserProfileViewModel.ViewState.Loaded::class.java), Hook { + val binding = (it.thisObject as WidgetEditUserOrGuildMemberProfile).binding + setMax(binding.root.findViewById(Utils.getResId("bio_preview_text", "id"))) + setMax(binding.root.findViewById(Utils.getResId("bio_editor_text_input_field", "id"))) + }) + + patcher.patch(WidgetUserSheet::class.java.getDeclaredMethod("configureAboutMe", WidgetUserSheetViewModel.ViewState.Loaded::class.java), Hook { + val binding = (it.thisObject as WidgetUserSheet).binding + setMax(binding.root.findViewById(Utils.getResId("about_me_text", "id"))) + }) + } + + private fun setMax(text: TextView?) { + text?.apply { + maxLines = Integer.MAX_VALUE + } + } + + // Called when your plugin is stopped + override fun stop(context: Context) { + patcher.unpatchAll() + } +} \ No newline at end of file diff --git a/plugins/FullBio/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt b/plugins/FullBio/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt new file mode 100644 index 0000000..534b8ac --- /dev/null +++ b/plugins/FullBio/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt @@ -0,0 +1,23 @@ +package com.aliucord.plugins + +import com.discord.databinding.WidgetSettingsUserProfileBinding +import com.discord.databinding.WidgetUserSheetBinding +import com.discord.widgets.settings.profile.WidgetEditUserOrGuildMemberProfile +import com.discord.widgets.user.usersheet.WidgetUserSheet +import java.lang.reflect.Method + +object ReflectionExtensions { + private lateinit var settingsBioBinding: Method + private lateinit var userBioBinding: Method + + fun init() { + settingsBioBinding = WidgetEditUserOrGuildMemberProfile::class.java.getDeclaredMethod("getBinding").apply { isAccessible = true } + userBioBinding = WidgetUserSheet::class.java.getDeclaredMethod("getBinding").apply { isAccessible = true } + } + + val WidgetEditUserOrGuildMemberProfile.binding: WidgetSettingsUserProfileBinding + get() = settingsBioBinding.invoke(this) as WidgetSettingsUserProfileBinding + + val WidgetUserSheet.binding: WidgetUserSheetBinding + get() = userBioBinding.invoke(this) as WidgetUserSheetBinding +} \ No newline at end of file diff --git a/plugins/MarqueeEverywhere/build.gradle.kts b/plugins/MarqueeEverywhere/build.gradle.kts new file mode 100644 index 0000000..5def060 --- /dev/null +++ b/plugins/MarqueeEverywhere/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.1.2" +description = "Applies marquees to every channel name" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/MarqueeEverywhere/src/main/java/com/aliucord/plugins/MarqueeEverywhere.kt b/plugins/MarqueeEverywhere/src/main/java/com/aliucord/plugins/MarqueeEverywhere.kt new file mode 100644 index 0000000..bf806ed --- /dev/null +++ b/plugins/MarqueeEverywhere/src/main/java/com/aliucord/plugins/MarqueeEverywhere.kt @@ -0,0 +1,117 @@ +package com.aliucord.plugins + +import android.content.Context +import android.os.Bundle +import android.text.TextUtils +import android.view.View +import android.widget.TextView +import com.aliucord.PluginManager +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.SettingsAPI +import com.aliucord.entities.Plugin +import com.aliucord.patcher.after +import com.aliucord.plugins.ReflectionExtensions.binding +import com.aliucord.widgets.BottomSheet +import com.discord.views.CheckedSetting +import com.discord.views.UsernameView +import com.discord.widgets.channels.list.WidgetChannelsListAdapter +import com.discord.widgets.channels.list.items.ChannelListItem +import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListAdapter +import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListViewHolderHeader +import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListViewHolderMember + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@AliucordPlugin +class MarqueeEverywhere : Plugin() { + init { + settingsTab = SettingsTab(PluginSettings::class.java, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings) + } + + @Throws(NoSuchMethodException::class) // Called when your plugin is started. This is the place to register command, add patches, etc + override fun start(context: Context) { + ReflectionExtensions.init() + + if (settings.getBool("channelNames", true)) { + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channels_item_channel_name", "id"))) + } + + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channels_item_voice_channel_name", "id"))) + } + + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("stage_channel_item_voice_channel_name", "id"))) + } + + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channels_item_category_name", "id"))) + } + + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channels_list_item_private_name", "id"))) + } + + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channels_item_thread_name", "id"))) + } + } + + if (settings.getBool("memberNames", true)) { + patcher.after("bind", ChannelMembersListAdapter.Item.Header::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channel_members_list_item_header_text", "id"))) + } + + patcher.after("bind", ChannelMembersListAdapter.Item.Member::class.java, Function0::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channel_members_list_item_name", "id")).findViewById(Utils.getResId("username_text", "id"))) + } + } + + if (settings.getBool("userStatus", true)) { + patcher.after("bind", ChannelMembersListAdapter.Item.Member::class.java, Function0::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channel_members_list_item_game", "id"))) + } + + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { + setMarquee(binding.root.findViewById(Utils.getResId("channels_list_item_private_desc", "id"))) + } + } + } + + private fun setMarquee(text: TextView?) { + text?.apply { + ellipsize = TextUtils.TruncateAt.MARQUEE + marqueeRepeatLimit = -1 + isSelected = true + } + } + + // Called when your plugin is stopped + override fun stop(context: Context) { + patcher.unpatchAll() + } + + class PluginSettings(val settings: SettingsAPI) : BottomSheet() { + override fun onViewCreated(view: View, bundle: Bundle?) { + super.onViewCreated(view, bundle) + + context?.let { + addView(createCheckedSetting(it, "Channel names", "Channel names (including names of DMs) should scroll", "channelNames")) + addView(createCheckedSetting(it, "Member names", "Member names should scroll", "memberNames")) + addView(createCheckedSetting(it, "User status", "User statuses should scroll", "userStatus")) + } + } + + private fun createCheckedSetting(ctx: Context, title: String, subtext: String, setting: String, default: Boolean = true): CheckedSetting { + return Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, subtext).apply { + isChecked = settings.getBool(setting, default) + setOnCheckedListener { + settings.setBool(setting, it) + PluginManager.stopPlugin("MarqueeEverywhere") + PluginManager.startPlugin("MarqueeEverywhere") + } + } + } + } +} \ No newline at end of file diff --git a/plugins/MarqueeEverywhere/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt b/plugins/MarqueeEverywhere/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt new file mode 100644 index 0000000..c7a359b --- /dev/null +++ b/plugins/MarqueeEverywhere/src/main/java/com/aliucord/plugins/ReflectionExtensions.kt @@ -0,0 +1,53 @@ +package com.aliucord.plugins + +import com.discord.databinding.* +import com.discord.widgets.channels.list.WidgetChannelsListAdapter +import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListViewHolderHeader +import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListViewHolderMember +import java.lang.reflect.Field + +object ReflectionExtensions { + private lateinit var textChannelBinding: Field + private lateinit var privateChannelBinding: Field + private lateinit var categoryChannelBinding: Field + private lateinit var stageChannelBinding: Field + private lateinit var threadChannelBinding: Field + private lateinit var voiceChannelBinding: Field + private lateinit var roleHeaderBinding: Field + private lateinit var memberBinding: Field + + fun init() { + textChannelBinding = WidgetChannelsListAdapter.ItemChannelText::class.java.getDeclaredField("binding").apply { isAccessible = true } + privateChannelBinding = WidgetChannelsListAdapter.ItemChannelPrivate::class.java.getDeclaredField("binding").apply { isAccessible = true } + categoryChannelBinding = WidgetChannelsListAdapter.ItemChannelCategory::class.java.getDeclaredField("binding").apply { isAccessible = true } + stageChannelBinding = WidgetChannelsListAdapter.ItemChannelStageVoice::class.java.getDeclaredField("binding").apply { isAccessible = true } + threadChannelBinding = WidgetChannelsListAdapter.ItemChannelThread::class.java.getDeclaredField("binding").apply { isAccessible = true } + voiceChannelBinding = WidgetChannelsListAdapter.ItemChannelVoice::class.java.getDeclaredField("binding").apply { isAccessible = true } + roleHeaderBinding = ChannelMembersListViewHolderHeader::class.java.getDeclaredField("binding").apply { isAccessible = true } + memberBinding = ChannelMembersListViewHolderMember::class.java.getDeclaredField("binding").apply { isAccessible = true } + } + + val WidgetChannelsListAdapter.ItemChannelText.binding: WidgetChannelsListItemChannelBinding + get() = textChannelBinding[this] as WidgetChannelsListItemChannelBinding + + val WidgetChannelsListAdapter.ItemChannelStageVoice.binding: WidgetChannelsListItemChannelStageVoiceBinding + get() = stageChannelBinding[this] as WidgetChannelsListItemChannelStageVoiceBinding + + val WidgetChannelsListAdapter.ItemChannelPrivate.binding: WidgetChannelsListItemChannelPrivateBinding + get() = privateChannelBinding[this] as WidgetChannelsListItemChannelPrivateBinding + + val WidgetChannelsListAdapter.ItemChannelCategory.binding: WidgetChannelsListItemCategoryBinding + get() = categoryChannelBinding[this] as WidgetChannelsListItemCategoryBinding + + val WidgetChannelsListAdapter.ItemChannelThread.binding: WidgetChannelsListItemThreadBinding + get() = threadChannelBinding[this] as WidgetChannelsListItemThreadBinding + + val WidgetChannelsListAdapter.ItemChannelVoice.binding: WidgetChannelsListItemChannelVoiceBinding + get() = voiceChannelBinding[this] as WidgetChannelsListItemChannelVoiceBinding + + val ChannelMembersListViewHolderHeader.binding: WidgetChannelMembersListItemHeaderBinding + get() = roleHeaderBinding[this] as WidgetChannelMembersListItemHeaderBinding + + val ChannelMembersListViewHolderMember.binding: WidgetChannelMembersListItemUserBinding + get() = memberBinding[this] as WidgetChannelMembersListItemUserBinding +} \ No newline at end of file diff --git a/plugins/NoReactions/build.gradle.kts b/plugins/NoReactions/build.gradle.kts new file mode 100644 index 0000000..0af1dc1 --- /dev/null +++ b/plugins/NoReactions/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.1" +description = "No Reactions" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/NoReactions/src/main/java/com/aliucord/plugins/NoReactions.java b/plugins/NoReactions/src/main/java/com/aliucord/plugins/NoReactions.java new file mode 100644 index 0000000..5fbb0e4 --- /dev/null +++ b/plugins/NoReactions/src/main/java/com/aliucord/plugins/NoReactions.java @@ -0,0 +1,31 @@ +package com.aliucord.plugins; + +import java.util.Collection; + +import android.content.Context; + +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.InsteadHook; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class NoReactions extends Plugin { + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) { + var className = "com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemReactions"; + var methodName = "displayReactions"; + var methodArguments = new Class[] { Collection.class, long.class, boolean.class, boolean.class, boolean.class }; + + // add the patch + patcher.patch(className, methodName, methodArguments, new InsteadHook(callFrame -> null)); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + patcher.unpatchAll(); + } +} diff --git a/plugins/PersistSettings/build.gradle.kts b/plugins/PersistSettings/build.gradle.kts new file mode 100644 index 0000000..231ebc4 --- /dev/null +++ b/plugins/PersistSettings/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.1.3" +description = "PersistSettings" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/PersistSettings/src/main/java/com/aliucord/plugins/PersistSettings.java b/plugins/PersistSettings/src/main/java/com/aliucord/plugins/PersistSettings.java new file mode 100644 index 0000000..0fd8c28 --- /dev/null +++ b/plugins/PersistSettings/src/main/java/com/aliucord/plugins/PersistSettings.java @@ -0,0 +1,108 @@ +package com.aliucord.plugins; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.aliucord.Utils; +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.api.SettingsAPI; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.InsteadHook; +import com.aliucord.widgets.BottomSheet; +import com.discord.stores.StoreAuthentication; +import com.discord.stores.StoreEmoji; +import com.discord.stores.StoreNux; +import com.discord.stores.StoreStickers; +import com.discord.utilities.persister.Persister; +import com.discord.views.CheckedSetting; +import com.discord.models.authentication.AuthState; +import com.discord.stores.authentication.AuthStateCache; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import d0.z.d.m; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class PersistSettings extends Plugin { + public static class PluginSettings extends BottomSheet { + private final SettingsAPI settings; + + public PluginSettings(SettingsAPI settings) { this.settings = settings; } + + public void onViewCreated(View view, Bundle bundle) { + super.onViewCreated(view, bundle); + + addView(createCheckedSetting(view.getContext(), "Save settings", "persistSetting", true)); + } + + private CheckedSetting createCheckedSetting(Context ctx, String title, String setting, boolean checked) { + CheckedSetting checkedSetting = Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, null); + + checkedSetting.setChecked(settings.getBool(setting, checked)); + checkedSetting.setOnCheckedListener( check -> { + settings.setBool(setting, check); + }); + + return checkedSetting; + } + } + + public PersistSettings() { + settingsTab = new SettingsTab(PluginSettings.class, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings); + } + + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) throws NoSuchMethodException, NoSuchFieldException { + Field fAuthState = StoreAuthentication.class.getDeclaredField("authState"); + fAuthState.setAccessible(true); + + Field fAuthStateCache = StoreAuthentication.class.getDeclaredField("authStateCache"); + fAuthStateCache.setAccessible(true); + + Method mCacheAuthState = AuthStateCache.class.getDeclaredMethod("cacheAuthState", AuthState.class); + mCacheAuthState.setAccessible(true); + + patcher.patch(StoreAuthentication.class.getDeclaredMethod("handleAuthState$app_productionGoogleRelease", AuthState.class), new InsteadHook(callFrame -> { + var storeAuth = (StoreAuthentication) callFrame.thisObject; + var authState = (AuthState) callFrame.args[0]; + + try { + fAuthState.set(storeAuth, authState); + var authStateCache = (AuthStateCache) fAuthStateCache.get(storeAuth); + mCacheAuthState.invoke(authStateCache, authState); + } catch (Exception e) { + logger.error(e); + } + + if (authState == null && !settings.getBool("persistSetting", true)) { + Persister.Companion.reset(); + SharedPreferences.Editor editorEdit = storeAuth.getPrefs().edit(); + m.checkNotNullExpressionValue(editorEdit, "editor"); + editorEdit.clear(); + editorEdit.apply(); + } + + return null; + })); + + patcher.patch(StoreEmoji.class.getDeclaredMethod("handlePreLogout"), InsteadHook.DO_NOTHING); + + patcher.patch(StoreStickers.class.getDeclaredMethod("handlePreLogout"), InsteadHook.DO_NOTHING); + + patcher.patch(StoreNux.class.getDeclaredMethod("setFirstOpen", boolean.class), InsteadHook.DO_NOTHING); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + patcher.unpatchAll(); + } +} diff --git a/plugins/RainbowCord/build.gradle.kts b/plugins/RainbowCord/build.gradle.kts new file mode 100644 index 0000000..8c75fa4 --- /dev/null +++ b/plugins/RainbowCord/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.3" +description = "RainbowCord" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/RainbowCord/src/main/java/com/aliucord/plugins/RainbowCord.java b/plugins/RainbowCord/src/main/java/com/aliucord/plugins/RainbowCord.java new file mode 100644 index 0000000..60e491b --- /dev/null +++ b/plugins/RainbowCord/src/main/java/com/aliucord/plugins/RainbowCord.java @@ -0,0 +1,111 @@ +package com.aliucord.plugins; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.text.Editable; +import android.text.InputType; +import android.view.View; +import android.widget.TextView; + +import com.aliucord.PluginManager; +import com.aliucord.Utils; +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.api.SettingsAPI; +import com.aliucord.entities.Plugin; +import com.aliucord.fragments.SettingsPage; +import com.aliucord.patcher.Hook; +import com.aliucord.views.TextInput; +import com.discord.utilities.view.text.SimpleDraweeSpanTextView; +import com.discord.utilities.view.text.TextWatcher; +import com.discord.views.CheckedSetting; +import com.discord.widgets.chat.input.MessageDraftsRepo; +import com.discord.widgets.chat.input.WidgetChatInputEditText; +import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage; +import com.discord.widgets.chat.list.entries.MessageEntry; +import com.lytefast.flexinput.widget.FlexEditText; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class RainbowCord extends Plugin { + public static class PluginSettings extends SettingsPage { + private final SettingsAPI settings; + + public PluginSettings(SettingsAPI settings) { this.settings = settings; } + + public void onViewBound(View view) { + super.onViewBound(view); + setActionBarTitle("RainbowCord"); + + TextInput textInput = new TextInput(view.getContext()); + //textInput.setHint("Animation Cycle Time"); + textInput.getEditText().setText(String.valueOf(settings.getLong("colorSpeed", RainbowCord.DEFAULT_SPEED))); + textInput.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER); + textInput.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + try { + if (Long.valueOf(editable.toString()) != 0) + settings.setLong("colorSpeed", Long.valueOf(editable.toString())); + } catch (Exception e) { + settings.setLong("colorSpeed", RainbowCord.DEFAULT_SPEED); + } + } + }); + + addView(textInput); + addView(createCheckedSetting(view.getContext(), "Rainbow Typing Box", "rainbowTyping", true)); + addView(createCheckedSetting(view.getContext(), "Rainbow Messages (may be laggy)", "rainbowMessages", false)); + } + + private CheckedSetting createCheckedSetting(Context ctx, String title, String setting, boolean checked) { + CheckedSetting checkedSetting = Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, null); + + checkedSetting.setChecked(settings.getBool(setting, checked)); + checkedSetting.setOnCheckedListener( check -> { + settings.setBool(setting, check); + PluginManager.stopPlugin("RainbowCord"); + PluginManager.startPlugin("RainbowCord"); + }); + + return checkedSetting; + } + } + + private static final int[] animatedColors = {0xFFe5e5ea, 0xFFfea7b9, 0xFFcd9aec, 0xFFb5b8f8, 0xFF87beff, 0xFF97f2c3, 0xFFbbe061, 0xFFf9e560, 0xFFffb43f, 0xFFcfa075, 0xFFe5e5ea}; + private static final long DEFAULT_SPEED = 5000; + + public RainbowCord() { + settingsTab = new SettingsTab(PluginSettings.class).withArgs(settings); + } + + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) throws NoSuchMethodException { + if (settings.getBool("rainbowTyping", true)) { + patcher.patch(WidgetChatInputEditText.class.getDeclaredConstructor(FlexEditText.class, MessageDraftsRepo.class), new Hook(callFrame -> { + setValueAnimator((FlexEditText) callFrame.args[0]); + })); + } + + if (settings.getBool("rainbowMessages", false)) { + patcher.patch(WidgetChatListAdapterItemMessage.class.getDeclaredMethod("processMessageText", SimpleDraweeSpanTextView.class, MessageEntry.class), new Hook(callFrame -> { + setValueAnimator((SimpleDraweeSpanTextView) callFrame.args[0]); + })); + } + } + + private void setValueAnimator(TextView textView) { + ObjectAnimator colorAnim = ObjectAnimator.ofArgb(textView, "textColor", animatedColors); + colorAnim.setRepeatMode(ObjectAnimator.REVERSE); + colorAnim.setRepeatCount(ObjectAnimator.INFINITE); + colorAnim.setDuration(settings.getLong("colorSpeed", RainbowCord.DEFAULT_SPEED)); + colorAnim.start(); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + patcher.unpatchAll(); + } +} diff --git a/plugins/RoleContextMenu/build.gradle.kts b/plugins/RoleContextMenu/build.gradle.kts new file mode 100644 index 0000000..665ff0a --- /dev/null +++ b/plugins/RoleContextMenu/build.gradle.kts @@ -0,0 +1,14 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.2.1" +description = "RoleContextMenu" + +aliucord { + author(Authors.HalalKing) + author(Authors.Wing) +} \ No newline at end of file diff --git a/plugins/RoleContextMenu/src/main/java/com/aliucord/plugins/RoleContextMenu.java b/plugins/RoleContextMenu/src/main/java/com/aliucord/plugins/RoleContextMenu.java new file mode 100644 index 0000000..66254ba --- /dev/null +++ b/plugins/RoleContextMenu/src/main/java/com/aliucord/plugins/RoleContextMenu.java @@ -0,0 +1,179 @@ +package com.aliucord.plugins; + +import android.content.Context; +import android.os.Bundle; +import android.graphics.drawable.Drawable; +import android.text.*; +import android.text.method.LinkMovementMethod; +import android.text.style.*; +import android.view.View; +import android.view.Gravity; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.LinearLayout; +import android.graphics.Color; + +import androidx.fragment.app.FragmentManager; +import androidx.core.graphics.ColorUtils; +import androidx.core.content.res.ResourcesCompat; +import androidx.core.content.ContextCompat; + +import com.aliucord.Constants; +import com.aliucord.Logger; +import com.aliucord.Utils; +import com.aliucord.utils.DimenUtils; +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.*; +import com.aliucord.widgets.BottomSheet; +import com.aliucord.views.Button; +import com.aliucord.views.Divider; + +import com.discord.api.role.GuildRole; +import com.discord.widgets.roles.RolesListView$updateView$$inlined$forEach$lambda$1; +import com.discord.widgets.roles.RolesListView; +import com.discord.utilities.color.ColorCompat; +import com.discord.utilities.textprocessing.node.RoleMentionNode; +import com.discord.utilities.guilds.RoleUtils; + +import com.facebook.drawee.view.SimpleDraweeView; + +import com.lytefast.flexinput.R; + +import java.util.*; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class RoleContextMenu extends Plugin { + private static FragmentManager cachedFragment = Utils.appActivity.getSupportFragmentManager(); + private static final int p = DimenUtils.dpToPx(16); + public static class RoleBottomSheet extends BottomSheet { + public void onViewCreated(View view, Bundle bundle) { + super.onViewCreated(view, bundle); + setPadding(p); + Context ctx = view.getContext(); + var args = getArguments(); + var themedColor = ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal); + boolean hasColor = args.getString("roleColor", "000000") != "default"; + + LinearLayout infoView = new LinearLayout(ctx); + infoView.setOrientation(LinearLayout.HORIZONTAL); + infoView.setVerticalGravity(Gravity.CENTER_VERTICAL); + infoView.setPadding(0, 0, 0, p); + + SimpleDraweeView icon = new SimpleDraweeView(ctx); + icon.setLayoutParams(new LinearLayout.LayoutParams(DimenUtils.dpToPx(48), DimenUtils.dpToPx(48))); + if(args.getBoolean("hasIcon", false)) { icon.setImageURI(String.format("https://cdn.discordapp.com/role-icons/%s/%s.png", args.getString("roleId", "0"), args.getString("icon", ""))); } else { Drawable shield = ContextCompat.getDrawable(ctx, R.e.ic_shieldstar_24dp).mutate(); shield.setTint(hasColor ? Color.parseColor("#" + args.getString("roleColor", "000000")) : themedColor); icon.setImageDrawable(shield); } + infoView.addView(icon); + + LinearLayout details = new LinearLayout(ctx); + details.setOrientation(LinearLayout.VERTICAL); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMarginStart(p); + details.setLayoutParams(params); + + details.addView(addText(ctx, args.getString("roleName", ""), 16, true)); + if(args.getBoolean("isManaged", false)) details.addView(addText(ctx, "This role is managed by an integration", 12, false)); + if(args.getBoolean("isHoisted", false)) details.addView(addText(ctx, "This role is hoisted", 12, false)); + + infoView.addView(details); + + Button copyIdBtn = new Button(ctx); + copyIdBtn.setText("Copy ID"); + LinearLayout.LayoutParams copyIdParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + copyIdParams.setMargins(0, p, 0, 0); + copyIdBtn.setLayoutParams(copyIdParams); + copyIdBtn.setOnClickListener(text -> { + copyToClipboard(args.getString("roleId", "0")); + }); + + Button copyColorBtn = new Button(ctx); + copyColorBtn.setText("Copy Color"); + copyColorBtn.setOnClickListener(text -> { + copyToClipboard(args.getString("roleColor", "000000")); + }); + + getLinearLayout().addView(infoView); + getLinearLayout().addView(new Divider(ctx)); + getLinearLayout().addView(copyIdBtn); + if(hasColor) getLinearLayout().addView(copyColorBtn); + } + + private void copyToClipboard(String copy) { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("Copied to clipboard.", copy); + Toast.makeText(getContext(), "Copied to clipboard.", Toast.LENGTH_SHORT).show(); + clipboard.setPrimaryClip(clip); + } + + private TextView addText(Context ctx, String text, int size, boolean isBold) { + TextView name = new TextView(ctx); + name.setText(text); + name.setTypeface(ResourcesCompat.getFont(ctx, isBold ? Constants.Fonts.whitney_bold : Constants.Fonts.whitney_medium)); + name.setTextSize(size); + name.setTextColor(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)); + return name; + } + } + + private final Logger log = new Logger(); + + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) throws NoSuchMethodException { + + patcher.patch(RolesListView$updateView$$inlined$forEach$lambda$1.class.getDeclaredMethod("onClick", View.class), new InsteadHook(callFrame -> { + try { + GuildRole role = ((RolesListView$updateView$$inlined$forEach$lambda$1) callFrame.thisObject).$role; + RolesListView view = ((RolesListView$updateView$$inlined$forEach$lambda$1) callFrame.thisObject).this$0; + + Bundle args = new Bundle(); + args.putString("roleColor", role.b() != 0 ? String.format("%06x", role.b()) : "default"); + args.putString("roleId", String.valueOf(role.getId())); + args.putString("roleName", role.g()); + args.putBoolean("isManaged", role.e()); + args.putBoolean("isHoisted", role.c()); + args.putBoolean("hasIcon", role.d() != null); + if(role.d() != null) args.putString("icon", role.d()); + + var roleMenu = new RoleBottomSheet(); + roleMenu.setArguments(args); + roleMenu.show(cachedFragment, "Role Menu"); + } catch (Exception e) { + log.error(e); + } + return null; + })); + + patcher.patch(RoleMentionNode.class.getDeclaredMethod("render", SpannableStringBuilder.class, RoleMentionNode.RenderContext.class), new InsteadHook(callFrame -> { + RoleMentionNode _this = (RoleMentionNode) callFrame.thisObject; + SpannableStringBuilder builder = (SpannableStringBuilder) callFrame.args[0]; + RoleMentionNode.RenderContext nodeRc = (RoleMentionNode.RenderContext) callFrame.args[1]; + + int length = builder.length(); + builder.append("@"); + Map roles = nodeRc.getRoles(); + GuildRole guildRole = roles != null ? roles.get(Long.valueOf(_this.getRoleId())) : null; + GuildRole role = roles != null ? roles.get(Long.valueOf(_this.getRoleId())) : null; + if (guildRole != null) { + builder.append(guildRole.g()); + ClickableSpan cs = new ClickableSpan() { @Override public void onClick(View widget) { try { Bundle args = new Bundle(); args.putString("roleColor", role.b() != 0 ? String.format("%06x", role.b()) : "default"); args.putString("roleId", String.valueOf(role.getId())); args.putString("roleName", role.g()); args.putBoolean("isManaged", role.e()); args.putBoolean("isHoisted", role.c()); args.putBoolean("hasIcon", role.d() != null); if(role.d() != null) args.putString("icon", role.d()); var roleMenu = new RoleBottomSheet(); roleMenu.setArguments(args); roleMenu.show(cachedFragment, "Role Menu"); } catch(Throwable e) { log.error(e); } } @Override public void updateDrawState(TextPaint ds) { ds.setUnderlineText(false); }}; + List listOf = Arrays.asList(cs, new StyleSpan(1), new ForegroundColorSpan(!RoleUtils.isDefaultColor(guildRole) ? ColorUtils.setAlphaComponent(guildRole.b(), 255) : ColorCompat.getThemedColor(nodeRc.getContext(), (int) R.b.theme_chat_mention_foreground)), new BackgroundColorSpan(!RoleUtils.isDefaultColor(guildRole) ? ColorUtils.setAlphaComponent(guildRole.b(), 25) : ColorCompat.getThemedColor(nodeRc.getContext(), (int) R.b.theme_chat_mention_background))); + + for (Object obj : listOf) { + builder.setSpan(obj, length, builder.length(), 33); + } + } else { + builder.append("invalid-role"); + } + return null; + })); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + patcher.unpatchAll(); + } +} diff --git a/plugins/ShittyDownloadStickers/build.gradle.kts b/plugins/ShittyDownloadStickers/build.gradle.kts new file mode 100644 index 0000000..6a09fc5 --- /dev/null +++ b/plugins/ShittyDownloadStickers/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.2" +description = "ShittyDownloadStickers" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/ShittyDownloadStickers.java b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/ShittyDownloadStickers.java new file mode 100644 index 0000000..891149d --- /dev/null +++ b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/ShittyDownloadStickers.java @@ -0,0 +1,167 @@ +package com.aliucord.plugins; + +import android.content.Context; +import android.os.Environment; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import com.aliucord.Http; +import com.aliucord.Logger; +import com.aliucord.Utils; +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.Hook; +import com.aliucord.plugins.clonemodal.Modal; +import com.aliucord.utils.DimenUtils; +import com.aliucord.views.Button; +import com.discord.api.sticker.BaseSticker; +import com.discord.api.sticker.Sticker; +import com.discord.databinding.WidgetGuildStickerSheetBinding; +import com.discord.widgets.stickers.GuildStickerSheetViewModel; +import com.discord.widgets.stickers.WidgetGuildStickerSheet; +import com.lytefast.flexinput.R; + +import java.io.File; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class ShittyDownloadStickers extends Plugin { + public static final Logger log = new Logger("ShittyDownloadStickers"); + private File downloadDirectory = new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))); + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) throws NoSuchMethodException { + final int layoutId = View.generateViewId(); + var method = WidgetGuildStickerSheet.class.getDeclaredMethod("getBinding"); + method.setAccessible(true); + patcher.patch(WidgetGuildStickerSheet.class, "configureUI", new Class[] { GuildStickerSheetViewModel.ViewState.Loaded.class }, new Hook(callFrame -> { + try { + // i looked at ven's plugin for help since i have no idea what im doing + var sticker = ( (GuildStickerSheetViewModel.ViewState.Loaded) callFrame.args[0]).component1(); + var nitro = ( (GuildStickerSheetViewModel.ViewState.Loaded) callFrame.args[0]).component2(); + var isKnown = ( (GuildStickerSheetViewModel.ViewState.Loaded) callFrame.args[0]).getGuildStickerGuildInfo() != null; + var isUserInGuild = ( (GuildStickerSheetViewModel.ViewState.Loaded) callFrame.args[0]).getGuildStickerGuildInfo().isUserInGuild(); + + var binding = (WidgetGuildStickerSheetBinding) method.invoke(callFrame.thisObject); + if (binding == null) return; + var root = (ViewGroup) binding.getRoot(); + var rootLayout = (android.widget.LinearLayout) root.getChildAt(0); + + if (rootLayout.findViewById(layoutId) != null) return; + + var ctx = rootLayout.getContext(); + if (ctx == null) return; + + int marginDpFour = DimenUtils.dpToPx(4); + int marginDpEight = marginDpFour * 2; + int marginDpSixteen = marginDpEight * 2; + + var downloadButton = new Button(ctx); + configureDownload(context, sticker, downloadButton); + + var copyUrl = new Button(ctx); + copyUrl.setText("Copy URL"); + copyUrl.setPadding(DimenUtils.dpToPx(18), DimenUtils.dpToPx(18), DimenUtils.dpToPx(18), DimenUtils.dpToPx(18)); + copyUrl.setOnClickListener( (view) -> { + String url = getCDNAssetUrl(sticker); + Utils.setClipboard("URL", url); + Utils.showToast("Copied to clipboard"); + }); + + var cloneButton = new Button(ctx); + cloneButton.setText("Clone to other server"); + cloneButton.setOnClickListener(v -> Utils.openPageWithProxy(ctx, new Modal(getCDNAssetUrl(sticker), sticker.h(), sticker.getId(), sticker.f(), sticker.j()))); + + var buttonParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + buttonParams.setMargins(0, 0, 0, 0); + downloadButton.setLayoutParams(buttonParams); + copyUrl.setLayoutParams(buttonParams); + cloneButton.setLayoutParams(buttonParams); + + var pluginButtonLayout = new com.aliucord.widgets.LinearLayout(ctx); + pluginButtonLayout.setId(layoutId); + pluginButtonLayout.addView(downloadButton); + pluginButtonLayout.addView(copyUrl); + pluginButtonLayout.addView(cloneButton); + + int idx = 2; + if ( + (!nitro /* need nitro */ || + !isUserInGuild /* not on server */ + ) && isKnown + ) { + // Nitro or Join Button visible + pluginButtonLayout.setPadding(marginDpSixteen, marginDpFour, marginDpSixteen, marginDpEight); + + // Adjust nitro and join button + var params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 0); + binding.k.setLayoutParams(params); // Nitro + binding.l.setLayoutParams(params); // Join + + // Adjust nitro/join container + var joinContainer = (FrameLayout) binding.k.getParent(); + var containerParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + containerParams.setMargins(marginDpSixteen, marginDpEight, marginDpSixteen, 0); + joinContainer.setLayoutParams(containerParams); + } else { + // No buttons + pluginButtonLayout.setPadding(marginDpSixteen, marginDpEight, marginDpSixteen, marginDpEight); + } + + rootLayout.addView(pluginButtonLayout, idx); + } catch (Exception e) { + logger.errorToast(e); + } + })); + } + + private void configureDownload(Context context, Sticker sticker, Button downloadButton) { + Runnable callSelf = () -> configureDownload(context, sticker, downloadButton); + + File stickerSave = new File(downloadDirectory, sticker.d() + sticker.b()); + downloadButton.setPadding(DimenUtils.dpToPx(18), DimenUtils.dpToPx(18), DimenUtils.dpToPx(18), DimenUtils.dpToPx(18)); + if (!stickerSave.exists()) { + downloadButton.setText("Save Sticker"); + downloadButton.setBackgroundColor(context.getColor(R.c.uikit_btn_bg_color_selector_brand)); + downloadButton.setOnClickListener((view) -> { + Utils.threadPool.execute(() -> { + try { + String url = getCDNAssetUrl(sticker); + + Http.simpleDownload(url, stickerSave); + Utils.showToast(String.format("Saved sticker to %s", stickerSave.getAbsolutePath())); + callSelf.run(); + } catch (Throwable e) { + logger.errorToast("Failed to download sticker.", e); + } + }); + }); + } else { + downloadButton.setText("Remove Saved Sticker"); + downloadButton.setBackgroundColor(context.getColor(R.c.uikit_btn_bg_color_selector_red)); + downloadButton.setOnClickListener((view) -> { + Utils.threadPool.execute(() -> { + if (stickerSave.delete()) + callSelf.run(); + else + Utils.showToast("Failed to delete sticker :("); + }); + }); + } + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + patcher.unpatchAll(); + } + + private final String getCDNAssetUrl(BaseSticker baseSticker) { + return "https://cdn.discordapp.com/stickers/" + baseSticker.d() + baseSticker.b(); + } + +} diff --git a/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/Adapter.java b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/Adapter.java new file mode 100644 index 0000000..5adfd68 --- /dev/null +++ b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/Adapter.java @@ -0,0 +1,69 @@ +/* + * Ven's Aliucord Plugins + * Copyright (C) 2021 Vendicated + * + * Licensed 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 +*/ + +package com.aliucord.plugins.clonemodal; + +import android.content.Context; +import android.view.*; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.aliucord.Utils; +import com.discord.models.guild.Guild; +import com.discord.utilities.extensions.SimpleDraweeViewExtensionsKt; +import com.lytefast.flexinput.R; + +import java.util.List; + +public class Adapter extends RecyclerView.Adapter { + private static final int layoutId = Utils.getResId("widget_user_profile_adapter_item_server", "layout"); + + private final List guilds; + private final Modal modal; + + public Adapter(Modal modal, List guilds) { + this.guilds = guilds; + this.modal = modal; + } + + @Override + public int getItemCount() { + return guilds.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + var layout = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); + return new ViewHolder(this, (ViewGroup) layout); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + var guild = guilds.get(position); + + if (guild.getIcon() != null) { + var color = holder.icon.getContext().getColor(R.c.primary_dark_600); + SimpleDraweeViewExtensionsKt.setGuildIcon(holder.icon, false, guild, 0, null, color, null, null, true, null); + holder.iconText.setVisibility(View.GONE); + } else { + holder.icon.setVisibility(View.GONE); + holder.iconText.setText(guild.getShortName()); + } + + holder.name.setText(guild.getName()); + } + + public void onClick(Context ctx, int position) { + var guild = guilds.get(position); + modal.clone(ctx, guild); + } +} diff --git a/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/Modal.java b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/Modal.java new file mode 100644 index 0000000..3e51481 --- /dev/null +++ b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/Modal.java @@ -0,0 +1,123 @@ +/* + * Ven's Aliucord Plugins + * Copyright (C) 2021 Vendicated + * + * Licensed 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 +*/ + +package com.aliucord.plugins.clonemodal; + +import android.content.Context; +import android.view.View; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.aliucord.CollectionUtils; +import com.aliucord.Http; +import com.aliucord.Utils; +import com.aliucord.fragments.SettingsPage; +import com.aliucord.plugins.ShittyDownloadStickers; +import com.discord.api.permission.Permission; +import com.discord.models.guild.Guild; +import com.discord.stores.StoreGuilds; +import com.discord.stores.StoreStream; +import com.discord.utilities.permissions.PermissionUtils; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class Modal extends SettingsPage { + private static final Map stickerLimits = new HashMap<>(); + static { + stickerLimits.put(0, 0); + stickerLimits.put(1, 15); + stickerLimits.put(2, 30); + stickerLimits.put(3, 60); + } + + private final Map guildPerms = StoreStream.getPermissions().getGuildPermissions(); + private final StoreGuilds guildStore = StoreStream.getGuilds(); + + private final String name; + private final String url; + private final long id; + private final String description; + private final String tags; + + public Modal(String url, String name, long id, String description, String tags) { + this.url = url; + this.name = name; + this.id = id; + this.description = description; + this.tags = tags; + } + + @Override + public void onViewBound(View view) { + super.onViewBound(view); + + setActionBarTitle("Clone Sticker"); + setActionBarSubtitle(name); + + var ctx = view.getContext(); + + setPadding(0); + + var recycler = new RecyclerView(ctx); + recycler.setLayoutManager(new LinearLayoutManager(ctx, RecyclerView.VERTICAL, false)); + var adapter = new Adapter(this, CollectionUtils.filter(guildStore.getGuilds().values(), this::isCandidate)); + recycler.setAdapter(adapter); + + addView(recycler); + } + + private boolean isCandidate(Guild guild) { + var perms = guildPerms.get(guild.getId()); + if (!PermissionUtils.can(Permission.MANAGE_EMOJIS_AND_STICKERS, perms)) return false; + int usedSlots = 0; + for (var sticker : guild.getStickers()) { + if (sticker.getId() == id) return false; + + usedSlots++; + } + var slots = stickerLimits.get(guild.getPremiumTier()); + return slots != null && usedSlots < slots; + } + + public void clone(Context ctx, Guild guild) { + Utils.threadPool.execute(() -> { + + try { + var request = Http.Request.newDiscordRequest("/guilds/"+guild.getId()+"/stickers", "POST"); + var tempFile = File.createTempFile("sticker", ".png"); + Http.simpleDownload(url, tempFile); + + request.executeWithMultipartForm(Map.of("file", tempFile, "name", name, "description", description != null ? description : "", "tags", tags)); + } catch (Exception e) { + ShittyDownloadStickers.log.errorToast(e); + } + + /** + var api = RestAPI.getApi(); + var uri = imageToDataUri(); + if (uri == null) { + //EmojiUtility.logger.error(ctx, "Something went wrong while preparing the image"); + return; + } + var obs = api.postGuildEmoji(guild.getId(), new RestAPIParams.PostGuildEmoji(name, uri)); + var res = RxUtils.getResultBlocking(obs); + /**if (res.second == null) + EmojiUtility.logger.info(ctx, String.format("Successfully cloned %s to %s", name, guild.getName())); + else + EmojiUtility.logger.error(ctx, "Something went wrong while cloning this emoji", res.second);*/ + + + }); + } +} diff --git a/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/ViewHolder.java b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/ViewHolder.java new file mode 100644 index 0000000..ac01c5b --- /dev/null +++ b/plugins/ShittyDownloadStickers/src/main/java/com/aliucord/plugins/clonemodal/ViewHolder.java @@ -0,0 +1,56 @@ +/* + * Ven's Aliucord Plugins + * Copyright (C) 2021 Vendicated + * + * Licensed 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 +*/ + +package com.aliucord.plugins.clonemodal; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.aliucord.Utils; +import com.facebook.drawee.view.SimpleDraweeView; + +public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + private static final int iconId = Utils.getResId("user_profile_adapter_item_server_image", "id"); + private static final int iconTextId = Utils.getResId("user_profile_adapter_item_server_text", "id"); + private static final int serverNameId = Utils.getResId("user_profile_adapter_item_server_name", "id"); + private static final int identityBarrierId = Utils.getResId("guild_member_identity_barrier", "id"); + private static final int serverAvatarId = Utils.getResId("guild_member_avatar", "id"); + private static final int serverNickId = Utils.getResId("user_profile_adapter_item_user_display_name", "id"); + + private final Adapter adapter; + + public final SimpleDraweeView icon; + public final TextView iconText; + public final TextView name; + + public ViewHolder(Adapter adapter, @NonNull ViewGroup layout) { + super(layout); + this.adapter = adapter; + + icon = layout.findViewById(iconId); + iconText = layout.findViewById(iconTextId); + name = layout.findViewById(serverNameId); + + // Hide server profile stuff + layout.findViewById(identityBarrierId).setVisibility(View.GONE); + layout.findViewById(serverAvatarId).setVisibility(View.GONE); + layout.findViewById(serverNickId).setVisibility(View.GONE); + + layout.setOnClickListener(this); + } + + @Override public void onClick(View view) { + adapter.onClick(view.getContext(), getAdapterPosition()); + } +} diff --git a/plugins/VCID/build.gradle.kts b/plugins/VCID/build.gradle.kts new file mode 100644 index 0000000..bd98747 --- /dev/null +++ b/plugins/VCID/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.1.3" +description = "VCID" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/VCID/src/main/java/com/aliucord/plugins/VCID.java b/plugins/VCID/src/main/java/com/aliucord/plugins/VCID.java new file mode 100644 index 0000000..92fa49f --- /dev/null +++ b/plugins/VCID/src/main/java/com/aliucord/plugins/VCID.java @@ -0,0 +1,93 @@ +package com.aliucord.plugins; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.aliucord.Utils; +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.Hook; +import com.aliucord.utils.DimenUtils; +import com.discord.databinding.WidgetChannelsListItemChannelVoiceBinding; +import com.discord.utilities.permissions.PermissionUtils; +import com.discord.widgets.channels.list.WidgetChannelsListAdapter; +import com.discord.widgets.channels.list.items.ChannelListItem; +import com.discord.widgets.channels.list.items.ChannelListItemVoiceChannel; +import com.discord.widgets.voice.settings.WidgetVoiceChannelSettings; +import com.lytefast.flexinput.R; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class VCID extends Plugin { + @SuppressLint("SetTextI18n") + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) throws Throwable { + var itemClass = WidgetChannelsListAdapter.ItemChannelVoice.class.getDeclaredField("binding"); + itemClass.setAccessible(true); + // add the patch + patcher.patch(WidgetChannelsListAdapter.ItemChannelVoice.class.getDeclaredMethod("onConfigure", int.class, ChannelListItem.class), new Hook(callFrame -> { + var channel = (ChannelListItemVoiceChannel) callFrame.args[1]; + if (PermissionUtils.can(16, channel.component1().k())) return; + + try { + var binding = (WidgetChannelsListItemChannelVoiceBinding) itemClass.get(callFrame.thisObject); + binding.a.setOnLongClickListener(view -> { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("VCID", String.valueOf(channel.getChannel().k())); + int duration = Toast.LENGTH_SHORT; + Toast.makeText(context, "Copied to clipboard.", duration).show(); + clipboard.setPrimaryClip(clip); + return true; + }); + + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + })); + + final int viewId = View.generateViewId(); + + patcher.patch(WidgetVoiceChannelSettings.class.getDeclaredMethod("configureUI", WidgetVoiceChannelSettings.Model.class), new Hook(callFrame -> { + var binding = WidgetVoiceChannelSettings.access$getBinding$p((WidgetVoiceChannelSettings) callFrame.thisObject); + var model = (WidgetVoiceChannelSettings.Model) callFrame.args[0]; + + var layout = (LinearLayout) binding.a.findViewById(Utils.getResId("channel_settings_section_user_management", "id")); + if (layout.findViewById(viewId) != null) { + return; + } + + var textView = new TextView(context, null, 0, Utils.getResId("UiKit_Settings_Item_Icon", "style")); + textView.setId(viewId); + textView.setText("Copy ID"); + textView.setTextColor(Color.WHITE); + textView.setTextSize(16); + textView.setCompoundDrawablePadding(DimenUtils.dpToPx(32)); + var padding = DimenUtils.dpToPx(16); + textView.setPadding(padding, padding, padding, padding); + textView.setCompoundDrawablesWithIntrinsicBounds(R.e.ic_content_copy_white_a60_24dp, 0,0, 0); + textView.setOnClickListener(view -> { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("VCID", String.valueOf(model.getChannel().k())); + int duration = Toast.LENGTH_SHORT; + Toast.makeText(context, "Copied to clipboard.", duration).show(); + clipboard.setPrimaryClip(clip); + }); + + layout.addView(textView, 2); + })); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + // Remove all patches + patcher.unpatchAll(); + } +} diff --git a/plugins/VideoEmbedPatch/build.gradle.kts b/plugins/VideoEmbedPatch/build.gradle.kts new file mode 100644 index 0000000..5edcdfe --- /dev/null +++ b/plugins/VideoEmbedPatch/build.gradle.kts @@ -0,0 +1,13 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.1.1" +description = "Video Embed Patch" + +aliucord { + author(Authors.HalalKing) +} \ No newline at end of file diff --git a/plugins/VideoEmbedPatch/src/main/java/com/aliucord/plugins/VideoEmbedPatch.java b/plugins/VideoEmbedPatch/src/main/java/com/aliucord/plugins/VideoEmbedPatch.java new file mode 100644 index 0000000..27dfc01 --- /dev/null +++ b/plugins/VideoEmbedPatch/src/main/java/com/aliucord/plugins/VideoEmbedPatch.java @@ -0,0 +1,114 @@ +package com.aliucord.plugins; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; +import android.view.View; + +import com.aliucord.Utils; +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.api.SettingsAPI; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.InsteadHook; +import com.aliucord.patcher.PreHook; +import com.aliucord.widgets.BottomSheet; +import com.discord.api.message.embed.EmbedType; +import com.discord.api.message.embed.MessageEmbed; +import com.discord.simpleast.core.node.Node; +import com.discord.utilities.embed.EmbedResourceUtils; +import com.discord.utilities.intent.IntentUtils; +import com.discord.utilities.textprocessing.MessagePreprocessor; +import com.discord.utilities.textprocessing.MessageRenderContext; +import com.discord.utilities.textprocessing.node.UrlNode; +import com.discord.views.CheckedSetting; + +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +import d0.t.u; + + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class VideoEmbedPatch extends Plugin { + public static class PluginSettings extends BottomSheet { + private final SettingsAPI settings; + + public PluginSettings(SettingsAPI settings) { this.settings = settings; } + + public void onViewCreated(View view, Bundle bundle) { + super.onViewCreated(view, bundle); + + addView(createCheckedSetting(view.getContext(), "Strip video embed links", "stripLinks", true)); + addView(createCheckedSetting(view.getContext(), "Correct copied links", "correctCopy", true)); + } + + private CheckedSetting createCheckedSetting(Context ctx, String title, String setting, boolean checked) { + CheckedSetting checkedSetting = Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, null); + + checkedSetting.setChecked(settings.getBool(setting, checked)); + checkedSetting.setOnCheckedListener( check -> { + settings.setBool(setting, check); + }); + + return checkedSetting; + } + } + + public VideoEmbedPatch() { + settingsTab = new SettingsTab(PluginSettings.class, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings); + } + + private final Pattern mediaRegex = Pattern.compile("(https:\\/\\/)media(\\.discordapp\\.)net(\\/attachments\\/\\d+\\/\\d+\\/.+(\\.mov|\\.mp4|\\.webm))"); + + @SuppressLint("SetTextI18n") + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) throws Throwable { + if (settings.getBool("correctCopy", true)) { + // add the patch + patcher.patch(IntentUtils.class.getDeclaredMethod("performChooserSendIntent", Context.class, String.class, CharSequence.class), new PreHook(callFrame -> { + callFrame.args[1] = fixMediaUrl(callFrame.args[1].toString()); + })); + } + + if (settings.getBool("stripLinks", true)) { + var field = MessagePreprocessor.class.getDeclaredField("embeds"); + field.setAccessible(true); + + patcher.patch("com.discord.utilities.textprocessing.MessagePreprocessor", "stripSimpleEmbedLink", new Class[]{Collection.class}, new InsteadHook(callFrame -> { + var collection = (Collection>) callFrame.args[0]; + + // fucky reflection shit that should not be done + try { + var embeds = (List) field.get(callFrame.thisObject); + + if (collection.size() == 1 && embeds != null && embeds.size() == 1) { + MessageEmbed messageEmbed = embeds.get(0); + if ((((Node) u.elementAt(collection, 0)) instanceof UrlNode) && (EmbedResourceUtils.INSTANCE.isSimpleEmbed(messageEmbed) || messageEmbed.k() == EmbedType.VIDEO)) { + collection.clear(); + } + } + } catch (IllegalAccessException e) { + logger.errorToast(e); + } + + return null; + })); + } + + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + // Remove all patches + patcher.unpatchAll(); + } + + private String fixMediaUrl(String url) { + return mediaRegex.matcher(url).replaceFirst("$1cdn$2com$3"); + } +}