From 13f33db243376ba109055b83bb85318f411c07db Mon Sep 17 00:00:00 2001 From: RazerTexz <66409416+RazerTexz@users.noreply.github.com> Date: Thu, 21 May 2026 09:45:43 +0700 Subject: [PATCH 1/2] feat: catsoft --- .../com/aliucord/gradle/repo/Authors.kt | 1 + .../CopyUrlInsteadOfShare/build.gradle.kts | 13 + .../CopyUrlInsteadOfShareMessages.kt | 131 ++++++ plugins/Find/build.gradle.kts | 13 + .../main/java/dev/nope/plugins/find/Find.kt | 147 +++++++ .../MoreMoreSlashCommands/build.gradle.kts | 22 + .../MoreMoreSlashCommands.kt | 409 ++++++++++++++++++ plugins/SearchHighlight/build.gradle.kts | 17 + .../searchhighlight/SearchHighlight.kt | 106 +++++ plugins/SnowflakeUtilities/build.gradle.kts | 19 + .../snowflakeutilities/SnowflakeUtilities.kt | 64 +++ plugins/Whois/build.gradle.kts | 18 + .../kotlin/dev/nope/plugins/whois/whois.kt | 189 ++++++++ 13 files changed, 1149 insertions(+) create mode 100644 plugins/CopyUrlInsteadOfShare/build.gradle.kts create mode 100644 plugins/CopyUrlInsteadOfShare/src/main/java/dev/nope/plugins/copyurlinsteadofshare/CopyUrlInsteadOfShareMessages.kt create mode 100644 plugins/Find/build.gradle.kts create mode 100644 plugins/Find/src/main/java/dev/nope/plugins/find/Find.kt create mode 100644 plugins/MoreMoreSlashCommands/build.gradle.kts create mode 100644 plugins/MoreMoreSlashCommands/src/main/java/dev/nope/plugins/moremoreslashcommands/MoreMoreSlashCommands.kt create mode 100644 plugins/SearchHighlight/build.gradle.kts create mode 100644 plugins/SearchHighlight/src/main/java/dev/nope/plugins/searchhighlight/SearchHighlight.kt create mode 100644 plugins/SnowflakeUtilities/build.gradle.kts create mode 100644 plugins/SnowflakeUtilities/src/main/java/dev/nope/plugins/snowflakeutilities/SnowflakeUtilities.kt create mode 100644 plugins/Whois/build.gradle.kts create mode 100644 plugins/Whois/src/main/kotlin/dev/nope/plugins/whois/whois.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 b8ed52b..72acdb6 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 @@ -56,6 +56,7 @@ object Authors { val Wing = Author("Wing", 298295889720770563L) val ArjixWasTaken = Author("ArjixWasTaken", 674710789138939916L) val PikachuGabe = Author("pikachugabe", 701095070870274139L) + val catsoft = Author("catsoft", 532967505438965780L) } /** diff --git a/plugins/CopyUrlInsteadOfShare/build.gradle.kts b/plugins/CopyUrlInsteadOfShare/build.gradle.kts new file mode 100644 index 0000000..d06f983 --- /dev/null +++ b/plugins/CopyUrlInsteadOfShare/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 = "Replaces share message function with a copy url one" + +aliucord { + author(Authors.catsoft) +} \ No newline at end of file diff --git a/plugins/CopyUrlInsteadOfShare/src/main/java/dev/nope/plugins/copyurlinsteadofshare/CopyUrlInsteadOfShareMessages.kt b/plugins/CopyUrlInsteadOfShare/src/main/java/dev/nope/plugins/copyurlinsteadofshare/CopyUrlInsteadOfShareMessages.kt new file mode 100644 index 0000000..9f2dc4c --- /dev/null +++ b/plugins/CopyUrlInsteadOfShare/src/main/java/dev/nope/plugins/copyurlinsteadofshare/CopyUrlInsteadOfShareMessages.kt @@ -0,0 +1,131 @@ +package dev.nope.plugins.copyurlinsteadofshare + +import android.annotation.SuppressLint +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.Utils.showToast +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.Hook +import com.discord.app.AppBottomSheet +import com.discord.databinding.WidgetChatListActionsBinding +import com.discord.utilities.color.ColorCompat +import com.discord.widgets.chat.list.actions.WidgetChatListActions +import com.lytefast.flexinput.R +import java.lang.reflect.InvocationTargetException + + +@AliucordPlugin(requiresRestart = false) +class MessageLinkContext : Plugin() { + + /* No settings tab for you because Cannot access 'com.discord.app.AppLogger$a' which is a supertype of 'dev.nope.plugins.copyurlinsteadofshare.HelpMeeee'. Check your module classpath for missing or conflicting dependencies + init { + settingsTab = SettingsTab( + Halp::class.java, + SettingsTab.Type.BOTTOM_SHEET + ).withArgs(settings) + } + */ + + @SuppressLint("SetTextI18n") + override fun start(context: Context) { + + val icon = ContextCompat.getDrawable(context, R.e.ic_diag_link_24dp)!! + .mutate() + + val copyMessageUrlViewId = View.generateViewId() + + with(WidgetChatListActions::class.java) { + val getBinding = getDeclaredMethod("getBinding").apply { isAccessible = true } + + val replaceShare = settings.getBool("replaceShare", true) + + patcher.patch( //creating the option + getDeclaredMethod("onViewCreated", View::class.java, Bundle::class.java), + Hook { callFrame -> + val shareMessagesViewId = Utils.getResId("dialog_chat_actions_share", "id") + val binding = + getBinding.invoke(callFrame.thisObject) as WidgetChatListActionsBinding + val shareMessageView = + binding.a.findViewById(shareMessagesViewId).apply { + visibility = View.VISIBLE + } + val linearLayout = + (callFrame.args[0] as NestedScrollView).getChildAt(0) as LinearLayout + val ctx = linearLayout.context + icon.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)) + val copyMessageUrl = + TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon).apply { + + text = ctx.getString(R.h.copy_link) + id = copyMessageUrlViewId + typeface = ResourcesCompat.getFont(ctx, Constants.Fonts.whitney_medium) + setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) + } + var replacementId = linearLayout.indexOfChild(shareMessageView) + linearLayout.removeView(shareMessageView) // poof + + linearLayout.addView( + copyMessageUrl, + replacementId + ) + }) + + patcher.patch( //setting onClickListener + getDeclaredMethod("configureUI", WidgetChatListActions.Model::class.java), + Hook { callFrame -> + try { + val binding = + getBinding.invoke(callFrame.thisObject) as WidgetChatListActionsBinding + val shareMessageView = + binding.a.findViewById(copyMessageUrlViewId).apply { + visibility = View.VISIBLE + } + + shareMessageView.setOnClickListener { + try { + val msg = (callFrame.args[0] as WidgetChatListActions.Model).message + val guild = + (callFrame.args[0] as WidgetChatListActions.Model).guild // because msg.guildId is null + val messageUri = String.format( + "https://discord.com/channels/%s/%s/%s", + try { + guild.id + } catch (e: Throwable) { // for DMs + "@me" + }, + msg.channelId, + msg.id + ) + Utils.setClipboard( + "message link", + messageUri + ) + showToast("Copied url", showLonger = false) + + val bottomSheetDismisser = + AppBottomSheet::class.java.getDeclaredMethod("dismiss") // because cannot access shit again + bottomSheetDismisser.invoke((callFrame.thisObject as WidgetChatListActions)) + } catch (e: IllegalAccessException) { + e.printStackTrace() + } catch (e: InvocationTargetException) { + e.printStackTrace() + } + } + } catch (e: Exception) { //yes generic maybe works idk + e.printStackTrace() + } + }) + } + } + + override fun stop(context: Context) = patcher.unpatchAll() +} diff --git a/plugins/Find/build.gradle.kts b/plugins/Find/build.gradle.kts new file mode 100644 index 0000000..eb55cfa --- /dev/null +++ b/plugins/Find/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.4" +description = "/find command" + +aliucord { + author(Authors.catsoft) +} \ No newline at end of file diff --git a/plugins/Find/src/main/java/dev/nope/plugins/find/Find.kt b/plugins/Find/src/main/java/dev/nope/plugins/find/Find.kt new file mode 100644 index 0000000..ae88902 --- /dev/null +++ b/plugins/Find/src/main/java/dev/nope/plugins/find/Find.kt @@ -0,0 +1,147 @@ +package dev.nope.plugins.find + +import android.content.Context +import com.aliucord.Http +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.CommandsAPI.CommandResult +import com.aliucord.entities.Plugin +import com.aliucord.utils.RxUtils.await +import com.aliucord.wrappers.ChannelWrapper.Companion.guildId +import com.aliucord.wrappers.ChannelWrapper.Companion.parentId +import com.discord.api.commands.ApplicationCommandType +import com.discord.models.user.User +import com.discord.stores.StoreStream +import com.discord.utilities.rest.RestAPI +import com.discord.api.user.UserProfile +import com.discord.models.user.CoreUser + + +@AliucordPlugin(requiresRestart = false) +class Find : Plugin() { + + private fun timestampToUnixTime(x: Long): Long { + val discordEpoch = 1420070400000 + val dateBits = x shr 22 + val unixTimes1000 = (dateBits + discordEpoch) + return unixTimes1000 / 1000 + } + + override fun start(context: Context) { + + commands.registerCommand( + "find", "Tries fo find what a timestamp or a list of timestamps refers to", listOf( + Utils.createCommandOption( + ApplicationCommandType.STRING, + "timestampList", + "Timestamps you want answers on. Separate with simple spaces.", + null, + required = true, + default = true + ) + ) + ) { ctx -> + val ids = ctx.getRequiredString("timestampList") + val input = findStringtoList(ids) + val results: MutableMap = mutableMapOf() + + input.forEach { + StoreStream.getUsers() + Utils + val colit: Collection = listOf(it) + val tempUser: User? = StoreStream.getUsers().getUsers(colit, false)[it] + val tempChannel = StoreStream.getChannels().getChannel(it) + val tempServer = StoreStream.getGuilds().getGuild(it) + // val tempMessage = StoreStream.getMessages().getMe i need to test every channel lolilol + try { + if (tempUser?.username == null) { + if (tempChannel?.guildId == null) { + if (tempServer?.id == null) { + results.put(it, "is neither a user, a channel nor guild ID, or is not cached.") + } else { + results.put(it, "is a server.\nName: ${tempServer.name}.") + } + } else { + results.put( + it, + "is the channel: <#${it}> in category ${tempChannel.parentId} in server ${ + StoreStream.getGuilds().getGuild(tempChannel.guildId).name + }." + ) + } + } else { + results.put(it, "is the user <@$it>.") + } + } catch (throwable: Throwable) { + return@registerCommand CommandResult( + "oopsie doopsie, an error happened. Sorry ! Please check the logs !", + null, + false + ) + } + + +// welp i want to avoid making requests like that so i have to see what to do with the uncached things + if (false && results[it] == "is neither a user, a channel nor guild ID.") { //Checks if the user exists. + try { + + val directuser = RestAPI.api.userGet(it).await().first ?: return@forEach + val userinfo = + UserProfile(null, null, directuser, null, null, null, null) + val user = CoreUser(userinfo.g()) + results[it] = + "is a user that is not cached. Name: ${user.username}#${user.discriminator}, created on . Avatar id: ${user.avatar}" + + + } catch (throwable: Throwable) { + return@forEach + } + + } + } + + var finalList = "" + results.forEach { (t, u) -> finalList += "\n**$t** $u" } + + CommandResult( + finalList, + null, + false + ) + } + } + + + private fun findStringtoList(ids: String): MutableList { + val result = ids.split(" ").map { it.trim() } + val result2: MutableList = result as MutableList + val result3: MutableList = mutableListOf() + val counter = 0 + result.forEach { + try { + it.toLong().takeIf { that -> that.toString().length in 17..19 } ?: (result2.set( + result.indexOf(it), + "0" + )) + } catch (throwable: Throwable) { + result2[result.indexOf(it)] = "0" + } + } + result2.forEach { + if (it.toLong() != 0L) { + result3.add(it.toLong()) + } else { + counter + 1 + } + + } + return result3 + } + + override fun stop(context: Context) { + // Unregister our commands + commands.unregisterAll() + } +} diff --git a/plugins/MoreMoreSlashCommands/build.gradle.kts b/plugins/MoreMoreSlashCommands/build.gradle.kts new file mode 100644 index 0000000..d0eabdd --- /dev/null +++ b/plugins/MoreMoreSlashCommands/build.gradle.kts @@ -0,0 +1,22 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.2" +description = "Adds a few charcter modification slash commands" + +aliucord { + changelog.set(""" + #1.0.2 + fixed morse translation issues + # 1.0.1 + * Now with morse, bold, and small yay + # 1.0.0 + * Initial release + """.trimIndent()) + + author(Authors.catsoft) +} \ No newline at end of file diff --git a/plugins/MoreMoreSlashCommands/src/main/java/dev/nope/plugins/moremoreslashcommands/MoreMoreSlashCommands.kt b/plugins/MoreMoreSlashCommands/src/main/java/dev/nope/plugins/moremoreslashcommands/MoreMoreSlashCommands.kt new file mode 100644 index 0000000..4dab286 --- /dev/null +++ b/plugins/MoreMoreSlashCommands/src/main/java/dev/nope/plugins/moremoreslashcommands/MoreMoreSlashCommands.kt @@ -0,0 +1,409 @@ +package dev.nope.plugins.moremoreslashcommands +/* + * Copyright (c) 2022 nope and the CutTheCord project on gitdab + * Licensed under the Open Software License version 3.0 + */ + + +import android.content.Context +import android.text.style.SubscriptSpan +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.CommandsAPI +import com.aliucord.entities.Plugin + +@AliucordPlugin( + requiresRestart = false, // Whether your plugin requires a restart after being installed/updated +) +@Suppress("unused") +class MoreSlashCommands : Plugin() { + override fun start(context: Context) { + commands.registerCommand("fw", "Makes text full width ([a-Z][0-9][!-~])", listOf(CommandsAPI.requiredMessageOption)) { ctx -> + CommandsAPI.CommandResult(fullwidthify(ctx.getRequiredString("message").trim())) + } + + commands.registerCommand("flip", "Flips characters ([a-z])", listOf(CommandsAPI.requiredMessageOption)) { ctx -> + CommandsAPI.CommandResult(flipify(ctx.getRequiredString("message").trim())) + } + commands.registerCommand("morse", "Makes your text morse([a-Z][0-9][.-=])", listOf(CommandsAPI.requiredMessageOption)) { ctx -> + CommandsAPI.CommandResult(morseify(ctx.getRequiredString("message").trim())) + } + + commands.registerCommand("unmorse", "decrypts morse text", listOf(CommandsAPI.requiredMessageOption)) { ctx -> + CommandsAPI.CommandResult(unmorseify(ctx.getRequiredString("message").trim()), null, false) + } //maybe an activable option to put it in message menu would be better ? idk. May make every single command activable + + commands.registerCommand("bolder", "Makes your text more bold ([a-Z][0-9])", listOf(CommandsAPI.requiredMessageOption)) { ctx -> + CommandsAPI.CommandResult(bolderify(ctx.getRequiredString("message").trim())) + } + + commands.registerCommand("small", "Makes your text more small ([a-z])", listOf(CommandsAPI.requiredMessageOption)) { ctx -> + CommandsAPI.CommandResult(smallify(ctx.getRequiredString("message").trim())) + } + commands.registerCommand("smaller", "Makes your text more more small ([a-z])", listOf(CommandsAPI.requiredMessageOption)) { ctx -> + CommandsAPI.CommandResult(smallerify(ctx.getRequiredString("message").trim())) + } + + + } + + + + override fun stop(context: Context) = commands.unregisterAll() + + + private fun fullwidthify(text: String): String { + return text + .replace(" ", " ") + .replace("!", "!") + .replace("#", "#") + .replace("$", "$") + .replace("%", "%") + .replace("&", "&") + .replace("'", "'") + .replace("(", "(") + .replace(")", ")") + .replace("*", "*") + .replace("+", "+") + .replace(",", ",") + .replace("-", "-") + .replace(".", ".") + .replace("/", "/") + .replace("0", "0") + .replace("1", "1") + .replace("2", "2") + .replace("3", "3") + .replace("4", "4") + .replace("5", "5") + .replace("6", "6") + .replace("7", "7") + .replace("8", "8") + .replace("9", "9") + .replace(":", ":") + .replace(";", ";") + .replace("<", "<") + .replace("=", "=") + .replace(">", ">") + .replace("?", "?") + .replace("@", "@") + .replace("A", "A") + .replace("B", "B") + .replace("C", "C") + .replace("D", "D") + .replace("E", "E") + .replace("F", "F") + .replace("G", "G") + .replace("H", "H") + .replace("I", "I") + .replace("J", "J") + .replace("K", "K") + .replace("L", "L") + .replace("M", "M") + .replace("N", "N") + .replace("O", "O") + .replace("P", "P") + .replace("Q", "Q") + .replace("R", "R") + .replace("S", "S") + .replace("T", "T") + .replace("U", "U") + .replace("V", "V") + .replace("W", "W") + .replace("X", "X") + .replace("Y", "Y") + .replace("Z", "Z") + .replace("[", "[") + .replace("]", "]") + .replace("^", "^") + .replace("_", "_") + .replace("`", "`") + .replace("a", "a") + .replace("b", "b") + .replace("c", "c") + .replace("d", "d") + .replace("e", "e") + .replace("f", "f") + .replace("g", "g") + .replace("h", "h") + .replace("i", "i") + .replace("j", "j") + .replace("k", "k") + .replace("l", "l") + .replace("m", "m") + .replace("n", "n") + .replace("o", "o") + .replace("p", "p") + .replace("q", "q") + .replace("r", "r") + .replace("s", "s") + .replace("t", "t") + .replace("u", "u") + .replace("v", "v") + .replace("w", "w") + .replace("x", "x") + .replace("y", "y") + .replace("z", "z") + .replace("{", "{") + .replace("|", "|") + .replace("}", "}") + .replace("~", "~") + + + + } + + private fun flipify(text :String): String { + return text.toLowerCase() + + + .replace("a", "ɐ") + .replace("b", "q") + .replace("c", "ɔ") + .replace("d", "p") + .replace("e", "ǝ") + .replace("f", "ɟ") + .replace("g", "ƃ") + .replace("h", "ɥ") + .replace("i", "ı") + .replace("j", "ɾ") + .replace("k", "ʞ") + .replace("l", "ן") + .replace("m", "ɯ") + .replace("n", "u") + .replace("p", "d") + .replace("q", "b") + .replace("r", "ɹ") + .replace("t", "ʇ") + .replace("u", "n") + .replace("v", "ʌ") + .replace("w", "ʍ") + .replace("y", "ʎ") + } + + private fun morseify(text: String): String { + return text.toUpperCase() + .replace(" ", "/ ") + .replace(".", ".-.-.- ") + .replace(",", "--..-- ") + .replace(":", "---... ") + .replace("?", "..--.. ") + .replace("'", ".----. ") + // .replace("-", "-....- ") user should not use - + .replace("/", "-..-. ") + .replace("@", ".--.-. ") + .replace("=", "-...- ") + .replace("A", ".- ") + .replace("B", "-... ") + .replace("C", "-.-. ") + .replace("D", "-.. ") + .replace("E", ". ") + .replace("F", "..-. ") + .replace("G", "--. ") + .replace("H", ".... ") + .replace("I", ".. ") + .replace("J", ".--- ") + .replace("K", "-.- ") + .replace("L", ".-.. ") + .replace("M", "-- ") + .replace("N", "-. ") + .replace("O", "--- ") + .replace("P", ".--. ") + .replace("Q", "--.- ") + .replace("R", ".-. ") + .replace("S", "... ") + .replace("T", "- ") + .replace("U", "..- ") + .replace("V", "...- ") + .replace("W", ".-- ") + .replace("X", "-..- ") + .replace("Y", "-.-- ") + .replace("Z", "--.. ") + .replace("0", "----- ") + .replace("1", ".---- ") + .replace("2", "..--- ") + .replace("3", "...-- ") + .replace("4", "....- ") + .replace("5", "..... ") + .replace("6", "-.... ") + .replace("7", "--... ") + .replace("8", "---.. ") + .replace("9", "----. ") + } + + private fun unmorseify(text: String): String { + return ("$text ") + .replace("..--.. ", "?") + .replace(".-.-.- ", ".") + .replace(".--.-. ", "@") + .replace(".----. ", "'") + .replace("--..-- ", ",") + .replace("---... ", ":") + .replace("..... ", "5") + .replace("....- ", "4") + .replace("...-- ", "3") + .replace("..--- ", "2") + .replace(".---- ", "1") + .replace("-.... ", "6") + .replace("-...- ", "=") + .replace("-..-. ", "/") + .replace("--... ", "7") + .replace("---.. ", "8") + .replace("----. ", "9") + .replace("----- ", "0") + .replace(".... ", "H") + .replace("...- ", "V") + .replace("..-. ", "F") + .replace(".-.. ", "L") + .replace(".--. ", "P") + .replace(".--- ", "J") + .replace("-... ", "B") + .replace("-..- ", "X") + .replace("-.-. ", "C") + .replace("-.-- ", "Y") + .replace("--.. ", "Z") + .replace("--.- ", "Q") + .replace("... ", "S") + .replace("..- ", "U") + .replace(".-. ", "R") + .replace(".-- ", "W") + .replace("-.. ", "D") + .replace("-.- ", "K") + .replace("--. ", "G") + .replace("--- ", "O") + .replace(".. ", "I") + .replace(".- ", "A") + .replace("-. ", "N") + .replace("-- ", "M") + .replace(". ", "E") + .replace("- ", "T") + .replace("/ ", " ") + } + + private fun bolderify(text: String): String { + return text + .replace("a", "𝗮") + .replace("b", "𝗯") + .replace("c", "𝗰") + .replace("d", "𝗱") + .replace("e", "𝗲") + .replace("f", "𝗳") + .replace("g", "𝗴") + .replace("h", "𝗵") + .replace("i", "𝗶") + .replace("j", "𝗷") + .replace("k", "𝗸") + .replace("l", "𝗹") + .replace("m", "𝗺") + .replace("n", "𝗻") + .replace("o", "𝗼") + .replace("p", "𝗽") + .replace("q", "𝗾") + .replace("r", "𝗿") + .replace("s", "𝘀") + .replace("t", "𝘁") + .replace("u", "𝘂") + .replace("v", "𝘃") + .replace("w", "𝘄") + .replace("x", "𝘅") + .replace("y", "𝘆") + .replace("z", "𝘇") + .replace("A", "𝗔") + .replace("B", "𝗕") + .replace("C", "𝗖") + .replace("D", "𝗗") + .replace("E", "𝗘") + .replace("F", "𝗙") + .replace("G", "𝗚") + .replace("H", "𝗛") + .replace("I", "𝗜") + .replace("J", "𝗝") + .replace("K", "𝗞") + .replace("L", "𝗟") + .replace("M", "𝗠") + .replace("N", "𝗡") + .replace("O", "𝗢") + .replace("P", "𝗣") + .replace("Q", "𝗤") + .replace("R", "𝗥") + .replace("S", "𝗦") + .replace("T", "𝗧") + .replace("U", "𝗨") + .replace("V", "𝗩") + .replace("W", "𝗪") + .replace("X", "𝗫") + .replace("Y", "𝗬") + .replace("Z", "𝗭") + .replace("0", "𝟬") + .replace("1", "𝟭") + .replace("2", "𝟮") + .replace("3", "𝟯") + .replace("4", "𝟰") + .replace("5", "𝟱") + .replace("6", "𝟲") + .replace("7", "𝟳") + .replace("8", "𝟴") + .replace("9", "𝟵") + } + + private fun smallify(text: String): String { + return text.toLowerCase() + .replace("a", "ᴀ") + .replace("b", "ʙ") + .replace("c", "ᴄ") + .replace("d", "ᴅ") + .replace("e", "ᴇ") + .replace("f", "ꜰ") + .replace("g", "ɢ") + .replace("h", "ʜ") + .replace("i", "ɪ") + .replace("j", "ᴊ") + .replace("k", "ᴋ") + .replace("l", "ʟ") + .replace("m", "ᴍ") + .replace("n", "ɴ") + .replace("o", "ᴏ") + .replace("p", "ᴘ") + .replace("q", "ǫ") + .replace("r", "ʀ") + .replace("t", "ᴛ") + .replace("u", "ᴜ") + .replace("v", "ᴠ") + .replace("w", "ᴡ") + .replace("y", "ʏ") + .replace("z", "ᴢ") + } + + private fun smallerify(text: String): String { + return text + .replace("a", "ᵃ") + .replace("b", "ᵇ") + .replace("c", "ᶜ") + .replace("d", "ᵈ") + .replace("e", "ᵉ") + .replace("f", "ᶠ") + .replace("g", "ᵍ") + .replace("h", "ʰ") + .replace("i", "ᶦ") + .replace("j", "ʲ") + .replace("k", "ᵏ") + .replace("l", "ˡ") + .replace("m", "ᵐ") + .replace("n", "ⁿ") + .replace("o", "ᵒ") + .replace("p", "ᵖ") + .replace("q", "ᑫ") + .replace("r", "ʳ") + .replace("s", "ˢ") + .replace("t", "ᵗ") + .replace("u", "ᵘ") + .replace("v", "ᵛ") + .replace("w", "ʷ") + .replace("x", "ˣ") + .replace("y", "ʸ") + .replace("z", "ᶻ") + + + } + + + + } diff --git a/plugins/SearchHighlight/build.gradle.kts b/plugins/SearchHighlight/build.gradle.kts new file mode 100644 index 0000000..38dca13 --- /dev/null +++ b/plugins/SearchHighlight/build.gradle.kts @@ -0,0 +1,17 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "0.0.1" +description = "Highlights search terms in message search results" + +aliucord { + changelog.set(""" + * initial release + """.trimIndent()) + + author(Authors.catsoft) +} \ No newline at end of file diff --git a/plugins/SearchHighlight/src/main/java/dev/nope/plugins/searchhighlight/SearchHighlight.kt b/plugins/SearchHighlight/src/main/java/dev/nope/plugins/searchhighlight/SearchHighlight.kt new file mode 100644 index 0000000..408f9c6 --- /dev/null +++ b/plugins/SearchHighlight/src/main/java/dev/nope/plugins/searchhighlight/SearchHighlight.kt @@ -0,0 +1,106 @@ +package dev.nope.plugins.searchhighlight + +import android.content.Context +import android.graphics.Color +import android.text.Spannable +import android.text.Spanned +import android.text.style.BackgroundColorSpan +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.after +import com.discord.stores.StoreSearchQuery +import com.discord.stores.StoreStream +import com.discord.utilities.view.text.SimpleDraweeSpanTextView +import com.discord.widgets.chat.list.adapter.WidgetChatListAdapter +import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage +import com.discord.widgets.chat.list.entries.ChatListEntry +import com.discord.widgets.search.results.WidgetSearchResults + +@AliucordPlugin(requiresRestart = false) +@Suppress("unused") +class SearchHighlight : Plugin() { + + override fun start(context: Context) { + val itemTextField = WidgetChatListAdapterItemMessage::class.java + .getDeclaredField("itemText") + .apply { isAccessible = true } + + val currentSearchStateField = StoreSearchQuery::class.java + .getDeclaredField("currentSearchState") + .apply { isAccessible = true } + + patcher.after( + "onConfigure", + Int::class.java, + ChatListEntry::class.java, + ) { + // Check if we're in a search results context + val chatAdapter = adapter ?: return@after + if (chatAdapter.data !is WidgetSearchResults.Model) return@after + + // Get the message text view + val textView = itemTextField.get(this) as? SimpleDraweeSpanTextView ?: return@after + val text = textView.text as? Spannable ?: return@after + if (text.isEmpty()) return@after + + // Get current search terms from the store + val storeSearchQuery = StoreStream.getSearch().storeSearchQuery + val searchState = currentSearchStateField.get(storeSearchQuery) + ?: return@after + val query = com.discord.utilities.search.network.state.SearchState::class.java + .getMethod("getSearchQuery") + .invoke(searchState) + ?: return@after + val params = com.discord.utilities.search.network.SearchQuery::class.java + .getMethod("getParams") + .invoke(query) as? Map<*, *> + ?: return@after + + @Suppress("UNCHECKED_CAST") + val contentTerms = params["content"] as? java.util.List<*> ?: return@after + + // Use only Java APIs to avoid Kotlin stdlib compat issues + val terms = java.util.ArrayList() + val iter = contentTerms.iterator() + while (iter.hasNext()) { + val entry = iter.next()?.toString() ?: continue + val parts = (entry as java.lang.String).split(" ") + var i = 0 + while (i < parts.size) { + val part = parts[i].trim() + if (part.length > 0) terms.add(part) + i++ + } + } + if (terms.size == 0) return@after + + highlightTerms(text, terms) + } + } + + private fun highlightTerms(text: Spannable, terms: java.util.ArrayList) { + // Semi-transparent yellow/gold highlight that works on dark backgrounds + val highlightColor = Color.argb(100, 255, 200, 0) + val content = text.toString().lowercase(java.util.Locale.ROOT) + + var i = 0 + while (i < terms.size) { + val lowerTerm = terms[i].lowercase(java.util.Locale.ROOT) + var start = 0 + while (true) { + val index = content.indexOf(lowerTerm, start) + if (index < 0) break + text.setSpan( + BackgroundColorSpan(highlightColor), + index, + index + lowerTerm.length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, + ) + start = index + lowerTerm.length + } + i++ + } + } + + override fun stop(context: Context) = patcher.unpatchAll() +} diff --git a/plugins/SnowflakeUtilities/build.gradle.kts b/plugins/SnowflakeUtilities/build.gradle.kts new file mode 100644 index 0000000..1f93129 --- /dev/null +++ b/plugins/SnowflakeUtilities/build.gradle.kts @@ -0,0 +1,19 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.6" +description = "Adds a /snowflake command to translate discord snowflakes to readable date and time" + +aliucord { + changelog.set(""" + # 1.0.6 Changed the /timestamp to /snowflake because conflicted with existing plugin + # 1.0.4 + * Created a separate plugin for Whois + """.trimIndent()) + + author(Authors.catsoft) +} \ No newline at end of file diff --git a/plugins/SnowflakeUtilities/src/main/java/dev/nope/plugins/snowflakeutilities/SnowflakeUtilities.kt b/plugins/SnowflakeUtilities/src/main/java/dev/nope/plugins/snowflakeutilities/SnowflakeUtilities.kt new file mode 100644 index 0000000..550d1b7 --- /dev/null +++ b/plugins/SnowflakeUtilities/src/main/java/dev/nope/plugins/snowflakeutilities/SnowflakeUtilities.kt @@ -0,0 +1,64 @@ +package dev.nope.plugins.snowflakeutilities + +import android.content.Context +import com.aliucord.Http +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.CommandsAPI +import com.aliucord.api.CommandsAPI.CommandResult +import com.aliucord.entities.MessageEmbedBuilder +import com.aliucord.entities.Plugin +import com.discord.api.commands.ApplicationCommandType +import java.util.* + + +@AliucordPlugin(requiresRestart = false ) +class SnowflakeUtilities : Plugin() { + + + private fun timestampToUnixTime(x: Long): Long { + val discordEpoch = 1420070400000 + val dateBits = x shr 22 + val unixTimes1000 = (dateBits + discordEpoch) + return unixTimes1000 / 1000 + // val time = Date(unix) less precise and adapting to timezones is harder + + } + + override fun start(context: Context) { + + + commands.registerCommand( + "snowflake", "Converts discord ID to date", listOf( + Utils.createCommandOption( + ApplicationCommandType.STRING, + "snowflake", + "The snowflake to convert", + null, + required = true, + default = true + ) + ) + ) { ctx -> + val id = ctx.getRequiredString("snowflake") + val unixTime = timestampToUnixTime(id.toLong()) + + + CommandsAPI.CommandResult( + "Message/User was created on ().", + null, + false + ) + + } + + + } + + override fun stop(context: Context) { + // Unregister our commands + commands.unregisterAll() + } + + +} diff --git a/plugins/Whois/build.gradle.kts b/plugins/Whois/build.gradle.kts new file mode 100644 index 0000000..0894926 --- /dev/null +++ b/plugins/Whois/build.gradle.kts @@ -0,0 +1,18 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.0" +description = "Adds a /whois that inputs a mention or a userid and returns the most info it can test about that user" + +aliucord { + changelog.set(""" + # 1.0.0 + * Initial release. Adds /fw (fullwidth) + """.trimIndent()) + + author(Authors.catsoft) +} \ No newline at end of file diff --git a/plugins/Whois/src/main/kotlin/dev/nope/plugins/whois/whois.kt b/plugins/Whois/src/main/kotlin/dev/nope/plugins/whois/whois.kt new file mode 100644 index 0000000..16d6111 --- /dev/null +++ b/plugins/Whois/src/main/kotlin/dev/nope/plugins/whois/whois.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 Vendicated & nope + * + * 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 dev.nope.plugins.whois + +import android.content.Context +import android.graphics.Color +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.CommandsAPI +import com.aliucord.entities.MessageEmbedBuilder +import com.aliucord.entities.Plugin +import com.aliucord.utils.RxUtils.await +import com.aliucord.wrappers.GuildRoleWrapper.Companion.permissions +import com.discord.api.commands.ApplicationCommandType +import com.discord.api.connectedaccounts.ConnectedAccount +import com.discord.api.permission.Permission +import com.discord.api.role.GuildRole +import com.discord.api.user.UserFlags +import com.discord.api.user.UserProfile +import com.discord.models.member.GuildMember +import com.discord.models.user.CoreUser +import com.discord.stores.StoreStream +import com.discord.utilities.SnowflakeUtils +import com.discord.utilities.icon.IconUtils +import com.discord.utilities.rest.RestAPI +import com.discord.utilities.time.ClockFactory +import com.discord.utilities.time.TimeUtils +import com.discord.utilities.user.UserProfileUtilsKt +import com.discord.utilities.user.UserUtils +import java.util.* +import java.util.Collections.emptyMap + +fun Long.snowflakeToDateString() = SnowflakeUtils.toTimestamp(this).toDateString() +fun Long.toDateString(): String = TimeUtils.INSTANCE.toReadableTimeStringEN(Locale.ENGLISH, this, ClockFactory.get()) + +@AliucordPlugin( + requiresRestart = false, // Whether your plugin requires a restart after being installed/updated +) +@Suppress("unused") +class UserLookup : Plugin() { + private val flagToEmoji = mapOf( + UserFlags.BUG_HUNTER_LEVEL_1 to "<:bughunter:585765206769139723>", + UserFlags.BUG_HUNTER_LEVEL_2 to "<:goldbughunter:853274684337946648>", + UserFlags.CERTIFIED_MODERATOR to "<:certifiedmod:853274382339670046>", + UserFlags.HYPESQUAD_HOUSE1 to "<:bravery:585763004218343426>", + UserFlags.HYPESQUAD_HOUSE2 to "<:brilliance:585763004495298575>", + UserFlags.HYPESQUAD_HOUSE3 to "<:balance:585763004574859273>", + UserFlags.PARTNER to "<:partnernew:754032603081998336>", + UserFlags.STAFF to "<:stafftools:314348604095594498>", + UserFlags.VERIFIED_DEVELOPER to "<:verifiedbotdev:853277205264859156>", + UserFlags.PREMIUM_EARLY_SUPPORTER to "<:supporter:585763690868113455>", + ) + + override fun start(_ctx: Context) { + val mentionOrId = arrayListOf( + Utils.createCommandOption( + ApplicationCommandType.SUBCOMMAND, "user-id", "Look up a user by ID", subCommandOptions = arrayListOf( + Utils.createCommandOption( + ApplicationCommandType.STRING, "id", "The id of the user to look up", required = true + ) + ) + ), Utils.createCommandOption( + ApplicationCommandType.SUBCOMMAND, "mention", "Get info on a user by mention", subCommandOptions = arrayListOf( + Utils.createCommandOption( + ApplicationCommandType.USER, "user", "The user", required = true + ) + ) + ) + ) + + commands.registerCommand("whois", "Look up a user", mentionOrId) { ctx -> + val userId = when { + ctx.containsArg("user-id") -> ctx.getRequiredSubCommandArgs("user-id")["id"] + else -> ctx.getRequiredSubCommandArgs("mention")["user"] + }.let { + (it as String).toLongOrNull().takeIf { _ -> it.length in 17..19 } ?: return@registerCommand fail("Invalid input: $it") + } + val profile = getUserProfile(userId, ctx.currentChannel.guildId) ?: run { + val user = RestAPI.api.userGet(userId).await().first ?: return@registerCommand fail("No such user") + UserProfile(emptyList(), emptyList(), user, null, null, null, null) // this is kind of shit + } + val user = CoreUser(profile.g()) + + + val guildMember : GuildMember? = profile.c()?.let { GuildMember.Companion.`from$default`(GuildMember.Companion, it, ctx.currentChannel.guildId, emptyMap(), StoreStream.getGuilds(), 0, null) } + val embed = MessageEmbedBuilder().run { + setAuthor( + "${if (user.isBot || user.isSystemUser) "\uD83E\uDD16 " else ""}${user.username}${ + UserUtils.INSTANCE.getDiscriminatorWithPadding( + user + ) + }" + ) + IconUtils.INSTANCE.getForGuildMemberOrUser( + user, guildMember, 512, true + ).let { + setThumbnail(it, it, 512, 512) + } + + user.bannerColor?.let { setColor(Color.parseColor(it)) } + user.banner?.let { hash -> + val icon = if (guildMember?.hasBanner() == true) IconUtils.INSTANCE.getForGuildMemberBanner( + guildMember.bannerHash, ctx.currentChannel.guildId, user.id, 2048, true + ) + else IconUtils.INSTANCE.getForUserBanner(user.id, hash, 2048, true) + setImage(icon, icon, 818, 2048) + } + setDescription(user.bio) + addField( + "Created At", user.id.snowflakeToDateString(), false + ) + guildMember?.let { + addField("Joined At", it.joinedAt.g().toDateString(), true) + addField("Colour", "#${it.color.toString(16)}", true) + + addField("Permissions", getPermissions(it.roles, ctx.currentChannel.guildId), true) + } + getBadgeEmojis(user.flags, profile).run { + if (isNotEmpty()) addField("Badges", this, true) + } + if (profile.d().isNotEmpty()) { + val guilds = StoreStream.getGuilds().guilds + val mutualGuilds = profile.d() + addField("Mutual Servers (${mutualGuilds.size})", mutualGuilds.joinToString("\n") { "• ${guilds[it.a()]!!.name}" }, true) + } + listOf(this.build()) + } + CommandsAPI.CommandResult(null, embed, false) + } + + + } + + override fun stop(context: Context) { + patcher.unpatchAll() + commands.unregisterAll() + } + + private fun getUserProfile(id: Long, guildId: Long?) = RestAPI.api.userProfileGet(id, true, guildId).await().first + + private fun getBadgeEmojis(flags: Int, profile: UserProfile) = StringBuilder().run { + flagToEmoji.forEach { (flag, emoji) -> + if (flags and flag != 0) append(emoji).append(' ') + } + if (UserProfileUtilsKt.isPremium(profile)) append("<:nitro:314068430611415041> ") + UserProfileUtilsKt.getGuildBoostMonthsSubscribed(profile)?.let { + append( + when (it) { + 0 -> "<:NitroBoost:699715144862662666>" + 1 -> "<:booster:585764032162562058>" + 2 -> "<:booster2:585764446253744128>" + 3 -> "<:booster3:585764446220189716>" + else -> "<:booster4:585764446178246657>" + } + ) + } + toString().trimEnd() + } + + @Suppress("NOTHING_TO_INLINE") + private inline fun fail(msg: String) = CommandsAPI.CommandResult(msg, null, false) + + private fun getPermissions(roleIds: List, guildId: Long): String { + val roles = StoreStream.getGuilds().roles[guildId]!! + val perms = roleIds.fold(roles[guildId]!!.permissions) { acc, curr -> + acc or roles[curr]!!.permissions + } + val re = "_\\w".toRegex() + return Permission::class.java.declaredFields.mapNotNull { f -> + when (f.name) { + "DEFAULT", "ALL", "NONE", "ELEVATED", "MODERATOR_PERMISSIONS", "MANAGEMENT_PERMISSIONS" -> return@mapNotNull null + } + if (f.type == Long::class.java && perms and f.get(null) as Long != 0L) + f.name.lowercase() + .replaceFirstChar { it.uppercase() } + .replace(re) { " ${it.value[1].uppercase()}" } + else null + }.joinToString(", ") + } + +} + From 29a67b276f72a7f19b3ebb9527e2185f0681ad28 Mon Sep 17 00:00:00 2001 From: RazerTexz <66409416+RazerTexz@users.noreply.github.com> Date: Thu, 21 May 2026 09:46:05 +0700 Subject: [PATCH 2/2] feat: catsoft