From 83f6345e55c89e47949be53eeaa68efbd616435a Mon Sep 17 00:00:00 2001 From: Ushie Date: Thu, 21 May 2026 05:23:18 +0300 Subject: [PATCH] feat: ArjixWasTaken --- .../com/aliucord/gradle/repo/Authors.kt | 1 + plugins/TextReact/build.gradle.kts | 13 ++ .../TextReact/src/main/kotlin/TextReact.kt | 133 ++++++++++++++++++ .../main/kotlin/textreact/PluginSettings.kt | 34 +++++ .../src/main/kotlin/textreact/TextToEmoji.kt | 127 +++++++++++++++++ 5 files changed, 308 insertions(+) create mode 100755 plugins/TextReact/build.gradle.kts create mode 100755 plugins/TextReact/src/main/kotlin/TextReact.kt create mode 100755 plugins/TextReact/src/main/kotlin/textreact/PluginSettings.kt create mode 100755 plugins/TextReact/src/main/kotlin/textreact/TextToEmoji.kt 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 86c515e..ddfbabd 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 ArjixWasTaken = Author("ArjixWasTaken", 674710789138939916L) } /** diff --git a/plugins/TextReact/build.gradle.kts b/plugins/TextReact/build.gradle.kts new file mode 100755 index 0000000..d43079a --- /dev/null +++ b/plugins/TextReact/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.41" +description = "A plugin that allows you to write text using reactions." + +aliucord { + author(Authors.ArjixWasTaken) +} diff --git a/plugins/TextReact/src/main/kotlin/TextReact.kt b/plugins/TextReact/src/main/kotlin/TextReact.kt new file mode 100755 index 0000000..546b8f2 --- /dev/null +++ b/plugins/TextReact/src/main/kotlin/TextReact.kt @@ -0,0 +1,133 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +import textreact.PluginSettings +import android.content.Context +import android.os.Bundle +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.widget.NestedScrollView +import com.aliucord.Constants +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.fragments.ConfirmDialog +import com.aliucord.fragments.InputDialog +import com.aliucord.patcher.Hook +import com.discord.databinding.WidgetChatListActionsBinding +import com.discord.models.domain.emoji.Emoji +import com.discord.stores.StoreStream +import com.discord.utilities.color.ColorCompat +import com.discord.widgets.chat.list.actions.WidgetChatListActions +import com.lytefast.flexinput.R +import textreact.TextToEmoji +import java.lang.reflect.InvocationTargetException + +@AliucordPlugin +class TextReact : Plugin() { + init { + settingsTab = SettingsTab(PluginSettings::class.java, SettingsTab.Type.BOTTOM_SHEET) + .withArgs(settings) + } + + override fun start(context: Context) { + val icon = ContextCompat.getDrawable(context, R.e.ic_keyboard_black_24dp) + val textReactId = View.generateViewId() + + with(WidgetChatListActions::class.java) { + val getBinding = getDeclaredMethod("getBinding").apply { isAccessible = true } + val addReaction = getDeclaredMethod("addReaction", Emoji::class.java).apply { isAccessible = true } + + patcher.patch( + getDeclaredMethod("configureUI", WidgetChatListActions.Model::class.java), + Hook { callFrame -> + try { + val binding = + getBinding.invoke(callFrame.thisObject) as WidgetChatListActionsBinding + val textReact = binding.a.findViewById(textReactId).apply { + visibility = + if ((callFrame.args[0] as WidgetChatListActions.Model).manageMessageContext.canAddReactions) View.VISIBLE else View.GONE + } + + if (!textReact.hasOnClickListeners()) textReact.setOnClickListener { + try { + val fragmentManager = + (callFrame.thisObject as WidgetChatListActions).parentFragmentManager + val inDialog = InputDialog() + .setTitle("Text React!") + .setDescription("Enter some text to send as reactions.") + .setPlaceholderText("Enter text...") + inDialog.setOnOkListener { + val result = TextToEmoji.generateEmojiArray(inDialog.input.toString().trim()) + inDialog.dismiss() + + if (result.second) { + // show prompt that the text is incomplete + val coDialog = ConfirmDialog() + .setTitle("Text React!") + .setDescription("Warning: The given input could not be 100% translated to reactions, do you still want to continue?") + coDialog.setOnOkListener { + coDialog.dismiss() + Utils.threadPool.execute { + result.first.forEach { emoji -> + addReaction.invoke( + callFrame.thisObject, + StoreStream.getEmojis().unicodeEmojiSurrogateMap[emoji]!! + ) + Thread.sleep(1000) + } + } + + } + coDialog.setOnCancelListener { + coDialog.dismiss() + Utils.showToast("Action cancelled by the user.") + } + coDialog.show(fragmentManager, "bbbbbb") + } else { + Utils.threadPool.execute { + result.first.forEach { emoji -> + addReaction.invoke( + callFrame.thisObject, + StoreStream.getEmojis().unicodeEmojiSurrogateMap[emoji]!! + ) + Thread.sleep(1000) + } + } + } + } + (callFrame.thisObject as WidgetChatListActions).dismiss() + inDialog.show(fragmentManager, "aaaaaa") + } catch (e: IllegalAccessException) { + e.printStackTrace() + } catch (e: InvocationTargetException) { + e.printStackTrace() + } + } + } catch (ignored: Throwable) { + } + }) + + patcher.patch( + getDeclaredMethod("onViewCreated", View::class.java, Bundle::class.java), + Hook { callFrame -> + val linearLayout = (callFrame.args[0] as NestedScrollView).getChildAt(0) as LinearLayout + val ctx = linearLayout.context + + icon?.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)) + + val textReact = TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon).apply { + text = "Text React" + id = textReactId + typeface = ResourcesCompat.getFont(ctx, Constants.Fonts.whitney_medium) + setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) + } + + linearLayout.addView(textReact, 1) + }) + } + } + override fun stop(context: Context) = patcher.unpatchAll() +} diff --git a/plugins/TextReact/src/main/kotlin/textreact/PluginSettings.kt b/plugins/TextReact/src/main/kotlin/textreact/PluginSettings.kt new file mode 100755 index 0000000..4c951d5 --- /dev/null +++ b/plugins/TextReact/src/main/kotlin/textreact/PluginSettings.kt @@ -0,0 +1,34 @@ +package textreact + +import android.os.Bundle +import android.view.View +import com.aliucord.PluginManager +import com.aliucord.Utils +import com.aliucord.api.SettingsAPI +import com.aliucord.widgets.BottomSheet +import com.discord.views.CheckedSetting + +@Suppress("MISSING_DEPENDENCY_SUPERCLASS") +class PluginSettings(private val settings: SettingsAPI) : BottomSheet() { + override fun onViewCreated(view: View, bundle: Bundle?) { + super.onViewCreated(view, bundle) + + val ctx = requireContext() + + fun createCheckedSetting(title: String, setting: String, checked: Boolean = true) { + return addView( + Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, null).apply { + isChecked = settings.getBool(setting, checked) + + setOnCheckedListener { + settings.setBool(setting, it) + PluginManager.stopPlugin("TextReact") + PluginManager.startPlugin("TextReact") + } + } + ) + } + + createCheckedSetting("Foo", "foo") + } +} \ No newline at end of file diff --git a/plugins/TextReact/src/main/kotlin/textreact/TextToEmoji.kt b/plugins/TextReact/src/main/kotlin/textreact/TextToEmoji.kt new file mode 100755 index 0000000..c40417c --- /dev/null +++ b/plugins/TextReact/src/main/kotlin/textreact/TextToEmoji.kt @@ -0,0 +1,127 @@ +package textreact + +// huge thanks to https://github.com/Juby210/text-react/blob/master/index.js + +class TextToEmoji { + companion object { + private data class ReactionsType( + val single: MutableMap>, + val multiple: MutableMap>, + ) + + private val reactions: ReactionsType = ReactionsType( + single=mutableMapOf( + "a" to arrayListOf("\uD83C\uDDE6", "\uD83C\uDD70"), + "b" to arrayListOf("\uD83C\uDDE7", "\uD83C\uDD71"), + "c" to arrayListOf("\uD83C\uDDE8", "©"), + "d" to arrayListOf("\uD83C\uDDE9"), + "e" to arrayListOf("\uD83C\uDDEA", "\uD83D\uDCE7", "🎼"), + "f" to arrayListOf("\uD83C\uDDEB"), + "g" to arrayListOf("\uD83C\uDDEC"), + "h" to arrayListOf("\uD83C\uDDED", "♓"), + "i" to arrayListOf("\uD83C\uDDEE", "ℹ"), + "j" to arrayListOf("\uD83C\uDDEF"), + "k" to arrayListOf("\uD83C\uDDF0"), + "l" to arrayListOf("\uD83C\uDDF1"), + "m" to arrayListOf("\uD83C\uDDF2", "Ⓜ", "♏", "♍"), + "n" to arrayListOf("\uD83C\uDDF3", "♑"), + "o" to arrayListOf("\uD83C\uDDF4", "\uD83C\uDD7E", "⭕"), + "p" to arrayListOf("\uD83C\uDDF5", "\uD83C\uDD7F"), + "q" to arrayListOf("\uD83C\uDDF6"), + "r" to arrayListOf("\uD83C\uDDF7", "®"), + "s" to arrayListOf("\uD83C\uDDF8"), + "t" to arrayListOf("\uD83C\uDDF9", "✝"), + "u" to arrayListOf("\uD83C\uDDFA"), + "v" to arrayListOf("\uD83C\uDDFB", "♈"), + "w" to arrayListOf("\uD83C\uDDFC"), + "x" to arrayListOf("\uD83C\uDDFD", "❎", "❌", "✖"), + "y" to arrayListOf("\uD83C\uDDFE"), + "z" to arrayListOf("\uD83C\uDDFF"), + "0" to arrayListOf("0️⃣"), + "1" to arrayListOf("1️⃣"), + "2" to arrayListOf("2️⃣"), + "3" to arrayListOf("3️⃣"), + "4" to arrayListOf("4️⃣"), + "5" to arrayListOf("5️⃣"), + "6" to arrayListOf("6️⃣"), + "7" to arrayListOf("7️⃣"), + "8" to arrayListOf("8️⃣"), + "9" to arrayListOf("9️⃣"), + "?" to arrayListOf("❔", "❓"), + "+" to arrayListOf("➕"), + "-" to arrayListOf("➖", "⛔", "\uD83D\uDCDB"), + "!" to arrayListOf("❕", "❗"), + "*" to arrayListOf("*️⃣"), + "$" to arrayListOf("\uD83D\uDCB2"), + "#" to arrayListOf("#️⃣"), + " " to arrayListOf("▪", "◾", "➖", "◼", "⬛", "⚫", "\uD83D\uDDA4", "\uD83D\uDD76") + ), + multiple=mutableMapOf( + "wc" to arrayListOf("\uD83D\uDEBE"), + "back" to arrayListOf("\uD83D\uDD19"), + "end" to arrayListOf("\uD83D\uDD1A"), + "on!" to arrayListOf("\uD83D\uDD1B"), + "soon" to arrayListOf("\uD83D\uDD1C"), + "top" to arrayListOf("\uD83D\uDD1D"), + "!!" to arrayListOf("‼"), + "!?" to arrayListOf("⁉"), + "tm" to arrayListOf("™"), + "10" to arrayListOf("\uD83D\uDD1F"), + "cl" to arrayListOf("\uD83C\uDD91"), + "cool" to arrayListOf("\uD83C\uDD92"), + "free" to arrayListOf("\uD83C\uDD93"), + "id" to arrayListOf("\uD83C\uDD94"), + "new" to arrayListOf("\uD83C\uDD95"), + "ng" to arrayListOf("\uD83C\uDD96"), + "ok" to arrayListOf("\uD83C\uDD97"), + "sos" to arrayListOf("\uD83C\uDD98"), + "up!" to arrayListOf("\uD83C\uDD99"), + "vs" to arrayListOf("\uD83C\uDD9A"), + "abc" to arrayListOf("\uD83D\uDD24"), + "ab" to arrayListOf("\uD83C\uDD8E"), + "18" to arrayListOf("\uD83D\uDD1E"), + "100" to arrayListOf("\uD83D\uDCAF"), + "atm" to arrayListOf("\uD83C\uDFE7") + ) + ) + + fun generateEmojiArray(string: String): Pair, Boolean> { + val unusedReactions = reactions + var msg = string.lowercase() + val newReactions = ArrayList() + + val allReactions = ArrayList((unusedReactions.single + unusedReactions.multiple).keys) + var incomplete = false + + while (msg.isNotEmpty()) { + if (!allReactions.contains(msg[0].toString())) { + msg = msg.slice(1 until msg.length) + incomplete = true + } + for (reactionName in unusedReactions.multiple.keys) { + if (msg.isNotEmpty() && msg.startsWith(reactionName) && unusedReactions.multiple[reactionName]!!.isNotEmpty()) { + val reactionValue = unusedReactions.multiple[reactionName]!!.first() + newReactions.add(reactionValue) + unusedReactions.multiple[reactionName] = ArrayList(unusedReactions.multiple[reactionName]!!.filter { value -> value != reactionValue }) + msg = msg.replace(reactionName, "") + } else if (msg.isNotEmpty() && msg.startsWith(reactionName) && unusedReactions.multiple[reactionName]!!.isEmpty()) { + msg = msg.replaceFirst(reactionName, "") + incomplete = true + } + } + for (reactionName in unusedReactions.single.keys) { + if (msg.isNotEmpty() && msg.startsWith(reactionName) && unusedReactions.single[reactionName]!!.isNotEmpty()) { + val reactionValue = unusedReactions.single[reactionName]!!.first() + newReactions.add(reactionValue) + unusedReactions.single[reactionName] = ArrayList(unusedReactions.single[reactionName]!!.filter { value -> value != reactionValue }) + msg = msg.replaceFirst(reactionName, "") + } else if (msg.isNotEmpty() && msg.startsWith(reactionName) && unusedReactions.single[reactionName]!!.isEmpty()) { + msg = msg.replaceFirst(reactionName, "") + incomplete = true + } + } + } + return Pair(newReactions, incomplete) + } + } +} \ No newline at end of file