From a4a3e4eca0ae5c554ac7a0380f7b5f5f42de798e Mon Sep 17 00:00:00 2001 From: RazerTexz <66409416+RazerTexz@users.noreply.github.com> Date: Thu, 21 May 2026 08:38:47 +0700 Subject: [PATCH] feat: Accelerator --- .../com/aliucord/gradle/repo/Authors.kt | 3 + .../AttachmentKeyboardFix/build.gradle.kts | 13 + .../plugins/AttachmentKeyboardFix.java | 44 +++ .../CopyInsteadOfShareImages/build.gradle.kts | 13 + .../plugins/CopyInsteadOfShareImages.java | 192 +++++++++++++ plugins/FakeStickers/build.gradle.kts | 29 ++ .../com/accelerator/plugins/FakeStickers.java | 102 +++++++ plugins/MediaPickerPatcher/build.gradle.kts | 30 ++ .../plugins/MediaPickerPatcher.java | 173 ++++++++++++ plugins/NitroSpoofForNQN/build.gradle.kts | 13 + .../java/com/accelerator/plugins/NS4NQN.kt | 75 +++++ plugins/TwitterRebuild/build.gradle.kts | 32 +++ .../java/com/accelerator/plugins/Main.java | 263 ++++++++++++++++++ 13 files changed, 982 insertions(+) create mode 100644 plugins/AttachmentKeyboardFix/build.gradle.kts create mode 100644 plugins/AttachmentKeyboardFix/src/main/java/com/accelerator/plugins/AttachmentKeyboardFix.java create mode 100644 plugins/CopyInsteadOfShareImages/build.gradle.kts create mode 100644 plugins/CopyInsteadOfShareImages/src/main/java/com/accelerator/plugins/CopyInsteadOfShareImages.java create mode 100644 plugins/FakeStickers/build.gradle.kts create mode 100644 plugins/FakeStickers/src/main/java/com/accelerator/plugins/FakeStickers.java create mode 100644 plugins/MediaPickerPatcher/build.gradle.kts create mode 100644 plugins/MediaPickerPatcher/src/main/java/com/accelerator/plugins/MediaPickerPatcher.java create mode 100644 plugins/NitroSpoofForNQN/build.gradle.kts create mode 100644 plugins/NitroSpoofForNQN/src/main/java/com/accelerator/plugins/NS4NQN.kt create mode 100644 plugins/TwitterRebuild/build.gradle.kts create mode 100644 plugins/TwitterRebuild/src/main/java/com/accelerator/plugins/Main.java 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 4a54c7e..2ef9381 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 @@ -48,6 +48,9 @@ object Authors { val miaaaa0a = Author("miaaaa0a", 435750383491481602L) val Cloudburst = Author("Cloudburst", 295186738085756929L) val HalalKing = Author("HalalKing", 261634919980204033L) + val Accelerator = Author("Accelerator", 150234173024501762L) + val Vendicated = Author("Vendicated", 343383572805058560L) + val Derlan = Author("Derlan", 821545900807028757L) } /** diff --git a/plugins/AttachmentKeyboardFix/build.gradle.kts b/plugins/AttachmentKeyboardFix/build.gradle.kts new file mode 100644 index 0000000..96226aa --- /dev/null +++ b/plugins/AttachmentKeyboardFix/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 = "Standalone version of the keyboard bugfix when using the media picker, for those that don't want to install the MediaPickerPatcher plugin." + +aliucord { + author(Authors.Accelerator) +} \ No newline at end of file diff --git a/plugins/AttachmentKeyboardFix/src/main/java/com/accelerator/plugins/AttachmentKeyboardFix.java b/plugins/AttachmentKeyboardFix/src/main/java/com/accelerator/plugins/AttachmentKeyboardFix.java new file mode 100644 index 0000000..598b2ea --- /dev/null +++ b/plugins/AttachmentKeyboardFix/src/main/java/com/accelerator/plugins/AttachmentKeyboardFix.java @@ -0,0 +1,44 @@ +package com.accelerator.plugins; + +import android.content.Context; + +import com.aliucord.annotations.AliucordPlugin; +//import com.aliucord.entities.MessageEmbedBuilder; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.InsteadHook; +import com.aliucord.Logger; + +import com.lytefast.flexinput.fragment.*; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class AttachmentKeyboardFix extends Plugin { + + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) { + patcher.patch(FlexInputFragment$b.class,"run",new Class[]{}, new InsteadHook(callFrame->{ + //logger.debug("b.run() fired"); + //logger.debug("State: "+String.valueOf(fragment.getShowsDialog())); + b.b.a.a.a fragment = (b.b.a.a.a)((FlexInputFragment$b)callFrame.thisObject).j; + + if (fragment != null && fragment.isAdded() && !fragment.isRemoving() && !fragment.isDetached()) { + try { + fragment.h(true); + } catch (IllegalStateException e) { + new Logger("AttachmentKBFix").warn("could not dismiss add content dialog"); + } + } + return null; + })); + + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + // Remove all patches + patcher.unpatchAll(); + } +} diff --git a/plugins/CopyInsteadOfShareImages/build.gradle.kts b/plugins/CopyInsteadOfShareImages/build.gradle.kts new file mode 100644 index 0000000..958d1d4 --- /dev/null +++ b/plugins/CopyInsteadOfShareImages/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.8" +description = "Replaces the share button in the image preview with one that copies the image to the clipboard." + +aliucord { + author(Authors.Accelerator) +} \ No newline at end of file diff --git a/plugins/CopyInsteadOfShareImages/src/main/java/com/accelerator/plugins/CopyInsteadOfShareImages.java b/plugins/CopyInsteadOfShareImages/src/main/java/com/accelerator/plugins/CopyInsteadOfShareImages.java new file mode 100644 index 0000000..2da8b62 --- /dev/null +++ b/plugins/CopyInsteadOfShareImages/src/main/java/com/accelerator/plugins/CopyInsteadOfShareImages.java @@ -0,0 +1,192 @@ +package com.accelerator.plugins; + +import android.content.Context; + +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.Hook; + +//Needed for settings page +import com.discord.views.CheckedSetting; +import com.aliucord.api.SettingsAPI; +import com.aliucord.widgets.BottomSheet; +import android.os.Bundle; + +//import com.aliucord.wrappers.embeds.MessageEmbedWrapper; +//import com.discord.widgets.chat.list.entries.ChatListEntry; +//import com.discord.widgets.chat.list.entries.MessageEntry; +import com.aliucord.Utils; +//import com.aliucord.utils.ReflectUtils; +import com.discord.widgets.media.WidgetMedia; +import com.discord.utilities.intent.IntentUtils; +import android.widget.Toast; + +import com.discord.app.AppFragment; +import android.view.View; +import androidx.appcompat.view.menu.ActionMenuItemView; +import androidx.appcompat.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +//import androidx.appcompat.view.menu.MenuBuilder; +//import com.discord.R; +import com.lytefast.flexinput.R; +import androidx.core.content.ContextCompat; +import android.graphics.drawable.*; + +//For setToTint +import com.discord.stores.StoreStream; +import com.discord.utilities.color.ColorCompat; +import android.graphics.Color; +//import com.aliucord.Logger; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class CopyInsteadOfShareImages 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(), "Replace media.discordapp.net with cdn.discordapp.com", "replaceMediaWithCDN", 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 CopyInsteadOfShareImages() { + settingsTab = new SettingsTab(PluginSettings.class, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings); + } + + /** + * Tints a {@link Drawable} to {@link Color#BLACK} if a user has set light theme. + * @param drawable Drawable + * @return Drawable for chaining + */ + public static Drawable tintToTheme(Context context, Drawable drawable) { + if (drawable != null && StoreStream.getUserSettingsSystem().getTheme().equals("light")) drawable.setTint(ColorCompat.getThemedColor(context, R.b.colorInteractiveNormal)); + return drawable; + } + + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) { + // The full name of the class to patch + //var className = "com.discord.widgets.media.WidgetMedia"; + // The method of that class to patch + var methodName = "onViewBoundOrOnResume"; + // These are the arguments the patched methods receives. In the case of + // WidgetChatListAdapterItemMessage.onConfigure the method's implementation is + // public void onConfigure(int i, ChatListEntry chatListEntry), so our methodArguments + // look like this: + var methodArguments = new Class[] { }; + + //Grab icon + Drawable icon = ContextCompat.getDrawable(context,R.e.ic_link_white_24dp).mutate(); + + final int shareButtonId = Utils.getResId("menu_media_share","id"); + + // add the patch + patcher.patch(WidgetMedia.class, methodName, methodArguments, new Hook(callFrame -> { + var binding = WidgetMedia.access$getBinding$p((WidgetMedia) callFrame.thisObject); + var root = binding.getRoot(); + var shareButton = root.findViewById(shareButtonId); + + //The buttons are located in WidgetMedia$onViewBoundOrOnResume$1 + // public final class b implements Toolbar.OnMenuItemClickListener + // package b.a.d; + + try { + //Old (Doesn't work for embeds and stuff) + //var imageUri = ReflectUtils.getField(callFrame.thisObject,"imageUri").toString(); + + //Better one + String imageUri = ((AppFragment)callFrame.thisObject).getMostRecentIntent().getStringExtra("INTENT_MEDIA_URL"); + + //Use INTENT_IMAGE_URL for Discord attachments urls to always get unexpired links. + if (imageUri.contains("cdn.discordapp.com/attachments")||imageUri.contains("media.discordapp.net/attachments")) + imageUri = ((AppFragment)callFrame.thisObject).getMostRecentIntent().getStringExtra("INTENT_IMAGE_URL"); + + if (settings.getBool("replaceMediaWithCDN", true)) + imageUri = imageUri.replace("media.discordapp.net/attachments","cdn.discordapp.com/attachments"); + + //Because I can't make a string final after it's already been created... + final String imageUriFinal = new String(imageUri); + + + if (shareButton != null) + { + + shareButton.setOnClickListener( new View.OnClickListener() { + @Override + public void onClick (View v) { + Utils.setClipboard(null,imageUriFinal); + Toast.makeText(context, "Copied "+imageUriFinal, Toast.LENGTH_SHORT).show(); + } + }); + + /* + m.checkNotNullExpressionValue(context, "context"); + String uri2 = this.$sourceUri.toString(); + m.checkNotNullExpressionValue(uri2, "sourceUri.toString()"); + IntentUtils.performChooserSendIntent$default(context, uri2, null, 4, null); + */ + shareButton.setOnLongClickListener((View.OnLongClickListener) v -> { + Toolbar toolbar = root.findViewById(Utils.getResId("action_bar_toolbar","id")); + IntentUtils.performChooserSendIntent$default(toolbar.getContext(),imageUriFinal,null,4,null); + return true; + }); + + + //Have to tint it every time in case they change themes + //((ActionMenuItemView)shareButton).setIcon(Utils.tintToTheme(icon)); + ((ActionMenuItemView)shareButton).setIcon(tintToTheme(shareButton.getContext(),icon)); + } + else + { + //Most likely there is no share button because the screen width is too small. + //Instead append a new command to the three dot menu. + //TODO... + Toolbar toolbar = root.findViewById(Utils.getResId("action_bar_toolbar","id")); + //toolbar.showOverflowMenu(); + + //Abandon all hope, ye who enter here! + Menu menu = toolbar.getMenu(); + MenuItem newItem = menu.add("Copy URL"); + newItem.setOnMenuItemClickListener(item -> { + Utils.setClipboard(null,imageUriFinal); + Toast.makeText(context, "Copied "+imageUriFinal, Toast.LENGTH_SHORT).show(); + return true; + }); + } + + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(context, "Oops, can't get image URL (Or something went wrong)", Toast.LENGTH_SHORT).show(); + } + + //shareButton.setVisibility(View.GONE); + + })); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + // Remove all patches + patcher.unpatchAll(); + } +} \ No newline at end of file diff --git a/plugins/FakeStickers/build.gradle.kts b/plugins/FakeStickers/build.gradle.kts new file mode 100644 index 0000000..06bca42 --- /dev/null +++ b/plugins/FakeStickers/build.gradle.kts @@ -0,0 +1,29 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.1.3" +description = "Posts a sticker as an image if the sticker is unavailable normally (Usually when you don't have Nitro). Lottie stickers are unsupported." + +aliucord { + changelogMedia.set("https://cdn.discordapp.com/stickers/883809297216192573.png") + changelog.set( + """ + # 1.1.3 + * Allow replying + # 1.1.2 + * Set sticker size to 160px + # 1.1.1 + * Support Discord 105.12 + # 1.1.0 + * Make sticker picker automatically close after selecting a sticker + * Do not mark stickers as unusable (monochrome filter) + """.trimIndent() + ) + + author(Authors.Accelerator) + author(Authors.Vendicated) +} \ No newline at end of file diff --git a/plugins/FakeStickers/src/main/java/com/accelerator/plugins/FakeStickers.java b/plugins/FakeStickers/src/main/java/com/accelerator/plugins/FakeStickers.java new file mode 100644 index 0000000..529e144 --- /dev/null +++ b/plugins/FakeStickers/src/main/java/com/accelerator/plugins/FakeStickers.java @@ -0,0 +1,102 @@ +package com.accelerator.plugins; + +import android.content.Context; + +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.*; + +import com.aliucord.Logger; +import com.aliucord.Utils; + +import com.aliucord.utils.ReflectUtils; +import com.discord.widgets.chat.input.sticker.*; +import com.discord.utilities.stickers.StickerUtils; +import com.discord.widgets.chat.MessageManager; +import java.util.Collections; +import com.discord.stores.StoreStream; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class FakeStickers extends Plugin { + + public FakeStickers() {} + + @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 fMessageManager = StickerPickerViewModel.class.getDeclaredField("messageManager"); + fMessageManager.setAccessible(true); + // add the patch + + // Do not mark stickers as unsendable (grey overlay) + patcher.patch(StickerItem.class.getDeclaredMethod("getSendability"), InsteadHook.returnConstant(StickerUtils.StickerSendability.SENDABLE)); + + //Patch onClick to send sticker + patcher.patch(WidgetStickerPicker.class.getDeclaredMethod("onStickerItemSelected", StickerItem.class), new PreHook(param -> { + try { + // getSendability is patched above to always return SENDABLE so get the real value via reflect + if (ReflectUtils.getField(param.args[0], "sendability") == StickerUtils.StickerSendability.SENDABLE) return; + + var sticker = ((StickerItem) param.args[0]).getSticker(); + var messageManager = (MessageManager) fMessageManager.get( + WidgetStickerPicker.access$getViewModel$p((WidgetStickerPicker) param.thisObject) + ); + messageManager.sendMessage( + "https://media.discordapp.net/stickers/"+sticker.d/*id*/()+sticker.b/*format*/()+"?size=160", + Collections.emptyList(),//mentions + null,//attachments + null,//channel ID + Collections.emptyList(),//stickers + true,//use pending reply + null,//message too long handler(?) + null,//attachments too large handler(?) + (m) -> { + this.logger.debug(m.getMessageResult().toString()); + return null; + }//messageSendResultHandler + ); + + // Skip original method + param.setResult(null); + + // Dismiss sticker picker + var stickerListener = (StickerPickerListener) ReflectUtils.getField(param.thisObject, "stickerPickerListener"); + stickerListener.onStickerPicked(sticker); + } catch (Throwable e) { + this.logger.error(e); + } + })); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + // Remove all patches + patcher.unpatchAll(); + } +} + +/* +This plugin was mainly written as a learning exercise in order to +figure out how to send links on tap since it would be required to +send custom stickers. Whether or not custom stickers will ever +happen remains to be seen... + + +Copyright (C) Rhythm Lunatic 2021 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ diff --git a/plugins/MediaPickerPatcher/build.gradle.kts b/plugins/MediaPickerPatcher/build.gradle.kts new file mode 100644 index 0000000..782f3ae --- /dev/null +++ b/plugins/MediaPickerPatcher/build.gradle.kts @@ -0,0 +1,30 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.1.0" +description = "Replaces the document picker with one that opens the default gallery app. Can also long press the open file button for the original document picker." + +aliucord { + changelog.set( + """ + # 1.1.0 + * Allow selecting a file picker inside the android file browser when holding the button + # 1.0.91 + * Aliucord removed Pine hooks finally so I had to fix it + # 1.0.9 + * Allow picking more than one file if your gallery app supports it, usually by holding down an image instead of tapping. + # 1.0.8 + * Fix for Discord 112.4 (Will break on earlier versions) + # 1.0.7 + * Fix for Discord 105.12 (Will break on earlier versions) + # 1.0.6 + * Fix the "Allow All" setting being on by default, finally + """.trimIndent() + ) + + author(Authors.Accelerator) +} \ No newline at end of file diff --git a/plugins/MediaPickerPatcher/src/main/java/com/accelerator/plugins/MediaPickerPatcher.java b/plugins/MediaPickerPatcher/src/main/java/com/accelerator/plugins/MediaPickerPatcher.java new file mode 100644 index 0000000..b3ce3b7 --- /dev/null +++ b/plugins/MediaPickerPatcher/src/main/java/com/accelerator/plugins/MediaPickerPatcher.java @@ -0,0 +1,173 @@ +package com.accelerator.plugins; + +import android.content.Context; + +import com.aliucord.PluginManager; +import com.aliucord.annotations.AliucordPlugin; +//import com.aliucord.entities.MessageEmbedBuilder; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.Hook; +import com.aliucord.patcher.InsteadHook; +import com.aliucord.Logger; +import com.aliucord.Utils; +import android.widget.Toast; +import android.provider.MediaStore; +import android.content.*; +import android.view.LayoutInflater; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +//Needed for settings page +import com.discord.views.CheckedSetting; +import com.aliucord.api.SettingsAPI; +import com.aliucord.widgets.BottomSheet; +import android.os.Bundle; + +// +import com.lytefast.flexinput.fragment.*; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class MediaPickerPatcher 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(), "Allow all file types", "MMP_AllowAllFiles", false)); + } + + private CheckedSetting createCheckedSetting(Context ctx, String title, String setting, boolean checkedByDefault) { + + //fun createCheckedSetting(context: Context, type: CheckedSetting.ViewType, text: CharSequence?, subtext: CharSequence?) + CheckedSetting checkedSetting = Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, null); + + checkedSetting.setChecked(settings.getBool(setting, checkedByDefault)); + checkedSetting.setOnCheckedListener( check -> { + settings.setBool(setting, check); + }); + + return checkedSetting; + } + } + + public MediaPickerPatcher() { + settingsTab = new SettingsTab(PluginSettings.class, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings); + if (PluginManager.plugins.get("AttachmentKeyboardFix") != null && PluginManager.isPluginEnabled("AttachmentKeyboardFix")) + { + Utils.showToast("Do not enable AttachmentKeyboardFix and MediaPickerPatcher at the same time, they do the same thing!!",true); + } + } + + private void launchPicker(b.b.a.a.a pickerObj, Intent intent) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + try { + pickerObj.startActivityForResult(intent, 5968); + } catch (ActivityNotFoundException unused) { + Toast.makeText(pickerObj.getContext(), "lmao", 0).show(); + } + } + + @Override + // Called when your plugin is started. This is the place to register command, add patches, etc + public void start(Context context) { + Logger logger = new Logger("MediaPickerPatcher"); + + patcher.patch(b.b.a.a.a.class, "onCreateView", new Class[]{LayoutInflater.class,ViewGroup.class,android.os.Bundle.class}, new Hook(callFrame -> { + + + try { + var pickerObj = (b.b.a.a.a)callFrame.thisObject; + var pickerButton = (ImageView)pickerObj.n; + + //pickerButton.setVisibility(View.GONE); + + pickerButton.setOnLongClickListener(view -> { + if (android.os.Build.VERSION.SDK_INT >= 30) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + launchPicker(pickerObj, intent); + } else + { + new b.b.a.a.a$a(1, pickerObj).onClick(view); + } + return true; + }); + + pickerButton.setOnClickListener( new View.OnClickListener() { + @Override + public void onClick (View v) { + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI); + if (settings.getBool("MMP_AllowAllFiles", false)) + intent.setType("*/*"); + else + { + intent.setType("image/* video/*"); + intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/*", "video/*"}); + } + launchPicker(pickerObj, intent); + } + }); + logger.debug("Patched media picker button."); + + } catch (Exception e) { + e.printStackTrace(); + //Toast.makeText(context, "Oops, can't get image URL", Toast.LENGTH_SHORT).show(); + } + + + + })); + + + patcher.patch(FlexInputFragment$b.class,"run",new Class[]{}, new InsteadHook(callFrame->{ + //logger.debug("b.run() fired"); + //logger.debug("State: "+String.valueOf(fragment.getShowsDialog())); + + //See "notes.txt" for more information. + b.b.a.a.a fragment = (b.b.a.a.a)((FlexInputFragment$b)callFrame.thisObject).j; + + if (fragment != null && fragment.isAdded() && !fragment.isRemoving() && !fragment.isDetached()) { + try { + fragment.h(true); //obfuscated .dismiss() function + } catch (IllegalStateException e) { + logger.warn("could not dismiss add content dialog"); + } + } + return null; + })); + } + + @Override + // Called when your plugin is stopped + public void stop(Context context) { + // Remove all patches + patcher.unpatchAll(); + } +} + +/* +Copyright (C) Rhythm Lunatic 2021 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ diff --git a/plugins/NitroSpoofForNQN/build.gradle.kts b/plugins/NitroSpoofForNQN/build.gradle.kts new file mode 100644 index 0000000..223e60f --- /dev/null +++ b/plugins/NitroSpoofForNQN/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") +} + +description = "Allows posting nitro emotes if the server you want to post emotes from and the server you're posting to both have the NQN bot." +version = "1.0.0" + +aliucord { + author(Authors.Accelerator) +} \ No newline at end of file diff --git a/plugins/NitroSpoofForNQN/src/main/java/com/accelerator/plugins/NS4NQN.kt b/plugins/NitroSpoofForNQN/src/main/java/com/accelerator/plugins/NS4NQN.kt new file mode 100644 index 0000000..f195e5e --- /dev/null +++ b/plugins/NitroSpoofForNQN/src/main/java/com/accelerator/plugins/NS4NQN.kt @@ -0,0 +1,75 @@ +package com.accelerator.plugins + +import android.content.Context +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.Hook +import com.aliucord.patcher.InsteadHook +import com.discord.models.domain.emoji.ModelEmojiCustom +import de.robv.android.xposed.XC_MethodHook +import java.lang.reflect.Field + +@SuppressWarnings("unused") +@AliucordPlugin +class NS4NQN : Plugin() { + + private val reflectionCache = HashMap() + + override fun start(context: Context) { + patcher.patch( + ModelEmojiCustom::class.java.getDeclaredMethod("getChatInputText"), + Hook { getChatReplacement(it) } + ) + patcher.patch( + ModelEmojiCustom::class.java.getDeclaredMethod("getMessageContentReplacement"), + Hook { getChatReplacement(it) } + ) + patcher.patch( + ModelEmojiCustom::class.java.getDeclaredMethod("isUsable"), + InsteadHook { true } + ) + patcher.patch( + ModelEmojiCustom::class.java.getDeclaredMethod("isAvailable"), + InsteadHook { true } + ) + } + + override fun stop(context: Context) { + patcher.unpatchAll() + } + + private fun getChatReplacement(callFrame: XC_MethodHook.MethodHookParam) { + val thisObject = callFrame.thisObject as ModelEmojiCustom + val isUsable = thisObject.getCachedField("isUsable") + + if (isUsable) { + callFrame.result = callFrame.result + return + } + + var finalUrl = "<:"+thisObject.getCachedField("name")+":" + finalUrl+=thisObject.getCachedField("idStr")+">" + //val isAnimated = thisObject.getCachedField("isAnimated") + + + + callFrame.result = finalUrl + } + + /** + * Get a reflected field from cache or compute it if cache is absent + * @param V type of the field value + */ + private inline fun Any.getCachedField( + name: String, + instance: Any? = this, + ): V { + val clazz = this::class.java + return reflectionCache.computeIfAbsent(clazz.name + name) { + clazz.getDeclaredField(name).also { + it.isAccessible = true + } + }.get(instance) as V + } + +} diff --git a/plugins/TwitterRebuild/build.gradle.kts b/plugins/TwitterRebuild/build.gradle.kts new file mode 100644 index 0000000..17af8f9 --- /dev/null +++ b/plugins/TwitterRebuild/build.gradle.kts @@ -0,0 +1,32 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.6" +description = "If any (usually Twitter) embed has more than one image, shows all of them. If a Twitter embed has a video, it will also show it." + +aliucord { + changelog.set( + """ + # 1.0.6 + * fxtwitter is no longer working so now we are getting data from twitter embed + # 1.0.5 + * Aliucord removed Pine hooks finally so I had to fix it + # 1.0.4 + * Fix for Discord v101.3 (Also works with older versions) + # 1.0.3 + * Bugfix for real this time I swear + # 1.0.2 + * bugfix + # 1.0.1 + * Now supports videos (barely). + * Special thanks to HalalKing and mantikafasi for the code I kanged to add video support. + """.trimIndent() + ) + + author(Authors.Accelerator) + author(Authors.Derlan) +} \ No newline at end of file diff --git a/plugins/TwitterRebuild/src/main/java/com/accelerator/plugins/Main.java b/plugins/TwitterRebuild/src/main/java/com/accelerator/plugins/Main.java new file mode 100644 index 0000000..3ace491 --- /dev/null +++ b/plugins/TwitterRebuild/src/main/java/com/accelerator/plugins/Main.java @@ -0,0 +1,263 @@ +package com.accelerator.plugins; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; + +import com.aliucord.annotations.AliucordPlugin; +import com.aliucord.api.SettingsAPI; +import com.aliucord.entities.MessageEmbedBuilder; +import com.aliucord.entities.Plugin; +import com.aliucord.patcher.Hook; +import com.aliucord.widgets.BottomSheet; +import com.aliucord.wrappers.embeds.MessageEmbedWrapper; +import com.aliucord.Logger; +import com.aliucord.utils.ReflectUtils; + +import com.discord.api.channel.Channel; +import com.discord.api.message.embed.EmbedType; +import com.discord.models.member.GuildMember; +import com.discord.models.message.Message; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.List; +import java.util.Map; + +import com.discord.api.message.embed.MessageEmbed; +import com.discord.stores.StoreMessageState; +import com.discord.views.CheckedSetting; +import com.discord.widgets.chat.list.entries.ChatListEntry; +import com.discord.widgets.chat.list.entries.EmbedEntry; + +import android.view.View; + +import com.discord.stores.StoreStream; +import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage; + +import com.aliucord.Http; +import com.aliucord.Utils; +import com.aliucord.wrappers.embeds.VideoWrapper; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.discord.views.CheckedSetting; +import com.aliucord.api.SettingsAPI; +import com.aliucord.widgets.BottomSheet; +import android.os.Bundle; + +import org.json.JSONArray; +import org.json.JSONObject; + +// This class is never used so your IDE will likely complain. Let's make it shut up! +@SuppressWarnings("unused") +@AliucordPlugin +public class Main extends Plugin { + + public Main() { + settingsTab = new SettingsTab(PluginSettings.class, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings); + } + + 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(), "VideoEmbeds", true, "Enable video embeds", null)); + addView(createCheckedSetting(view.getContext(), "EnableProxy", false, "Enable proxy", "For example, if twitter is blocked in your country")); + } + + private View createCheckedSetting(Context ctx, String key, boolean defVal, String title, String s) { + CheckedSetting cs = Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, s); + cs.setChecked(settings.getBool(key, defVal)); + cs.setOnCheckedListener(check -> settings.setBool(key, check)); + return cs; + } + } + + public void start(Context context) { + try { + patcher.patch( + ChatListEntry.Companion.class.getDeclaredMethod( + "createEmbedEntries", + Message.class, //0 + StoreMessageState.State.class, //1 + boolean.class, //2 + boolean.class, //3 + boolean.class, //4 + boolean.class, //5 + boolean.class, //6 + Channel.class, //7 + GuildMember.class, //8 + Map.class, //9 + Map.class //10 + ), new Hook(callFrame -> { + Message message = (Message)callFrame.args[0]; + + List embedAndAttachmentList = (List)callFrame.getResult(); + List embeds = message.getEmbeds(); + + if (embedAndAttachmentList.size() < 1 || embeds.size() < 2 || embedAndAttachmentList.size() == embeds.size()) return; + long guildID = embedAndAttachmentList.get(0).getGuildId(); + + EmbedEntry lastAddedEmbed = (EmbedEntry)embedAndAttachmentList.get(embedAndAttachmentList.size()-1); + int embedIndex = lastAddedEmbed.component1()+1; + + var embedColor = embeds.get(0).b() != null ? embeds.get(0).b() : null; + for (int i = 1; i < embeds.size(); i++) { + var embed = embeds.get(i); + if (embedColor != null) { + try { + ReflectUtils.setField(embed,"color",embedColor); + } catch (Exception ignored) {} + } + + embedAndAttachmentList.add(new EmbedEntry( + embedIndex, + guildID, + message, + (StoreMessageState.State)callFrame.args[1], + embed, + (boolean)callFrame.args[2], + (boolean)callFrame.args[3], + (boolean)callFrame.args[4], + (boolean)callFrame.args[5] + )); + embedIndex++; + } + }) + ); + } catch (Exception e) { + e.printStackTrace(); + } + + if (settings.getBool("VideoEmbeds", true)) { + try { + patcher.patch( + WidgetChatListAdapterItemMessage.class.getDeclaredMethod("configureItemTag", + Message.class, + boolean.class + ), new Hook((cf) -> { + var msg = (Message) cf.args[0]; + var embeds = msg.getEmbeds(); + if (embeds.size() != 1) return; + + MessageEmbedWrapper twEmbed = new MessageEmbedWrapper(embeds.get(0)); + VideoWrapper v = twEmbed.getVideo(); + if (v != null && v.getUrl().startsWith("https://twitter.com/i/videos")) { + Pattern pattern = Pattern.compile("\\/tweet\\/([0-9]*)"); + Matcher matcher = pattern.matcher(v.getUrl()); + if(matcher.find()){ + new Thread(() -> { + try { + //Note: p91878l7.beget.tech/api.php - https://gist.github.com/ServOKio/0c08858a7400b2d759fb3fa216926fc6 + //Why ? Directly twitter returned "Connection reset" #RussianSanctionsAgainstTwitter + String defUrl = "https://cdn.syndication.twimg.com/tweet-result?lang=en&id="+matcher.group(1); + URL obj = new URL(settings.getBool("EnableProxy", false) ? addParameter(addParameter("http://p91878l7.beget.tech/api.php", "reg", "get_content"), "url", defUrl) : defUrl); + HttpURLConnection con = (HttpURLConnection) obj.openConnection(); + + con.setRequestMethod("GET"); + + con.setRequestProperty("cache-control", "max-age=0"); + con.setRequestProperty("upgrade-insecure-requests", "1"); + con.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0"); + con.setRequestProperty("sec-fetch-user:", "?1"); + con.setRequestProperty("accept-language:", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7"); + + con.setRequestProperty("Accept-Charset", "UTF-8"); + con.setDoOutput(true); + int responseCode = con.getResponseCode(); + if(responseCode == 200) { + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String line; + StringBuilder response = new StringBuilder(); + + while ((line = in.readLine()) != null) response.append(line); + in.close(); + + String txt = response.toString(); + JSONObject json = new JSONObject(txt); + + MessageEmbedBuilder embed = new MessageEmbedBuilder(EmbedType.VIDEO); + String poster = null; + String videoURL = null; + if (json.has("video")) { + if (json.getJSONObject("video").has("poster")) poster = json.getJSONObject("video").getString("poster"); + JSONArray a = json.getJSONObject("video").getJSONArray("variants"); + for (int i = 0; i < a.length(); i++) { + JSONObject va = a.getJSONObject(i); + if(va.getString("type").equals("video/mp4")) videoURL = va.getString("src"); + } + } else logger.debug(txt); + + if (videoURL != null || poster != null) { + if (videoURL == null) { + embed.setImage(poster); + } else embed.setVideo(videoURL); + msg.getEmbeds().add(embed.build()); + StoreStream.getMessages().handleMessageUpdate(msg.synthesizeApiMessage()); + } + } + } catch (Exception e) { + Utils.showToast("Failed to fix twitter video embed, message:\n"+e.getMessage()); + e.printStackTrace(); + } + }).start(); + } + } + }) + ); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private String addParameter(String URL, String name, String value) { + int qpos = URL.indexOf('?'); + int hpos = URL.indexOf('#'); + char sep = qpos == -1 ? '?' : '&'; + String seg = sep + encodeUrl(name) + '=' + encodeUrl(value); + return hpos == -1 ? URL + seg : URL.substring(0, hpos) + seg + + URL.substring(hpos); + } + + private String encodeUrl(String url) { + try { + return URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException uee) { + throw new IllegalArgumentException(uee); + } + } + + @Override + public void stop(Context context) { + patcher.unpatchAll(); + } +} + +/* +Copyright (C) Rhythm Lunatic 2021 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/