From a36151cb10d3542c389813a8c1d8c7de76949173 Mon Sep 17 00:00:00 2001 From: Ushie Date: Thu, 21 May 2026 04:06:35 +0300 Subject: [PATCH 1/6] feat: miaaaa0a Co-authored-by: miaaaa0a --- .../com/aliucord/gradle/repo/Authors.kt | 1 + plugins/ImageToClipboard/build.gradle.kts | 20 +++ .../imagetoclipboard/ImageToClipboard.kt | 128 ++++++++++++++++++ .../imagetoclipboard/MimeTypeGetter.kt | 93 +++++++++++++ .../NativeFriendNicknames/build.gradle.kts | 20 +++ .../EditNicknameDialog.kt | 64 +++++++++ .../NativeFriendNicknames.kt | 111 +++++++++++++++ 7 files changed, 437 insertions(+) create mode 100644 plugins/ImageToClipboard/build.gradle.kts create mode 100644 plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/ImageToClipboard.kt create mode 100644 plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/MimeTypeGetter.kt create mode 100644 plugins/NativeFriendNicknames/build.gradle.kts create mode 100644 plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/EditNicknameDialog.kt create mode 100644 plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/NativeFriendNicknames.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 9b001f6..cab4c36 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 @@ -44,6 +44,7 @@ object Authors { val Nyako = Author("nyakowint", 118437263754395652L) val Juby210 = Author("Juby210", 324622488644616195L) val LampDelivery = Author("LampDelivery", 650805815623680030L) + val miaaaa0a = Author("miaaaa0a", 435750383491481602L) } /** diff --git a/plugins/ImageToClipboard/build.gradle.kts b/plugins/ImageToClipboard/build.gradle.kts new file mode 100644 index 0000000..786730b --- /dev/null +++ b/plugins/ImageToClipboard/build.gradle.kts @@ -0,0 +1,20 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.2.0" +description = "add an option to copy images to your clipboard" + +aliucord { + author(Authors.miaaaa0a) + + changelog.set( + """ + # 1.2.0 + * completely rewrite mime type detection + """.trimIndent(), + ) +} diff --git a/plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/ImageToClipboard.kt b/plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/ImageToClipboard.kt new file mode 100644 index 0000000..287e9ef --- /dev/null +++ b/plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/ImageToClipboard.kt @@ -0,0 +1,128 @@ +package ing.frolick.imagetoclipboard + +import android.annotation.SuppressLint +import android.content.ClipData +import android.content.ClipDescription +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider + +import com.aliucord.Logger +import com.aliucord.Utils +import com.aliucord.Utils.appContext +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.Hook +import com.aliucord.utils.ViewUtils.findViewById +import com.discord.app.AppFragment +import com.discord.databinding.WidgetMediaBinding +import com.discord.utilities.color.ColorCompat +import com.discord.widgets.media.WidgetMedia + +import com.google.android.material.appbar.AppBarLayout +import com.lytefast.flexinput.R +import java.io.File +import java.io.InputStream +import java.net.URL + +@SuppressWarnings("unused") +@AliucordPlugin +class ImageToClipboard : Plugin() { + // https://stackoverflow.com/questions/14473180/regex-to-get-a-filename-from-a-url#comment115606945_26253039 + var urlRegex = Regex("[^/\\\\&?#]+\\.\\w{3,4}(?=([?&#].*$|$))") + + @SuppressLint("RestrictedApi") + override fun start(context: Context) { + patcher.patch( + WidgetMedia::class.java, "onViewBoundOrOnResume", arrayOf(), + Hook { + val widgetMedia = it.thisObject as WidgetMedia + val binding = WidgetMedia.`access$getBinding$p`(widgetMedia) + val field = WidgetMediaBinding::class.java.getDeclaredField("b").apply { isAccessible = true } + val appBarLayout = field.get(binding) as AppBarLayout + val menuMediaGroup = appBarLayout.findViewById("menu_media_download").parent as ViewGroup + + val imageUrl = (widgetMedia as AppFragment).mostRecentIntent + .getStringExtra("INTENT_MEDIA_URL") ?: return@Hook + val fileName = urlRegex.find(imageUrl, 0)?.value + val fileExt = fileName?.split('.')?.last() + var mimeType = MimeType.OTHER + Utils.threadPool.submit { + mimeType = MimeTypeGetter.get(imageUrl) + }.get() + //logger.info("MIME TYPE: ${mimeType.type}") + + //Logger().info("CHILDREN COUNT: ${actionBar.childCount}") + //val menuMediaGroup = actionBar.getChildAt(0) as ViewGroup + + val ctx = menuMediaGroup.context + val copyBtn = TextView(ctx, null, 0, R.i.UiKit_ImageButton).apply { + setCompoundDrawablesWithIntrinsicBounds( + ContextCompat + .getDrawable(ctx, R.e.ic_copy_24dp)!! + .mutate() + .apply { + setTint(ColorCompat.getThemedColor(ctx, R.b.colorTextNormal)) + }, + null, + null, + null, + ) + setOnClickListener { + Utils.showToast("copying...", false) + + Utils.threadPool.execute { + try { + val stream = URL(imageUrl).openStream() + + // context.filesDir is /data/data/com.aliucord/files, we are saving the image there + // in theory it'd be better to use context.cacheDir, but discord doesn't expose that + // in file_paths.xml, so it's not usable + val file = File(context.filesDir, "clipboard_tmp.${mimeType.ext}") + file.outputStream().use { out -> stream.copyTo(out) } + val uri = FileProvider.getUriForFile( + appContext, + "${context.packageName}.file-provider", + file + ) + val clip = ClipData( + ClipDescription("image", arrayOf(mimeType.type)), + ClipData.Item(uri) + ) + (appContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) + .setPrimaryClip(clip) + + Utils.mainThread.post { Utils.showToast("copied the image!", false) } + } catch (e: Exception) { + logger.error("itc error: ${e.message}", e) + Utils.mainThread.post { Utils.showToast("failed to copy >.<", false) } + } + } + } + } + if (appBarLayout.findViewWithTag("copy_btn") == null) { + // non image begone!!! + if (fileExt == "png" || + fileExt == "jpg" || + fileExt == "jpeg" || + fileExt == "webp" || + fileExt == "gif" || + fileExt == "apng") { + copyBtn.tag = "copy_btn" + menuMediaGroup.addView(copyBtn) + } + } + }, + ) + } + + override fun stop(context: Context) { + patcher.unpatchAll() + } +} diff --git a/plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/MimeTypeGetter.kt b/plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/MimeTypeGetter.kt new file mode 100644 index 0000000..13adf4d --- /dev/null +++ b/plugins/ImageToClipboard/src/main/java/ing/frolick/imagetoclipboard/MimeTypeGetter.kt @@ -0,0 +1,93 @@ +package ing.frolick.imagetoclipboard + +import android.os.Build +import androidx.annotation.RequiresApi +import java.io.File +import java.io.InputStream +import java.net.URL +import java.nio.file.Files +import java.util.Arrays.asList +import com.aliucord.Logger +import java.net.HttpURLConnection + +class MimeTypeGetter { + companion object { + private fun fromMagicNumber(url: String): MimeType { + val stream = URL(url).openStream() + val header = ByteArray(12).also { buf -> + var pos = 0 + while (pos < buf.size) { + val n = stream.read(buf, pos, buf.size - pos) + if (n == -1) break + pos += n + } + } + stream.close() + fun at(vararg bytes: Int, offset: Int = 0) = + bytes.indices.all { header[offset + it].toInt() and 0xFF == bytes[it] } + + // TODO: FIX APNG DETECTION AND MAKE IT OPTIMAL, THIS ALGO IS REALLY SLOW AND BROKEN + // only real way to detect APNG is to look for the animation control chunk (acTL) + // that comes later in the file, since the format has the same file magic as PNG + /*if (at(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A)) { + val buf = ArrayDeque() + // read first 4 bytes for sliding window + while (buf.size <= 4) buf.addLast(stream.read().also { if (it == -1) return MimeType.PNG }) + Logger("piglin").info("CHUNK: ${ArrayDeque(listOf(0x49, 0x44, 0x41, 0x54)).joinToString(" ") { "%02x".format(it.toByte()) }}") + + while (buf.intersect(listOf(0x61, 0x63, 0x54, 0x4C)).isNotEmpty()) { + // acTL always comes before the IDAT chunk. IDAT chunk before acTL == PNG + if (buf.intersect(listOf(0x49, 0x44, 0x41, 0x54)).isEmpty()) return MimeType.PNG + + buf.removeFirst() + val n = stream.read() + //Logger("piglin").info("CHUNK: ${buf.joinToString(" ") { "%02x".format(it.toByte()) }}") + if (n == -1) return MimeType.PNG + buf.addLast(n) + } + return MimeType.APNG + }*/ + + return when { + at(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A) -> MimeType.PNG + at(0xFF, 0xD8, 0xFF, 0xDB) -> MimeType.JPEG + at(0xFF, 0xD8, 0xFF, 0xE0) -> MimeType.JPEG + at(0xFF, 0xD8, 0xFF, 0xEE) -> MimeType.JPEG + at(0xFF, 0xD8, 0xFF, 0xE1) -> MimeType.JPEG + at(0x47, 0x49, 0x46, 0x38, 0x37, 0x61) -> MimeType.GIF + at(0x47, 0x49, 0x46, 0x38, 0x39, 0x61) -> MimeType.GIF + at(0x42, 0x4D) -> MimeType.BMP + // webp: RIFF????WEBP + at(0x52, 0x49, 0x46, 0x46) && + at(0x57, 0x45, 0x42, 0x50, offset = 8) -> MimeType.WEBP + else -> MimeType.OTHER + } + } + + private fun fromHeaders(url: String): MimeType? { + val connection = (URL(url).openConnection() as HttpURLConnection).apply { + this.instanceFollowRedirects = true + this.requestMethod = "HEAD" + } + connection.connect() + connection.headerFields["Content-Type"]?.let { + return MimeType.entries.firstOrNull { e -> e.type == it.first() } ?: MimeType.OTHER + } + return null + } + + fun get(url: String): MimeType { + return fromHeaders(url) ?: fromMagicNumber(url) + } + } +} + +enum class MimeType(val type: String, val ext: String) { + PNG("image/png", "png"), + APNG("image/apng", "png"), + JPEG("image/jpeg", "jpg"), + GIF("image/gif", "gif"), + BMP("image/x-ms-bmp", "bmp"), + WEBP("image/webp", "webp"), + OTHER("", "bin") +} diff --git a/plugins/NativeFriendNicknames/build.gradle.kts b/plugins/NativeFriendNicknames/build.gradle.kts new file mode 100644 index 0000000..f18eed3 --- /dev/null +++ b/plugins/NativeFriendNicknames/build.gradle.kts @@ -0,0 +1,20 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.1.6" +description = "a plugin that backports the friend nickname feature" + +aliucord { + author(Authors.miaaaa0a) + + changelog.set( + """ + # 1.1.6 + * don't display friend nickname in servers + """.trimIndent(), + ) +} diff --git a/plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/EditNicknameDialog.kt b/plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/EditNicknameDialog.kt new file mode 100644 index 0000000..063b99e --- /dev/null +++ b/plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/EditNicknameDialog.kt @@ -0,0 +1,64 @@ +package ing.frolick.nativefriendnicknames + +import android.view.View + +import com.aliucord.Http +import com.aliucord.Utils +import com.aliucord.fragments.InputDialog +import com.aliucord.wrappers.users.globalName +import com.discord.models.user.User +import com.discord.stores.StoreStream +import com.discord.stores.updates.ObservationDeck + +data class UpdateRelationshipRequest( + val nickname: String? +) + +class EditNicknameDialog(private val user: User) : InputDialog() { + override fun onViewBound(view: View) { + setTitle("Edit Friend Nickname") + setDescription("Enter a new nickname for your friend:") + setPlaceholderText("Nickname") + + setOnDialogShownListener { + // only replace input if a friend nickname already exists + if (Stores.friendNicknames.getNickname(user.id) != null) + inputLayout.editText?.setText(user.globalName) + } + + setOnOkListener { + val inputName = input.trim() + + if (inputName != user.globalName) { + Utils.threadPool.execute { + val req = UpdateRelationshipRequest(inputName) + val res = Http.Request + .newDiscordRNRequest("/users/@me/relationships/${user.id}", "PATCH") + .executeWithJson(req) + // why does this not throw the usual 200 success code dieee discord + if (res.statusCode == 204) { + // this is supposed to instantly update everything that uses User.globalName + // but it doesn't :( + /*StoreStream.`access$getDispatcher$p`(StoreStream.getPresences().stream).schedule { + val storeUsers = StoreStream.Companion!!.users + val getUsersUpdate = + storeUsers::class.java.getDeclaredMethod("access\$getUsersUpdate\$cp").apply { + isAccessible = true + } + val usersUpdate = getUsersUpdate.invoke(null) + storeUsers.markChanged(usersUpdate as ObservationDeck.UpdateSource?) + }*/ + } else { + Utils.mainThread.post { + Utils.showToast("Error ${res.statusCode} while changing ${user.username}'s nickname!", true) + } + } + } + } + + dismiss() + } + + super.onViewBound(view) + } +} diff --git a/plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/NativeFriendNicknames.kt b/plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/NativeFriendNicknames.kt new file mode 100644 index 0000000..8356432 --- /dev/null +++ b/plugins/NativeFriendNicknames/src/main/java/ing/frolick/nativefriendnicknames/NativeFriendNicknames.kt @@ -0,0 +1,111 @@ +package ing.frolick.nativefriendnicknames + +import android.content.Context +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.lytefast.flexinput.R + +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.GatewayAPI +import com.aliucord.entities.Plugin +import com.aliucord.patcher.* +import com.aliucord.wrappers.users.globalName +import com.discord.api.channel.`ChannelUtils$getDisplayName$1` +import com.discord.models.user.CoreUser +import com.discord.api.user.User +import com.discord.utilities.color.ColorCompat +import b.a.a.d.a as UserActionsDialog +import com.discord.stores.StoreStream +import com.aliucord.Utils +import com.aliucord.wrappers.ChannelWrapper.Companion.isDM + +internal class Relationship(val id: Long, val nickname: String?) +internal class Ready(val relationships: List) +object Stores { + val friendNicknames = StoreRelationshipNick() +} + +@SuppressWarnings("unused") +@AliucordPlugin(requiresRestart = true) +class NativeFriendNicknames : Plugin() { + override fun start(context: Context) { + Stores.friendNicknames.setup() + + patcher.before("getGlobalName") { + if (!StoreStream.getChannelsSelected().selectedChannel.isDM()) return@before + val nickname = Stores.friendNicknames.getNickname(this.id) ?: this.globalName + it.result = nickname + } + + // this gets globalName from a different api, so I have to patch it out manually + patcher.before<`ChannelUtils$getDisplayName$1`>("invoke", Any::class.java) { + val nickname = Stores.friendNicknames.getNickname((it.args[0] as User).id) ?: (it.args[0] as User).globalName ?: (it.args[0] as User).username + it.result = nickname + } + //Logger().info("METHODS: ${`ChannelUtils$getDisplayName$1`::class.java.declaredMethods.joinToString(",")}") + + patcher.patch( + UserActionsDialog::class.java, + "onViewBound", + arrayOf(View::class.java), + PreHook { param -> + val dialog = param.thisObject as UserActionsDialog + val root = param.args[0] as LinearLayout + + val userId = dialog.argumentsOrDefault + .getLong("com.discord.intent.extra.EXTRA_USER_ID", 0L) + val user = StoreStream.Companion!!.users.getUsers(listOf(userId), false)[userId] ?: return@PreHook + // no friend = no option + if (!Stores.friendNicknames.data.containsKey(userId)) return@PreHook + + val ctx = root.context + val newItem = TextView(ctx, null, 0, R.i.UiKit_ListItem_Icon).apply { + text = "Edit Friend Nickname" + setOnClickListener { + dialog.dismiss() + EditNicknameDialog(user).show(dialog.parentFragmentManager, "EditNickname") + } + setCompoundDrawablesWithIntrinsicBounds( + ContextCompat + .getDrawable(ctx, R.e.ic_edit_24dp)!! + .mutate() + .apply { + setTint(ColorCompat.getThemedColor(ctx, R.b.colorChannelIcon)) + }, + null, + null, + null + ) + } + + root.addView(newItem) + } + ) + } + + override fun stop(context: Context) { + patcher.unpatchAll() + } +} + +class StoreRelationshipNick(var data: MutableMap = mutableMapOf()) { + fun setup() { + GatewayAPI.onEvent("READY") { event -> + data = event.relationships.associate { relationship -> + relationship.id to relationship.nickname + } as MutableMap + } + GatewayAPI.onEvent("RELATIONSHIP_ADD") { event -> + data[event.id] = event.nickname + } + GatewayAPI.onEvent("RELATIONSHIP_UPDATE") { event -> + data[event.id] = event.nickname + } + GatewayAPI.onEvent("RELATIONSHIP_REMOVE") { event -> + data.remove(event.id) + } + } + fun getNickname(id: Long?): String? = data[id] +} From da2d4992e98bd89c35cb35c1fa143b5fa8d78516 Mon Sep 17 00:00:00 2001 From: Ushie Date: Thu, 21 May 2026 04:25:02 +0300 Subject: [PATCH 2/6] feat: Nyako --- .../com/aliucord/gradle/repo/Authors.kt | 2 + plugins/AutoShowSpoilers/build.gradle.kts | 13 + .../me/aniimalz/plugins/AutoShowSpoilers.kt | 35 ++ plugins/ChannelLocker/build.gradle.kts | 13 + .../kotlin/me/aniimalz/plugins/ChannelCard.kt | 34 ++ .../me/aniimalz/plugins/ChannelListAdapter.kt | 60 +++ .../me/aniimalz/plugins/ChannelLocker.kt | 200 +++++++++ .../me/aniimalz/plugins/PluginSettings.kt | 77 ++++ plugins/HomeIconSwitcher/build.gradle.kts | 13 + .../me/aniimalz/plugins/HomeIconSwitcher.kt | 113 +++++ .../me/aniimalz/plugins/PluginSettings.kt | 78 ++++ plugins/HopOnBloonsWhen/build.gradle.kts | 13 + .../me/aniimalz/plugins/HopOnBloonsWhen.kt | 61 +++ plugins/MoarConfirm/build.gradle.kts | 16 + .../kotlin/me/aniimalz/plugins/MoarConfirm.kt | 201 +++++++++ .../me/aniimalz/plugins/MoarSettings.kt | 50 +++ plugins/Moyai/build.gradle.kts | 13 + .../main/kotlin/me/aniimalz/plugins/Moyai.kt | 73 ++++ plugins/PinIcon/build.gradle.kts | 13 + .../kotlin/me/aniimalz/plugins/PinIcon.kt | 69 +++ plugins/Quoter/build.gradle.kts | 13 + .../main/kotlin/me/aniimalz/plugins/Quoter.kt | 255 ++++++++++++ plugins/ReactionNotifier/build.gradle.kts | 13 + .../me/aniimalz/plugins/PluginSettings.kt | 40 ++ .../me/aniimalz/plugins/ReactionNotifier.kt | 73 ++++ plugins/RevoltCord/build.gradle.kts | 13 + .../kotlin/me/aniimalz/plugins/BottomShit.kt | 66 +++ .../kotlin/me/aniimalz/plugins/RevoltCord.kt | 92 ++++ plugins/RoleMembers/build.gradle.kts | 13 + .../kotlin/me/aniimalz/plugins/MemberView.kt | 69 +++ .../kotlin/me/aniimalz/plugins/RoleMembers.kt | 41 ++ .../me/aniimalz/plugins/RoleMembersPage.kt | 126 ++++++ plugins/StartupChannel/build.gradle.kts | 13 + .../kotlin/me/aniimalz/plugins/BottomShit.kt | 59 +++ .../me/aniimalz/plugins/StartupChannel.kt | 94 +++++ plugins/StatusSheetPlus/build.gradle.kts | 13 + .../kotlin/me/aniimalz/plugins/StatusSheet.kt | 392 ++++++++++++++++++ .../me/aniimalz/plugins/StatusSheetPlus.kt | 24 ++ plugins/Timezones/build.gradle.kts | 65 +++ .../me/aniimalz/plugins/PluginSettings.kt | 57 +++ .../me/aniimalz/plugins/SettingsTZAdapter.kt | 91 ++++ .../main/kotlin/me/aniimalz/plugins/Stuff.kt | 50 +++ .../kotlin/me/aniimalz/plugins/Timezones.kt | 270 ++++++++++++ .../kotlin/me/aniimalz/plugins/UserTZCard.kt | 48 +++ .../UnknownConnectionIcons/build.gradle.kts | 13 + .../me/aniimalz/plugins/ConnectionDetails.kt | 51 +++ .../plugins/UnknownConnectionIcons.kt | 133 ++++++ .../src/main/res/drawable/amazonmusic.xml | 45 ++ .../main/res/drawable/amazonmusic_light.xml | 45 ++ .../src/main/res/drawable/bluesky.xml | 11 + .../src/main/res/drawable/bluesky_light.xml | 11 + .../src/main/res/drawable/bungie.xml | 5 + .../src/main/res/drawable/bungie_light.xml | 5 + .../src/main/res/drawable/crunchyroll.xml | 5 + .../main/res/drawable/crunchyroll_light.xml | 5 + .../src/main/res/drawable/domain.xml | 9 + .../src/main/res/drawable/domain_light.xml | 9 + .../src/main/res/drawable/ebay.xml | 8 + .../src/main/res/drawable/ebay_light.xml | 8 + .../src/main/res/drawable/epicgames.xml | 10 + .../src/main/res/drawable/epicgames_light.xml | 10 + .../src/main/res/drawable/instagram.png | Bin 0 -> 1153 bytes .../src/main/res/drawable/instagram_light.png | Bin 0 -> 1153 bytes .../src/main/res/drawable/mastodon.xml | 12 + .../src/main/res/drawable/mastodon_light.xml | 12 + .../src/main/res/drawable/paypal.xml | 6 + .../src/main/res/drawable/paypal_light.xml | 6 + .../src/main/res/drawable/riotgames.xml | 12 + .../src/main/res/drawable/riotgames_light.xml | 12 + .../src/main/res/drawable/roblox.xml | 9 + .../src/main/res/drawable/roblox_light.xml | 5 + .../src/main/res/drawable/tiktok.xml | 11 + .../src/main/res/drawable/tiktok_light.xml | 11 + plugins/xkcd/build.gradle.kts | 13 + .../main/kotlin/me/aniimalz/plugins/xkcd.kt | 100 +++++ 75 files changed, 3729 insertions(+) create mode 100755 plugins/AutoShowSpoilers/build.gradle.kts create mode 100755 plugins/AutoShowSpoilers/src/main/kotlin/me/aniimalz/plugins/AutoShowSpoilers.kt create mode 100755 plugins/ChannelLocker/build.gradle.kts create mode 100755 plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelCard.kt create mode 100755 plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelListAdapter.kt create mode 100755 plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelLocker.kt create mode 100755 plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt create mode 100755 plugins/HomeIconSwitcher/build.gradle.kts create mode 100755 plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/HomeIconSwitcher.kt create mode 100755 plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt create mode 100755 plugins/HopOnBloonsWhen/build.gradle.kts create mode 100755 plugins/HopOnBloonsWhen/src/main/kotlin/me/aniimalz/plugins/HopOnBloonsWhen.kt create mode 100755 plugins/MoarConfirm/build.gradle.kts create mode 100755 plugins/MoarConfirm/src/main/kotlin/me/aniimalz/plugins/MoarConfirm.kt create mode 100755 plugins/MoarConfirm/src/main/kotlin/me/aniimalz/plugins/MoarSettings.kt create mode 100755 plugins/Moyai/build.gradle.kts create mode 100755 plugins/Moyai/src/main/kotlin/me/aniimalz/plugins/Moyai.kt create mode 100755 plugins/PinIcon/build.gradle.kts create mode 100755 plugins/PinIcon/src/main/kotlin/me/aniimalz/plugins/PinIcon.kt create mode 100755 plugins/Quoter/build.gradle.kts create mode 100755 plugins/Quoter/src/main/kotlin/me/aniimalz/plugins/Quoter.kt create mode 100755 plugins/ReactionNotifier/build.gradle.kts create mode 100755 plugins/ReactionNotifier/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt create mode 100755 plugins/ReactionNotifier/src/main/kotlin/me/aniimalz/plugins/ReactionNotifier.kt create mode 100755 plugins/RevoltCord/build.gradle.kts create mode 100755 plugins/RevoltCord/src/main/kotlin/me/aniimalz/plugins/BottomShit.kt create mode 100755 plugins/RevoltCord/src/main/kotlin/me/aniimalz/plugins/RevoltCord.kt create mode 100755 plugins/RoleMembers/build.gradle.kts create mode 100755 plugins/RoleMembers/src/main/kotlin/me/aniimalz/plugins/MemberView.kt create mode 100755 plugins/RoleMembers/src/main/kotlin/me/aniimalz/plugins/RoleMembers.kt create mode 100755 plugins/RoleMembers/src/main/kotlin/me/aniimalz/plugins/RoleMembersPage.kt create mode 100755 plugins/StartupChannel/build.gradle.kts create mode 100755 plugins/StartupChannel/src/main/kotlin/me/aniimalz/plugins/BottomShit.kt create mode 100755 plugins/StartupChannel/src/main/kotlin/me/aniimalz/plugins/StartupChannel.kt create mode 100755 plugins/StatusSheetPlus/build.gradle.kts create mode 100644 plugins/StatusSheetPlus/src/main/kotlin/me/aniimalz/plugins/StatusSheet.kt create mode 100755 plugins/StatusSheetPlus/src/main/kotlin/me/aniimalz/plugins/StatusSheetPlus.kt create mode 100644 plugins/Timezones/build.gradle.kts create mode 100755 plugins/Timezones/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt create mode 100755 plugins/Timezones/src/main/kotlin/me/aniimalz/plugins/SettingsTZAdapter.kt create mode 100644 plugins/Timezones/src/main/kotlin/me/aniimalz/plugins/Stuff.kt create mode 100644 plugins/Timezones/src/main/kotlin/me/aniimalz/plugins/Timezones.kt create mode 100755 plugins/Timezones/src/main/kotlin/me/aniimalz/plugins/UserTZCard.kt create mode 100644 plugins/UnknownConnectionIcons/build.gradle.kts create mode 100755 plugins/UnknownConnectionIcons/src/main/kotlin/me/aniimalz/plugins/ConnectionDetails.kt create mode 100644 plugins/UnknownConnectionIcons/src/main/kotlin/me/aniimalz/plugins/UnknownConnectionIcons.kt create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/amazonmusic.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/amazonmusic_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/bluesky.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/bluesky_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/bungie.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/bungie_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/crunchyroll.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/crunchyroll_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/domain.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/domain_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/ebay.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/ebay_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/epicgames.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/epicgames_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/instagram.png create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/instagram_light.png create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/mastodon.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/mastodon_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/paypal.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/paypal_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/riotgames.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/riotgames_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/roblox.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/roblox_light.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/tiktok.xml create mode 100755 plugins/UnknownConnectionIcons/src/main/res/drawable/tiktok_light.xml create mode 100755 plugins/xkcd/build.gradle.kts create mode 100755 plugins/xkcd/src/main/kotlin/me/aniimalz/plugins/xkcd.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 cab4c36..4cd4e35 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 @@ -45,6 +45,8 @@ object Authors { val Juby210 = Author("Juby210", 324622488644616195L) val LampDelivery = Author("LampDelivery", 650805815623680030L) val miaaaa0a = Author("miaaaa0a", 435750383491481602L) + val Vendicated = Author("Vendicated", 343383572805058560L) + val mantikafasi = Author("mantikafasi", 287555395151593473L) } /** diff --git a/plugins/AutoShowSpoilers/build.gradle.kts b/plugins/AutoShowSpoilers/build.gradle.kts new file mode 100755 index 0000000..ab3b149 --- /dev/null +++ b/plugins/AutoShowSpoilers/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.0" +description = "Automatically clicks on spoilers for you" + +aliucord { + author(Authors.Nyako) +} \ No newline at end of file diff --git a/plugins/AutoShowSpoilers/src/main/kotlin/me/aniimalz/plugins/AutoShowSpoilers.kt b/plugins/AutoShowSpoilers/src/main/kotlin/me/aniimalz/plugins/AutoShowSpoilers.kt new file mode 100755 index 0000000..d0c0110 --- /dev/null +++ b/plugins/AutoShowSpoilers/src/main/kotlin/me/aniimalz/plugins/AutoShowSpoilers.kt @@ -0,0 +1,35 @@ +package me.aniimalz.plugins + +import android.content.Context +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.InsteadHook +import com.aliucord.patcher.after +import com.discord.simpleast.core.node.Node +import com.discord.utilities.textprocessing.MessagePreprocessor +import com.discord.utilities.textprocessing.node.SpoilerNode +import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemAttachment +import com.discord.widgets.chat.list.entries.AttachmentEntry + +@AliucordPlugin +class AutoShowSpoilers : Plugin() { + override fun start(ctx: Context) { + patcher.after("processNode", Node::class.java) { + val node = it.args[0] + if (node !is SpoilerNode<*>) return@after + node.updateState(spoilers.size, true) + } + patcher.after>("isRevealed") { this.isRevealed = true } + + // Embed spoiler patch + with(WidgetChatListAdapterItemAttachment.Model::class.java) { + val rev = getDeclaredMethod("isSpoilerEmbedRevealed", AttachmentEntry::class.java) + rev.apply { isAccessible = true } + patcher.patch(rev, InsteadHook { return@InsteadHook true }) + } + } + + override fun stop(ctx: Context) { + patcher.unpatchAll() + } +} \ No newline at end of file diff --git a/plugins/ChannelLocker/build.gradle.kts b/plugins/ChannelLocker/build.gradle.kts new file mode 100755 index 0000000..c732d72 --- /dev/null +++ b/plugins/ChannelLocker/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 = "Lets you lock channels so you cant type in them anymore" + +aliucord { + author(Authors.Nyako) +} \ No newline at end of file diff --git a/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelCard.kt b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelCard.kt new file mode 100755 index 0000000..febfb00 --- /dev/null +++ b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelCard.kt @@ -0,0 +1,34 @@ +package me.aniimalz.plugins + +import android.content.Context +import android.view.Gravity +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.aliucord.utils.DimenUtils.dpToPx +import com.lytefast.flexinput.R + +@Suppress("Deprecation") +class ChannelCard(ctx: Context?) : LinearLayout(ctx) { + val name: TextView + val delete: com.aliucord.views.ToolbarButton + + init { + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + name = TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon) + val buttons = LinearLayout(ctx) + buttons.orientation = HORIZONTAL + val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + params.marginEnd = dpToPx(16) + buttons.layoutParams = params + buttons.setHorizontalGravity(Gravity.END) + buttons.setVerticalGravity(Gravity.CENTER_VERTICAL) + delete = com.aliucord.views.ToolbarButton(ctx) + delete.setImageDrawable(ContextCompat.getDrawable(ctx!!, R.e.ic_close_primary_200_24dp)) + buttons.addView(delete) + addView(name) + addView(buttons) + } +} \ No newline at end of file diff --git a/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelListAdapter.kt b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelListAdapter.kt new file mode 100755 index 0000000..1a6eb07 --- /dev/null +++ b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelListAdapter.kt @@ -0,0 +1,60 @@ +package me.aniimalz.plugins + +import android.annotation.SuppressLint +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.aliucord.PluginManager +import com.aliucord.Utils +import com.aliucord.Utils.getResId +import com.aliucord.fragments.SettingsPage +import com.discord.utilities.color.ColorCompat +import com.lytefast.flexinput.R +import java.util.* + +class ChannelListAdapter( + page: SettingsPage, + private val channels: MutableMap +) : + RecyclerView.Adapter() { + inner class ChannelListHolder(private val adapter: ChannelListAdapter, val item: ChannelCard) : + RecyclerView.ViewHolder(item), View.OnClickListener { + override fun onClick(view: View) { + adapter.onClick() + } + + } + + private val ctx: Context? = page.context + override fun getItemCount(): Int { + return ArrayList(channels.keys).size + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelListHolder { + return ChannelListHolder(this, ChannelCard(ctx)) + } + + @SuppressLint("NotifyDataSetChanged") + override fun onBindViewHolder(holder: ChannelListHolder, position: Int) { + val name = ArrayList(channels.keys)[position] + holder.item.name.text = name + holder.item.delete.setOnClickListener { + channels.remove(name) + PluginManager.plugins["ChannelLocker"]!!.settings.setObject("channels", channels) + notifyDataSetChanged() + Utils.showToast("Channel removed: unlocked") + } + val icon = + ContextCompat.getDrawable(ctx!!, getResId("ic_text_channel_white_24dp", "drawable"))!! + .mutate() + icon.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)) + holder.item.name.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) + } + + fun onClick() { + + } + +} \ No newline at end of file diff --git a/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelLocker.kt b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelLocker.kt new file mode 100755 index 0000000..466969a --- /dev/null +++ b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/ChannelLocker.kt @@ -0,0 +1,200 @@ +package me.aniimalz.plugins + +import android.annotation.SuppressLint +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.aliucord.Constants +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.CommandsAPI +import com.aliucord.entities.Plugin +import com.aliucord.patcher.Hook +import com.aliucord.patcher.InsteadHook +import com.aliucord.patcher.after +import com.aliucord.utils.DimenUtils +import com.aliucord.utils.ReflectUtils +import com.aliucord.wrappers.ChannelWrapper.Companion.id +import com.aliucord.wrappers.ChannelWrapper.Companion.name +import com.discord.databinding.WidgetChannelsListItemActionsBinding +import com.discord.databinding.WidgetChatInputBinding +import com.discord.utilities.color.ColorCompat +import com.discord.widgets.channels.list.WidgetChannelsListItemChannelActions +import com.discord.widgets.chat.input.ChatInputViewModel +import com.discord.widgets.chat.input.WidgetChatInput +import com.google.gson.reflect.TypeToken +import com.lytefast.flexinput.R +import java.util.* + +@AliucordPlugin +class ChannelLocker : Plugin() { + init { + settingsTab = SettingsTab( + PluginSettings::class.java, + SettingsTab.Type.PAGE + ).withArgs(settings) + } + + + @SuppressLint("SetTextI18n") + override fun start(ctx: Context) { + val icon = Utils.tintToTheme( + ContextCompat.getDrawable( + Utils.appContext, + R.e.ic_lock_dark_a60_24dp + ) + ) // diamong makes me husk + val channelLockId = View.generateViewId() + val channels = settings.getObject( + "channels", HashMap(), TypeToken.getParameterized( + HashMap::class.java, String::class.javaObjectType, Long::class.javaObjectType + ).type + ) + + commands.registerCommand( + "lockchannel", + "Use this to lock the channel so you cannot type in it anymore" + ) { + if (channels.containsValue(it.channelId)) return@registerCommand CommandsAPI.CommandResult( + "Channel is already locked! how did you do this lol", + null, + false + ) + channels[it.currentChannel.name] = it.channelId + settings.setObject("channels", channels) + CommandsAPI.CommandResult("Channel has been locked!", null, false) + } + + + val bindingMethod = WidgetChatInput::class.java.getDeclaredMethod("getBinding") + .apply { isAccessible = true } + patcher.after( + "configureChatGuard", ChatInputViewModel.ViewState.Loaded::class.java + ) { + val loaded = it.args[0] as ChatInputViewModel.ViewState.Loaded + + if (channels.containsValue(loaded.channelId) && !loaded.shouldShowVerificationGate) { + val binding = bindingMethod(it.thisObject) as WidgetChatInputBinding + + val gateButtonText = binding.root.findViewById( + Utils.getResId( + "chat_input_member_verification_guard_text", + "id" + ) + ) + val chatWrap = binding.root.findViewById( + Utils.getResId( + "chat_input_wrap", + "id" + ) + ) + val gateButtonImage = binding.root.findViewById( + Utils.getResId( + "chat_input_member_verification_guard_icon", + "id" + ) + ) + val gateButtonArrow = binding.root.findViewById( + Utils.getResId( + "chat_input_member_verification_guard_action", + "id" + ) + ) + val gateButtonLayout = binding.root.findViewById( + Utils.getResId( + "guard_member_verification", + "id" + ) + ) + + gateButtonLayout.visibility = View.VISIBLE + chatWrap.visibility = View.GONE + + gateButtonImage.setImageResource(R.e.ic_channel_text_locked) + gateButtonText.text = + "Channel is locked" + gateButtonArrow.setImageResource(R.e.ic_check_white_24dp) + val pd = DimenUtils.defaultPadding + gateButtonArrow.setPadding(pd, pd, pd, pd) + gateButtonArrow.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + gateButtonArrow.setOnLongClickListener { + channels.remove(loaded.channel.name) + settings.setObject("channels", channels) + + gateButtonLayout.visibility = View.GONE + chatWrap.visibility = View.VISIBLE + return@setOnLongClickListener true + } + gateButtonArrow.setOnClickListener { + InsteadHook.DO_NOTHING + } + gateButtonArrow.visibility = + if (settings.getBool("showUnlock", true)) View.VISIBLE else View.GONE + } + } + + + // just adding stupid buttons to channel settings and channel list actions + + patcher.patch( + WidgetChannelsListItemChannelActions::class.java, "configureUI", arrayOf>( + WidgetChannelsListItemChannelActions.Model::class.java + ), Hook { + val model = it.args[0] as WidgetChannelsListItemChannelActions.Model + val actions = it.thisObject as WidgetChannelsListItemChannelActions + try { + val binding = ReflectUtils.invokeMethod( + actions, + "getBinding" + ) as WidgetChannelsListItemActionsBinding? + val root = (binding?.root as ViewGroup).getChildAt(0) as ViewGroup + TextView(root.context, null, 0, R.i.UiKit_Settings_Item_Icon).apply { + text = + if (!channels.containsValue(model.channel.id)) "Lock Channel" else "Unlock Channel" + setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) + id = channelLockId + typeface = ResourcesCompat.getFont( + Utils.appContext, + Constants.Fonts.whitney_medium + ) + setOnClickListener { + if (channels.containsValue(model.channel.id)) { + channels.remove(model.channel.name) + actions.dismiss() + Utils.showToast("Channel unlocked") + settings.setObject("channels", channels) + return@setOnClickListener + } + channels[model.channel.name] = model.channel.id + actions.dismiss() + Utils.showToast("Channel locked") + settings.setObject("channels", channels) + } + root.addView(this) + } + icon?.setTint( + ColorCompat.getThemedColor( + root.context, + R.b.colorInteractiveNormal + ) + ) + } catch (e: Throwable) { + logger.error("Error adding lock button to channel actions", e) + } + }) + } + + override fun stop(ctx: Context) { + patcher.unpatchAll() + commands.unregisterAll() + } +} \ No newline at end of file diff --git a/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt new file mode 100755 index 0000000..b7e93ce --- /dev/null +++ b/plugins/ChannelLocker/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt @@ -0,0 +1,77 @@ +package me.aniimalz.plugins + +import android.annotation.SuppressLint +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.aliucord.Utils +import com.aliucord.api.SettingsAPI +import com.aliucord.fragments.InputDialog +import com.aliucord.fragments.SettingsPage +import com.aliucord.views.Button +import com.aliucord.views.Divider +import com.aliucord.wrappers.ChannelWrapper.Companion.name +import com.discord.stores.StoreStream +import com.discord.views.CheckedSetting +import com.google.gson.reflect.TypeToken +import java.util.* + +class PluginSettings(private val settings: SettingsAPI) : SettingsPage() { + @SuppressLint("SetTextI18n", "NotifyDataSetChanged") + override fun onViewBound(view: View) { + super.onViewBound(view) + val ctx = view.context + val layout = linearLayout + setActionBarTitle("Channel Locker") + val fm = parentFragmentManager + + val channels = settings.getObject( + "channels", + HashMap(), + TypeToken.getParameterized( + HashMap::class.java, + String::class.javaObjectType, + Long::class.javaObjectType + ).type + ) + + val recycler = RecyclerView(ctx).apply { + layoutManager = LinearLayoutManager(ctx) + adapter = ChannelListAdapter(this@PluginSettings, channels) + } + + addView(Utils.createCheckedSetting(requireContext(), CheckedSetting.ViewType.SWITCH, "Show unlock button", "Whether to show the checkmark or not. If hidden you will have to unlock the channel through the settings").apply { + isChecked = settings.getBool("showUnlock", true) + setOnCheckedListener { settings.setBool("showUnlock", it) } + }) + + Button(ctx).run { + text = "Add Channel" + setOnClickListener { + InputDialog().run { + setTitle("Add channel") + setDescription("Enter the id of the channel you want to lock") + setOnCancelListener { dismiss() } + setOnOkListener { + if (channels.containsValue(input.toLong())) { + dismiss() + return@setOnOkListener + } + channels[StoreStream.getChannels().getChannel(input.toLong()).name] = + input.toLong() + dismiss() + Utils.showToast("Channel added: locked") + settings.setObject("channels", channels) + recycler.adapter?.notifyItemInserted(recycler.adapter!!.itemCount) + } + show(fm, "fsdkjlbgxjkhdlbfghfjkmugyhkl") + } + } + layout.addView(this) + } + + layout.addView(Divider(ctx)) + + layout.addView(recycler) + } +} \ No newline at end of file diff --git a/plugins/HomeIconSwitcher/build.gradle.kts b/plugins/HomeIconSwitcher/build.gradle.kts new file mode 100755 index 0000000..32a57e7 --- /dev/null +++ b/plugins/HomeIconSwitcher/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 = "switch the home icon above guild list also fresco sucks" + +aliucord { + author(Authors.Nyako) +} \ No newline at end of file diff --git a/plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/HomeIconSwitcher.kt b/plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/HomeIconSwitcher.kt new file mode 100755 index 0000000..2d38d12 --- /dev/null +++ b/plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/HomeIconSwitcher.kt @@ -0,0 +1,113 @@ +package me.aniimalz.plugins + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.OvalShape +import android.net.Uri +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.content.ContextCompat +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.after +import com.discord.widgets.guilds.list.GuildListItem +import com.discord.widgets.guilds.list.GuildListViewHolder +import com.facebook.drawee.view.SimpleDraweeView +import com.lytefast.flexinput.R +import b.f.g.a.a.b as bruh + + +@AliucordPlugin +class HomeIconSwitcher : Plugin() { + init { + settingsTab = SettingsTab( + PluginSettings::class.java, + SettingsTab.Type.BOTTOM_SHEET + ).withArgs(settings) + } + + private lateinit var homeView: AppCompatImageView + + var pluginIcon: Drawable? = null + + override fun start(ctx: Context) { + val viewId = View.generateViewId() + pluginIcon = ContextCompat.getDrawable(Utils.appContext, R.e.ic_tab_home) + patcher.after( + "configure", + GuildListItem.FriendsItem::class.java + ) { + if (!settings.getBool("enabled", true)) return@after + homeView = itemView.findViewById( + Utils.getResId( + "guilds_item_profile_avatar", + "id" + ) + ) + val layout = homeView.parent as FrameLayout + val homeIcon = settings.getString("homeIcon", null).takeIf { it != null } + try { + if (homeIcon?.let { it1 -> Utils.getResId(it1, "drawable") } != 0) { + val field = homeIcon?.let { R.e::class.java.getField(homeIcon) } + val icon = homeIcon?.let { + field?.let { it1 -> + ContextCompat.getDrawable(ctx, it1.getInt(field))?.apply { + mutate() + Utils.tintToTheme(this) + } + } + } + homeView.setImageDrawable(icon) + return@after + } + if (settings.getBool("removeBg", false)) { + layout.apply { + backgroundTintList = null + setBackgroundColor(Color.TRANSPARENT) + } + homeView.apply { + backgroundTintList = null + imageTintList = null + setBackgroundColor(Color.TRANSPARENT) + } + } + if (layout.findViewById(viewId) != null) return@after + SimpleDraweeView(ctx).apply { + id = viewId + imageTintList = null + clipToOutline = true + setImageURI(homeIcon) + layout.addView(this) + homeView.setImageDrawable(null) + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ).apply { + gravity = Gravity.CENTER + } + background = ShapeDrawable(OvalShape()).apply { paint.color = Color.TRANSPARENT } + controller = bruh.a().run { + f(Uri.parse(homeIcon)) + m = true + j = true + a() + } + } + } catch (t: Throwable) { + logger.error(t) + } + } + } + + override fun stop(ctx: Context) { + patcher.unpatchAll() + commands.unregisterAll() + } + +} \ No newline at end of file diff --git a/plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt b/plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt new file mode 100755 index 0000000..0a8e2cb --- /dev/null +++ b/plugins/HomeIconSwitcher/src/main/kotlin/me/aniimalz/plugins/PluginSettings.kt @@ -0,0 +1,78 @@ +package me.aniimalz.plugins + +import android.annotation.SuppressLint +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.widget.TextView +import androidx.core.content.res.ResourcesCompat +import com.aliucord.Constants +import com.aliucord.Logger +import com.aliucord.Utils +import com.aliucord.api.SettingsAPI +import com.aliucord.utils.DimenUtils +import com.aliucord.views.Divider +import com.aliucord.views.TextInput +import com.aliucord.widgets.BottomSheet +import com.discord.views.CheckedSetting +import com.lytefast.flexinput.R + +class PluginSettings(private val settings: SettingsAPI) : BottomSheet() { + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, bundle: Bundle?) { + super.onViewCreated(view, bundle) + val ctx = requireContext() + val logger = Logger("HomeIconSwitcher") + + addView(TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Header).apply { + typeface = ResourcesCompat.getFont(ctx, Constants.Fonts.whitney_bold) + text = "Home Icon Switcher" + isAllCaps = true + setPadding(DimenUtils.defaultPadding) + }) + + addView(Utils.createCheckedSetting( + ctx, CheckedSetting.ViewType.SWITCH, "Enable plugin", null + ).apply { + isChecked = settings.getBool("enabled", true) + setOnCheckedListener { + settings.setBool("enabled", it) + promptRestart() + } + }) + addView(Utils.createCheckedSetting( + ctx, CheckedSetting.ViewType.SWITCH, "Remove colored background", "Remove discord's colored background behind the image" + ).apply { + isChecked = settings.getBool("removeBg", true) + setOnCheckedListener { + settings.setBool("removeBg", it) + promptRestart() + } + }) + addView(Divider(ctx)) + + val icon = settings.getString("homeIcon", null).takeIf { it != null } + + addView(TextInput(ctx, "Home icon URL (supports drawables)", icon?.let { icon } ?: "", object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable?) { + try { + val txt = s.toString() + settings.setString("homeIcon", txt) + promptRestart("Restart to apply icon change.") + } catch (n: Throwable) { + logger.error(n) + } + } + })) + + } + + private fun promptRestart(t: String = "Restart to apply changes") { + Utils.promptRestart(t) + } +} \ No newline at end of file diff --git a/plugins/HopOnBloonsWhen/build.gradle.kts b/plugins/HopOnBloonsWhen/build.gradle.kts new file mode 100755 index 0000000..43366da --- /dev/null +++ b/plugins/HopOnBloonsWhen/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.4" +description = "hop on bloons when" + +aliucord { + author(Authors.Nyako) +} \ No newline at end of file diff --git a/plugins/HopOnBloonsWhen/src/main/kotlin/me/aniimalz/plugins/HopOnBloonsWhen.kt b/plugins/HopOnBloonsWhen/src/main/kotlin/me/aniimalz/plugins/HopOnBloonsWhen.kt new file mode 100755 index 0000000..74135c9 --- /dev/null +++ b/plugins/HopOnBloonsWhen/src/main/kotlin/me/aniimalz/plugins/HopOnBloonsWhen.kt @@ -0,0 +1,61 @@ +package me.aniimalz.plugins + +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import androidx.core.content.ContextCompat +import com.aliucord.Main +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.after +import com.aliucord.utils.RxUtils.onBackpressureBuffer +import com.aliucord.utils.RxUtils.subscribe +import com.discord.models.domain.ModelUserSettings +import com.discord.models.message.Message +import com.discord.models.user.CoreUser +import com.discord.stores.StoreStream +import com.discord.widgets.settings.WidgetSettingsAppearance +import com.discord.widgets.settings.`WidgetSettingsAppearance$updateTheme$1` +import com.lytefast.flexinput.R +import rx.Subscription +import java.util.* +import java.util.concurrent.ThreadLocalRandom +import kotlin.system.exitProcess + + +@AliucordPlugin +class HopOnBloonsWhen : Plugin() { + private var observable: Subscription? = null + var pluginIcon: Drawable? = null + override fun start(ctx: Context) { + pluginIcon = ContextCompat.getDrawable(Utils.appContext, R.e.ic_emoji_24dp) + observable = StoreStream.getGatewaySocket().messageCreate.onBackpressureBuffer().subscribe { + if (this == null) return@subscribe + val message = Message(this) + if (CoreUser(message.author).id == StoreStream.getUsers().me.id) return@subscribe + val content = message.content.lowercase() + if (content.contains("hop on bloons") || content.contains("get on bloons") || + content.contains("com.ninjakiwi.bloonstd6") + ) { + hopOnBloons() + } + } + } + + private fun hopOnBloons() { + try { + val bloons = + Utils.appContext.packageManager.getLaunchIntentForPackage("com.ninjakiwi.bloonstd6") + Utils.appContext.startActivity(bloons) + } catch (ignored: Throwable) { + // nop nop nop nop + } + } + + override fun stop(ctx: Context) { + patcher.unpatchAll() + commands.unregisterAll() + observable?.unsubscribe() + } +} \ No newline at end of file diff --git a/plugins/MoarConfirm/build.gradle.kts b/plugins/MoarConfirm/build.gradle.kts new file mode 100755 index 0000000..08ef5be --- /dev/null +++ b/plugins/MoarConfirm/build.gradle.kts @@ -0,0 +1,16 @@ +import com.aliucord.gradle.repo.Authors +import com.aliucord.gradle.repo.author + +plugins { + id("com.aliucord.plugins-repo") +} + +version = "1.0.6" +description = "More confirmations on shit." + +aliucord { + changelog.set(""" + ## 1.0.4 + - Added confirmation for deleting a role + """.trimIndent()) +} \ No newline at end of file diff --git a/plugins/MoarConfirm/src/main/kotlin/me/aniimalz/plugins/MoarConfirm.kt b/plugins/MoarConfirm/src/main/kotlin/me/aniimalz/plugins/MoarConfirm.kt new file mode 100755 index 0000000..617cfa0 --- /dev/null +++ b/plugins/MoarConfirm/src/main/kotlin/me/aniimalz/plugins/MoarConfirm.kt @@ -0,0 +1,201 @@ +package me.aniimalz.plugins + +import android.content.Context +import android.view.MenuItem +import android.widget.Button +import androidx.fragment.app.FragmentManager +import com.aliucord.Utils +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.fragments.ConfirmDialog +import com.aliucord.patcher.* +import com.aliucord.wrappers.GuildRoleWrapper.Companion.name +import com.discord.app.AppComponent +import com.discord.app.AppPermissionsRequests +import com.discord.databinding.WidgetUserSheetBinding +import com.discord.widgets.servers.`WidgetServerSettingsEditRole$setupMenu$1` +import com.discord.widgets.user.calls.PrivateCallLauncher +import com.discord.widgets.user.usersheet.WidgetUserSheet +import com.discord.widgets.user.usersheet.WidgetUserSheetViewModel +import com.discord.widgets.voice.call.PrivateCallLaunchUtilsKt +import de.robv.android.xposed.XposedBridge + +@AliucordPlugin +class MoarConfirm : Plugin() { + init { + settingsTab = + SettingsTab(MoarSettings::class.java, SettingsTab.Type.PAGE).withArgs(settings) + } + + private var permRequests: AppPermissionsRequests? = null + private var appComponent: AppComponent? = null + private var callFragManager: FragmentManager? = null + + override fun start(ctx: Context) { + patchCallButtons() + patchUserSheet() + patchRoleContext() + } + + private fun patchRoleContext() { + // confirmation for deleting roles + if (!settings.getBool("deleteRoleConfirm", true)) return + val args = arrayOf(MenuItem::class.java, Context::class.java) + patcher.before<`WidgetServerSettingsEditRole$setupMenu$1`>( + "call", + *args + ) { + val uh = this.`$data`.role + val cf = ConfirmDialog().apply { + setTitle("Delete Role") + setDescription("Do you want to delete ${uh.name}?") + setOnOkListener { _ -> + XposedBridge.invokeOriginalMethod( + `WidgetServerSettingsEditRole$setupMenu$1`::class.java.getDeclaredMethod( + "call", + *args + ), + it.thisObject, + arrayOf(it.args[0], it.args[1]) + ) + } + } + cf.show(Utils.appActivity.supportFragmentManager, "confirm_delete_role") + it.result = null + } + } + + private fun patchCallButtons() { + val permReqField = + PrivateCallLauncher::class.java.getDeclaredField("appPermissionsRequests") + .apply { isAccessible = true } + val appComponentField = PrivateCallLauncher::class.java.getDeclaredField("appComponent") + .apply { isAccessible = true } + val fragManangerField = PrivateCallLauncher::class.java.getDeclaredField("fragmentManager") + .apply { isAccessible = true } + + patcher.patch( + PrivateCallLauncher::class.java.getDeclaredMethod( + "launchVoiceCall", + Long::class.javaPrimitiveType + ), PreHook { + if (!settings.getBool("callConfirm", true)) return@PreHook + permRequests = permReqField.get(it.thisObject) as AppPermissionsRequests + appComponent = appComponentField.get(it.thisObject) as AppComponent + callFragManager = fragManangerField.get(it.thisObject) as FragmentManager + val userId = it.args[0] as Long + val confirmCall = ConfirmDialog().apply { + setTitle("Confirm Call") + setOnOkListener { + dismiss() + callUser(Utils.appContext, userId, false) + } + setDescription("Do you really want to call this user?") + setOnCancelListener { dismiss() } + } + confirmCall.show(callFragManager!!, "call_confirmation") + @Suppress("UsePropertyAccessSyntax") + it.setResult(null) + }) + + patcher.patch( + PrivateCallLauncher::class.java.getDeclaredMethod( + "launchVideoCall", + Long::class.javaPrimitiveType + ), PreHook { + if (!settings.getBool("callConfirm", true)) return@PreHook + permRequests = permReqField.get(it.thisObject) as AppPermissionsRequests + appComponent = appComponentField.get(it.thisObject) as AppComponent + callFragManager = fragManangerField.get(it.thisObject) as FragmentManager + val userId = it.args[0] as Long + val confirmCall = ConfirmDialog().apply { + setTitle("Confirm Video Call") + setOnOkListener { + dismiss() + callUser(Utils.appContext, userId, true) + } + setDescription("Do you really want to video call this user?") + setOnCancelListener { dismiss() } + } + confirmCall.show(callFragManager!!, "call_confirmation") + @Suppress("UsePropertyAccessSyntax") + it.setResult(null) + }) + } + + private fun patchUserSheet() { + with(WidgetUserSheet::class.java) { + val actionButtons = getDeclaredMethod( + "configureProfileActionButtons", + WidgetUserSheetViewModel.ViewState.Loaded::class.java + ) + actionButtons.apply { isAccessible = true } + patcher.patch(actionButtons, Hook { + + val addFriendId = Utils.getResId("user_sheet_add_friend_action_button", "id") + val userSheet = it.thisObject as WidgetUserSheet + + val root = userSheet.javaClass.getDeclaredMethod("getBinding").let { m -> + m.isAccessible = true + m.invoke(userSheet) as WidgetUserSheetBinding + }.root + val loaded = it.args[0] as WidgetUserSheetViewModel.ViewState.Loaded + val confirmBtn = root.findViewById