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 .
+*/