diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 6fe7679..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Build - -# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency -concurrency: - group: "build" - cancel-in-progress: true - -on: - push: - branches: - - main - paths-ignore: - - '*.md' - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@master - with: - path: "src" - - - name: Checkout builds - uses: actions/checkout@master - with: - ref: "builds" - path: "builds" - - - name: Checkout Aliucord - uses: actions/checkout@master - with: - repository: "Aliucord/Aliucord" - path: "repo" - - - name: Setup JDK 21 - uses: actions/setup-java@v1 - with: - java-version: 21 - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build Plugins - run: | - cd $GITHUB_WORKSPACE/src - chmod +x gradlew - ./gradlew make generateUpdaterJson - cp {canary,plugins}/*/build/outputs/*.zip $GITHUB_WORKSPACE/builds - cp build/outputs/updater.json $GITHUB_WORKSPACE/builds - - - name: Push builds - run: | - cd $GITHUB_WORKSPACE/builds - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - git add . - git commit -m "Build $GITHUB_SHA" || exit 0 # do not error if nothing to commit - git push diff --git a/.gitignore b/.gitignore deleted file mode 100644 index f5a8eb6..0000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea -.DS_Store -/build -**/build -/captures -.externalNativeBuild -.cxx -local.properties -/libs diff --git a/AlignThreads.zip b/AlignThreads.zip new file mode 100644 index 0000000..8ebe38e Binary files /dev/null and b/AlignThreads.zip differ diff --git a/Bubbles.zip b/Bubbles.zip new file mode 100644 index 0000000..83b48d6 Binary files /dev/null and b/Bubbles.zip differ diff --git a/Clump.zip b/Clump.zip new file mode 100644 index 0000000..799aff1 Binary files /dev/null and b/Clump.zip differ diff --git a/ComponentsV2Beta.zip b/ComponentsV2Beta.zip new file mode 100644 index 0000000..3f2c186 Binary files /dev/null and b/ComponentsV2Beta.zip differ diff --git a/Glance.zip b/Glance.zip new file mode 100644 index 0000000..155421c Binary files /dev/null and b/Glance.zip differ diff --git a/LICENCE b/LICENCE deleted file mode 100644 index 4452855..0000000 --- a/LICENCE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2025 Cilly Leang - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 63011cc..0000000 --- a/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Awoocord Plugins - -## [Bubbles](plugins/Crocosmia) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Bubbles.zip) - -Wrap messages in bubbles inspired by Material 3 Expressive - -## [Clump](plugins/Bocchi) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Clump.zip) - -Group messages more leniently (e.g. mentions, attachments, etc..), reducing clutter and wasted space. - -## [Glance](plugins/Myosotis) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Glance.zip) - -Backports DM previews similar to latest RN. Shows you a line of the last message sent in a DM. - -## [RoleBlocks](plugins/Zinnia) | [Download](https://github.com/cillynder/Awoocord/raw/builds/RoleBlocks.zip) - -Apply the role colour as a background of usernames, improving contrast with some role colours - -## [Scout](plugins/Scout) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Scout.zip) - -Vastly improves the search experience on Aliucord. - -Features: -- Sort by oldest messages first -- Sort by oldest first -- Filter by date (before, during, after) -- Exclude certain messages (opposite of `in:`) (not even desktop has this!) -- Search by user ID -- Search in threads - -Fixes: -- Removes the large padding from the top, most noticable if your device has a large status bar -- Removes the unnecessary #0000 discriminator - -# WIP Backports - -## [SlashCommandsFix](canary/SlashCommandsFix) | [Download](https://github.com/cillynder/Awoocord/raw/builds/SlashCommandsFixBeta.zip) - -Fixes slash commands not showing up. - -## [ComponentsV2](canary/ComponentsV2) | [Download](https://github.com/cillynder/Awoocord/raw/builds/ComponentsV2Beta.zip) - -Fix missing/empty bot messages using the new embed system. Such messages will be marked "CV2" as part of its tag. diff --git a/RoleBlocks.zip b/RoleBlocks.zip new file mode 100644 index 0000000..a3a74b5 Binary files /dev/null and b/RoleBlocks.zip differ diff --git a/Scout.zip b/Scout.zip new file mode 100644 index 0000000..1aae370 Binary files /dev/null and b/Scout.zip differ diff --git a/SlashCommandsFixBeta.zip b/SlashCommandsFixBeta.zip new file mode 100644 index 0000000..97981c3 Binary files /dev/null and b/SlashCommandsFixBeta.zip differ diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 6cfa17b..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,78 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -import com.aliucord.gradle.AliucordExtension -import com.android.build.gradle.LibraryExtension -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidExtension -import org.jlleitschuh.gradle.ktlint.KtlintExtension - -plugins { - alias(libs.plugins.kotlin.android) apply false - alias(libs.plugins.android.library) apply false - alias(libs.plugins.aliucord.plugin) apply true - alias(libs.plugins.ktlint) apply false - alias(libs.plugins.shadow) apply false -} - -subprojects { - val libs = rootProject.libs - - apply { - plugin(libs.plugins.android.library.get().pluginId) - plugin(libs.plugins.aliucord.plugin.get().pluginId) - plugin(libs.plugins.kotlin.android.get().pluginId) - plugin(libs.plugins.ktlint.get().pluginId) - } - - configure { - compileSdk = 36 - namespace = "moe.lava.awoocord" - - defaultConfig { - minSdk = 21 - } - - buildFeatures { - aidl = false - buildConfig = true - renderScript = false - shaders = false - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - } - } - - configure { - author("cilly", 368398754077868032L, hyperlink = false) - github("https://github.com/cillynder/Awoocord") - } - - configure { - version.set(libs.versions.ktlint.asProvider()) - - coloredOutput.set(true) - outputColorName.set("RED") - ignoreFailures.set(true) - } - - configure { - compilerOptions { - jvmTarget = JvmTarget.JVM_21 - optIn.add("kotlin.RequiresOptIn") - } - } - - @Suppress("unused") - dependencies { - val compileOnly by configurations - val implementation by configurations - - compileOnly(libs.discord) - compileOnly(libs.aliucord) - compileOnly(libs.aliuhook) - compileOnly(libs.kotlin.stdlib) - } -} diff --git a/canary/ComponentsV2/build.gradle.kts b/canary/ComponentsV2/build.gradle.kts deleted file mode 100644 index a568738..0000000 --- a/canary/ComponentsV2/build.gradle.kts +++ /dev/null @@ -1,69 +0,0 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - -version = "8.8.0" -description = "Beta backport of ComponentsV2" - -android { - namespace = "moe.lava.corenary.componentsv2" -} - -aliucord { - // Changelog of your plugin - changelog.set(""" - TODO {fixed} - ====================== - * File component - * SelectV2: searching - * SelectV2: showing selected items in chat list - - Changelog {added marginTop} - ====================== - # 8.8.0 - * Fix a possible weird crash - - # 8.7.0 - * Prevent ViewRaw crash - * Add a CV2 tag to distinguish new embeds (will not be in core) - - # 7.15.1 - * Fix broken reply preview >w< - - # 7.15.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} - -apply { - plugin(libs.plugins.shadow.get().pluginId) -} - -val shadowDir = File(buildDir, "intermediates/shadowed") - -tasks.register("relocateJar") { - val task = tasks.findByName("compileDebugKotlin")!! - from(task.outputs) -// relocate("com.discord.api.botuikit", "moe.lava.awoocanary.componentsv2.botuikit") { -// exclude("com.discord.api.botuikit.ComponentType") -// } - relocate("com.aliucord.coreplugins.componentsv2", "moe.lava.corenary.componentsv2") - relocate("com.aliucord.coreplugins.ComponentsV2", "moe.lava.corenary.ComponentsV2") - archiveClassifier.set("shadowed") - destinationDirectory.set(File(buildDir, "intermediates")) -} - -tasks.register("copyShadowed") { - val reloc = tasks.findByName("relocateJar")!! as ShadowJar - dependsOn(reloc) - from(zipTree(reloc.archiveFile)) - into(shadowDir) -} - -project.afterEvaluate { - tasks.compileDex { - val copyShadowed = tasks.findByName("copyShadowed")!! as Sync - dependsOn(copyShadowed) - input.setFrom(shadowDir) - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/CV2Compat.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/CV2Compat.kt deleted file mode 100644 index 598aed3..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/CV2Compat.kt +++ /dev/null @@ -1,137 +0,0 @@ -package com.aliucord.coreplugins - -import android.annotation.SuppressLint -import android.view.View -import android.widget.TextView -import com.aliucord.Constants -import com.aliucord.Utils -import com.aliucord.api.PatcherAPI -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.patcher.* -import com.aliucord.utils.GsonUtils -import com.aliucord.utils.GsonUtils.toJson -import com.aliucord.utils.ReflectUtils -import com.discord.api.botuikit.ComponentType -import com.discord.api.botuikit.gson.ComponentRuntimeTypeAdapter -import com.discord.api.botuikit.gson.ComponentTypeTypeAdapter -import com.discord.api.message.attachment.MessageAttachment -import com.discord.models.domain.Model -import com.discord.models.message.Message -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage -import com.google.gson.stream.JsonReader -import java.io.File -import b.a.b.a as TypeAdapterRegistrar -import b.i.d.c as FieldNamingPolicy -import b.i.d.e as GsonBuilder - -fun ComponentsV2.compat(patcher: PatcherAPI) { - // check for old cursed plugin, probably not needed anymore - val oldFile = File("${Constants.PLUGINS_PATH}/ComponentsV2-Beta.zip") - if (oldFile.exists()) { - logger.info("old plugin found, deleting and prompting restart") - oldFile.delete() - Utils.promptRestart() - return - } - - // I'm sorry - // ViewRaw crashes without this - val cuteGson = GsonBuilder().run { - c = FieldNamingPolicy.m // LOWER_CASE_WITH_UNDERSCORES - TypeAdapterRegistrar.a(this) - e.add(Model.TypeAdapterFactory()) - a().apply { - ReflectUtils.setField(this, "k", true) - } - } - patcher.patch(GsonUtils::class.java.getDeclaredMethod("toJsonPretty", Object::class.java)) - { (param, obj: Any) -> - if (obj is Message && obj.isComponentV2) - param.result = cuteGson.toJson(obj) - } - - // add cv2 tag - patcher.after("configureItemTag", Message::class.java, Boolean::class.javaPrimitiveType!!) - { (_, msg: Message) -> - val textView = ReflectUtils.getField(this, "itemTag") as TextView? - ?: return@after - - if (!msg.isComponentV2) - return@after - - if (textView.text.isEmpty()) { - // this code path shouldn't really ever run (only bots can send cv2, and bots have the tag already) - // but idk maybe someone self-bots or something - textView.visibility = View.VISIBLE - @SuppressLint("SetTextI18n") - textView.text = "CV2" - textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) - } else { - @SuppressLint("SetTextI18n") - textView.text = textView.text.toString() + " | CV2" - } - } - - ComponentV2Type.make() - patchGson(patcher) -} - -fun ComponentsV2.stopCompat() { - unpatchGson() - ComponentV2Type.unmake(logger) -} - -private fun patchGson(patcher: PatcherAPI) { - val factory = ComponentRuntimeTypeAdapter.INSTANCE.a() - val typeToClass = factory.l - val classToType = factory.m - ComponentV2Type.newValues?.forEach { - typeToClass[it.type.toString()] = it.clazz - classToType[it.clazz] = it.type.toString() - } - - patcher.instead("read", JsonReader::class.java) - { (_, jsonReader: JsonReader) -> - val type: Int = b.c.a.a0.d.n1(jsonReader) - ComponentType.values().find { it.type == type } ?: ComponentType.UNKNOWN - } -} - -private fun unpatchGson() { - val factory = ComponentRuntimeTypeAdapter.INSTANCE.a() - val typeToClass = factory.l - val classToType = factory.m - ComponentV2Type.newValues?.forEach { - typeToClass.remove(it.type.toString()) - classToType.remove(it.clazz) - } -} - -object CV2Compat { - /** Creates a new [MessageAttachment] */ - fun createAttachment( - filename: String, - filesize: Long, - proxyUrl: String, - url: String, - width: Int, - height: Int, - ): MessageAttachment { - val inst = ReflectUtils.allocateInstance(clazz) - filenameField.set(inst, filename) - filesizeField.set(inst, filesize) - proxyUrlField.set(inst, proxyUrl) - urlField.set(inst, url) - widthField.set(inst, width) - heightField.set(inst, height) - return inst - } -} - -private val clazz = MessageAttachment::class.java -private val filenameField = clazz.getDeclaredField("filename").apply { isAccessible = true } -private val filesizeField = clazz.getDeclaredField("size").apply { isAccessible = true } -private val proxyUrlField = clazz.getDeclaredField("proxyUrl").apply { isAccessible = true } -private val urlField = clazz.getDeclaredField("url").apply { isAccessible = true } -private val widthField = clazz.getDeclaredField("width").apply { isAccessible = true } -private val heightField = clazz.getDeclaredField("height").apply { isAccessible = true } diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/ComponentsV2.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/ComponentsV2.kt deleted file mode 100644 index 7302dd9..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/ComponentsV2.kt +++ /dev/null @@ -1,165 +0,0 @@ -package com.aliucord.coreplugins - -import android.content.Context -import android.view.ViewGroup -import android.widget.LinearLayout -import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.Utils -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.* -import com.aliucord.coreplugins.componentsv2.patchMessageItems -import com.aliucord.coreplugins.componentsv2.views.* -import com.aliucord.entities.Plugin -import com.aliucord.patcher.* -import com.discord.api.botuikit.* -import com.discord.models.botuikit.* -import com.discord.models.message.Message -import com.discord.stores.StoreApplicationInteractions.InteractionSendState -import com.discord.utilities.view.extensions.ViewExtensions -import com.discord.widgets.botuikit.* -import com.discord.widgets.botuikit.ComponentChatListState.ComponentStoreState -import com.discord.widgets.botuikit.views.* -import com.discord.widgets.botuikit.views.select.SelectComponentView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapter -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRow -import com.discord.widgets.chat.list.entries.BotUiComponentEntry -import com.lytefast.flexinput.R -import de.robv.android.xposed.XposedBridge - -val Message.isComponentV2 get() = ((flags ?: 0) shr 15) and 1 == 1L - -@AliucordPlugin(requiresRestart = true) -@Suppress("unused") -class ComponentsV2 : Plugin() { - override fun start(context: Context) { - compat(patcher) - XposedBridge.makeClassInheritable(BotUiComponentEntry::class.java) - // https://github.com/LSPosed/LSPlant/issues/41 - patchMessageItems(patcher) - - patcher.instead( - "toMessageLayoutComponent", - LayoutComponent::class.java, - Int::class.javaPrimitiveType!!, - List::class.java, - ComponentExperiments::class.java - ) { (_, layout: LayoutComponent, index: Int, components: List) -> - when (layout) { - is ActionRowComponent -> - ActionRowMessageComponent(layout.type, index, components) - is SectionComponent -> - SectionMessageComponent.mergeToMessageComponent(layout, index, components) - is TextDisplayComponent -> - TextDisplayMessageComponent.mergeToMessageComponent(layout, index) - is ThumbnailComponent -> - ThumbnailMessageComponent.mergeToMessageComponent(layout, index) - is MediaGalleryComponent -> - MediaGalleryMessageComponent.mergeToMessageComponent(layout, index) - is FileComponent -> - ActionRowMessageComponent(layout.type, index, components) - is SeparatorComponent -> - SeparatorMessageComponent.mergeToMessageComponent(layout, index) - is ContainerComponent -> - ContainerMessageComponent.mergeToMessageComponent(layout, index, components) - else -> - throw IllegalArgumentException("Unknown layout component ${layout::class.java.name} (${layout.type.type}:${layout.type.name})") - } - } - - patcher.instead("configureView", ComponentActionListener::class.java, MessageComponent::class.java, ComponentView::class.java) - { (_, listener: ComponentActionListener, component: MessageComponent, view: ComponentView?) -> - view?.configure(component, this, listener) - } - - patcher.instead("inflateComponent", ComponentType::class.java, ViewGroup::class.java) - { (_, type: ComponentType, viewGroup: ViewGroup) -> - when (type) { - ComponentType.ACTION_ROW -> - ActionRowComponentView.Companion!!.inflateComponent(this.context, viewGroup) - ComponentType.BUTTON -> - ButtonComponentView.Companion!!.inflateComponent(this.context, viewGroup) - ComponentType.SELECT -> - SelectComponentView.Companion!!.inflateComponent(this.context, viewGroup) - ComponentV2Type.USER_SELECT, - ComponentV2Type.ROLE_SELECT, - ComponentV2Type.MENTIONABLE_SELECT, - ComponentV2Type.CHANNEL_SELECT -> - SelectV2ComponentView(this.context, type) - ComponentV2Type.SECTION -> - SectionComponentView(this.context) - ComponentV2Type.TEXT_DISPLAY -> - TextDisplayComponentView(this.context) - ComponentV2Type.THUMBNAIL -> - ThumbnailComponentView(this.context) - ComponentV2Type.MEDIA_GALLERY -> - MediaGalleryComponentView(this.context) - ComponentV2Type.FILE -> - null - ComponentV2Type.SEPARATOR -> - SeparatorComponentView(this.context) - ComponentV2Type.CONTAINER -> - ContainerComponentView(this.context) - else -> null - } - } - - patcher.after(WidgetChatListAdapter::class.java) - { - val rootLayout = itemView.findViewById(Utils.getResId("chat_list_adapter_item_component_root", "id")) - rootLayout.layoutParams = (rootLayout.layoutParams as ConstraintLayout.LayoutParams).apply { - marginEnd = adapter.context.resources.getDimension(R.d.chat_cell_horizontal_spacing_padding).toInt() - } - - ViewExtensions.setOnLongClickListenerConsumeClick(itemView) { - adapter.eventHandler.onMessageLongClicked(entry.message, "", false) - } - itemView.setOnClickListener { - adapter.eventHandler.onMessageClicked(entry.message, false) - } - } - - patcher.instead( - "createActionMessageComponent", - ActionComponent::class.java, - Int::class.javaPrimitiveType!!, - ComponentStoreState::class.java, - ComponentExperiments::class.java, - ) { ( - _, - actionComponent: ActionComponent, - index: Int, - componentStoreState: ComponentStoreState, - ) -> - val interactionState: Map? = componentStoreState.interactionState; - val num = interactionState?.entries?.find { it.value is InteractionSendState.Loading }?.key - - val state = interactionState?.get(index) - val comState: ActionInteractionComponentState = when { - state is InteractionSendState.Failed -> ActionInteractionComponentState.Failed(state.errorMessage) - num == null -> ActionInteractionComponentState.Enabled.INSTANCE - num == index -> ActionInteractionComponentState.Loading.INSTANCE - else -> ActionInteractionComponentState.Disabled.INSTANCE - } - - when (actionComponent) { - is ButtonComponent -> - ButtonMessageComponentKt.mergeToMessageComponent(actionComponent, index, comState, componentStoreState) - is SelectComponent -> - SelectMessageComponentKt.mergeToMessageComponent(actionComponent, index, comState, componentStoreState) - is SelectV2Component -> - SelectV2MessageComponent.mergeToMessageComponent(actionComponent, index, comState, componentStoreState) - else -> null - } - } - - patcher.after("shouldShowReplyPreviewAsAttachment") { param -> - if (this.isComponentV2) param.result = true - } - } - - override fun stop(context: Context) { - patcher.unpatchAll() - stopCompat() - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/BotUiComponentV2Entry.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/BotUiComponentV2Entry.kt deleted file mode 100644 index 18a68b0..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/BotUiComponentV2Entry.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.aliucord.coreplugins.componentsv2 - -import com.discord.api.channel.Channel -import com.discord.api.role.GuildRole -import com.discord.models.botuikit.MessageComponent -import com.discord.models.member.GuildMember -import com.discord.models.message.Message -import com.discord.stores.StoreMessageState -import com.discord.widgets.chat.list.entries.BotUiComponentEntry - -@Suppress("EqualsOrHashCode") -class BotUiComponentV2Entry( - message: Message, appId: Long, guildId: Long?, components: MutableList, - private val v2Fields: V2Fields -) : BotUiComponentEntry(message, appId, guildId, components) { - data class V2Fields( - val state: StoreMessageState.State?, - val meId: Long, - val channel: Channel, - val guildMembers: Map, - val guildRoles: Map, - // val channelNames: Map, - ) - - companion object { - fun fromV1(entry: BotUiComponentEntry, fields: V2Fields) = - entry.run { BotUiComponentV2Entry(message, applicationId, guildId, messageComponents, fields) } - } - - val state get() = v2Fields.state - val meId get() = v2Fields.meId - val channel get() = v2Fields.channel - val guildMembers get() = v2Fields.guildMembers - val guildRoles get() = v2Fields.guildRoles - - override fun equals(other: Any?) = - super.equals(other) && if (other is BotUiComponentV2Entry) this.v2Fields == other.v2Fields else true - - override fun toString() = - "AliuV2" + super.toString() + "& " + v2Fields.toString() -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/ComponentV2Type.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/ComponentV2Type.kt deleted file mode 100644 index 79c35e0..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/ComponentV2Type.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.aliucord.coreplugins.componentsv2 - -import com.aliucord.Logger -import com.discord.api.botuikit.* - -// Values added by smali patch -object ComponentV2Type { - lateinit var USER_SELECT: ComponentType - lateinit var ROLE_SELECT: ComponentType - lateinit var MENTIONABLE_SELECT: ComponentType - lateinit var CHANNEL_SELECT: ComponentType - lateinit var SECTION: ComponentType - lateinit var TEXT_DISPLAY: ComponentType - lateinit var THUMBNAIL: ComponentType - lateinit var MEDIA_GALLERY: ComponentType - lateinit var FILE: ComponentType - lateinit var SEPARATOR: ComponentType - lateinit var CONTAINER: ComponentType - - var newValues: Array? = null - private var oldValues: Array? = null - @Suppress("UNCHECKED_CAST", "UNUSED_CHANGED_VALUE") - fun make() { - if (oldValues != null) - return - oldValues = ComponentType.values() - - val cls = ComponentType::class.java - val constructor = cls.declaredConstructors[0] - constructor.isAccessible = true - - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - val values = ComponentType.values() - var nextIdx = values.size - - USER_SELECT = constructor.newInstance("USER_SELECT", nextIdx++, 5, UserSelectComponent::class.java) as ComponentType - ROLE_SELECT = constructor.newInstance("ROLE_SELECT", nextIdx++, 6, RoleSelectComponent::class.java) as ComponentType - MENTIONABLE_SELECT = constructor.newInstance("MENTIONABLE_SELECT", nextIdx++, 7, MentionableSelectComponent::class.java) as ComponentType - CHANNEL_SELECT = constructor.newInstance("CHANNEL_SELECT", nextIdx++, 8, ChannelSelectComponent::class.java) as ComponentType - SECTION = constructor.newInstance("SECTION", nextIdx++, 9, SectionComponent::class.java) as ComponentType - TEXT_DISPLAY = constructor.newInstance("TEXT_DISPLAY", nextIdx++, 10, TextDisplayComponent::class.java) as ComponentType - THUMBNAIL = constructor.newInstance("THUMBNAIL", nextIdx++, 11, ThumbnailComponent::class.java) as ComponentType - MEDIA_GALLERY = constructor.newInstance("MEDIA_GALLERY", nextIdx++, 12, MediaGalleryComponent::class.java) as ComponentType - FILE = constructor.newInstance("FILE", nextIdx++, 13, FileComponent::class.java) as ComponentType - SEPARATOR = constructor.newInstance("SEPARATOR", nextIdx++, 14, SeparatorComponent::class.java) as ComponentType - CONTAINER = constructor.newInstance("CONTAINER", nextIdx++, 17, ContainerComponent::class.java) as ComponentType - - newValues = arrayOf(USER_SELECT, ROLE_SELECT, MENTIONABLE_SELECT, CHANNEL_SELECT, SECTION, TEXT_DISPLAY, THUMBNAIL, MEDIA_GALLERY, FILE, SEPARATOR, CONTAINER) - field.set(null, values + newValues!!) - } - - fun unmake(logger: Logger) { - if (oldValues == null) - return logger.error("No unpatched component types?", null) - - val cls = ComponentType::class.java - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - field.set(null, oldValues) - oldValues = null - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/PatchMessageItems.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/PatchMessageItems.kt deleted file mode 100644 index 9c13ff1..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/PatchMessageItems.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.aliucord.coreplugins.componentsv2 - -import com.aliucord.api.PatcherAPI -import com.aliucord.coreplugins.isComponentV2 -import com.aliucord.patcher.* -import com.discord.api.channel.Channel -import com.discord.api.role.GuildRole -import com.discord.models.member.GuildMember -import com.discord.models.message.Message -import com.discord.stores.StoreMessageReplies.MessageState -import com.discord.stores.StoreMessageState -import com.discord.stores.StoreThreadMessages -import com.discord.widgets.chat.list.entries.BotUiComponentEntry -import com.discord.widgets.chat.list.entries.ChatListEntry -import com.discord.widgets.chat.list.model.WidgetChatListModelMessages - -fun patchMessageItems(patcher: PatcherAPI) { - @Suppress("UNUSED_DESTRUCTURED_PARAMETER_ENTRY", "LocalVariableName", "UnusedVariable") - patcher.patch(WidgetChatListModelMessages.Companion::class.java.declaredMethods.find { it.name == "getMessageItems" }!!) - {( - param, - channel: Channel, - guildMembers: Map, - guildRoles: Map, - _blockedRelationships: Map?, - _referencedChannel: Channel?, - _threadStoreState: StoreThreadMessages.ThreadState?, - _message: Message, - state: StoreMessageState.State?, - _repliedMessages: Map?, - _isBlockedExpanded: Boolean, - _isMinimal: Boolean, - ) -> - @Suppress("UNCHECKED_CAST") - val result = (param.result as MutableList) - val meId = param.args[15] as Long - result.forEachIndexed { index, entry -> - if (entry is BotUiComponentEntry && entry.message.isComponentV2) { - val fields = BotUiComponentV2Entry.V2Fields(state, meId, channel, guildMembers, guildRoles) - result[index] = BotUiComponentV2Entry.fromV1(entry, fields) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/ContainerMessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/ContainerMessageComponent.kt deleted file mode 100644 index 62213c7..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/ContainerMessageComponent.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.api.botuikit.ComponentType -import com.discord.api.botuikit.ContainerComponent -import com.discord.models.botuikit.MessageComponent - -data class ContainerMessageComponent( - private val type: ComponentType, - private val index: Int, - - override val id: Int, - val components: List, - val accentColor: Int?, - override val spoiler: Boolean, -) : SpoilableMessageComponent { - override fun getType() = type - override fun getIndex() = index - - companion object { - fun mergeToMessageComponent( - component: ContainerComponent, - index: Int, - components: List, - ): ContainerMessageComponent { - components.forEach { - if (it is MediaGalleryMessageComponent) - it.markedContained = true - } - return component.run { - ContainerMessageComponent( - type, - index, - id, - components, - accentColor, - spoiler, - ) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/MediaGalleryMessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/MediaGalleryMessageComponent.kt deleted file mode 100644 index 350c17c..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/MediaGalleryMessageComponent.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.api.botuikit.* -import com.discord.models.botuikit.MessageComponent - -data class MediaGalleryMessageComponent( - private val type: ComponentType, - private val index: Int, - - val id: Int, - val items: List, - // Set by ContainerComponentView to tell MediaGalleryComponentView it is contained - var markedContained: Boolean = false, -) : MessageComponent { - override fun getType() = type - override fun getIndex() = index - - companion object { - fun mergeToMessageComponent( - component: MediaGalleryComponent, - index: Int - ): MediaGalleryMessageComponent { - return component.run { - MediaGalleryMessageComponent( - type, - index, - id, - items, - ) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SectionMessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SectionMessageComponent.kt deleted file mode 100644 index 6b189ea..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SectionMessageComponent.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.api.botuikit.ComponentType -import com.discord.api.botuikit.SectionComponent -import com.discord.models.botuikit.MessageComponent - -data class SectionMessageComponent( - private val type: ComponentType, - private val index: Int, - - val id: Int, - val components: List, - val accessory: MessageComponent?, -) : MessageComponent { - override fun getType() = type - override fun getIndex() = index - - companion object { - fun mergeToMessageComponent( - component: SectionComponent, - index: Int, - components: List, - ): SectionMessageComponent { - return component.run { - val realComponents = components.toMutableList() - val accessory = realComponents.removeAt(realComponents.lastIndex) - SectionMessageComponent( - type, - index, - id, - realComponents, - accessory, - ) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SelectV2MessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SelectV2MessageComponent.kt deleted file mode 100644 index 3560f3f..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SelectV2MessageComponent.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.api.botuikit.* -import com.discord.models.botuikit.ActionInteractionComponentState -import com.discord.models.botuikit.ActionMessageComponent -import com.discord.widgets.botuikit.ComponentChatListState - -data class SelectV2MessageComponent( - private val type: ComponentType, - private val index: Int, - private val stateInteraction: ActionInteractionComponentState, - - val id: Int, - val customId: String, - val placeholder: String, - val minValues: Int, - val maxValues: Int, - val defaultValues: List, - val emojiAnimationsEnabled: Boolean, -) : ActionMessageComponent() { - override fun getType() = type - override fun getIndex() = index - override fun getStateInteraction() = stateInteraction - - companion object { - fun mergeToMessageComponent( - selectComponent: SelectV2Component, - index: Int, - stateInteraction: ActionInteractionComponentState, - componentStoreState: ComponentChatListState.ComponentStoreState - ): SelectV2MessageComponent { - return selectComponent.run { - SelectV2MessageComponent( - type, - index, - stateInteraction, - id, - customId, - placeholder, - minValues, - maxValues, - defaultValues ?: listOf(), - componentStoreState.animateEmojis - ) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SeparatorMessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SeparatorMessageComponent.kt deleted file mode 100644 index e055d36..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SeparatorMessageComponent.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.api.botuikit.ComponentType -import com.discord.api.botuikit.SeparatorComponent -import com.discord.models.botuikit.MessageComponent - -data class SeparatorMessageComponent( - private val type: ComponentType, - private val index: Int, - - val divider: Boolean, - val spacing: Int, // 1 = small padding, 2 = large padding -) : MessageComponent { - override fun getType() = type - override fun getIndex() = index - - companion object { - fun mergeToMessageComponent( - component: SeparatorComponent, - index: Int - ): SeparatorMessageComponent { - return component.run { - SeparatorMessageComponent( - type, - index, - divider, - spacing, - ) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SpoilableMessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SpoilableMessageComponent.kt deleted file mode 100644 index 4219856..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/SpoilableMessageComponent.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.models.botuikit.MessageComponent - -interface SpoilableMessageComponent : MessageComponent { - val id: Int - val spoiler: Boolean -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/TextDisplayMessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/TextDisplayMessageComponent.kt deleted file mode 100644 index aeb3e75..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/TextDisplayMessageComponent.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.api.botuikit.ComponentType -import com.discord.api.botuikit.TextDisplayComponent -import com.discord.models.botuikit.MessageComponent - -data class TextDisplayMessageComponent( - private val type: ComponentType, - private val index: Int, - - val id: Int, - val content: String, -) : MessageComponent { - override fun getType() = type - override fun getIndex() = index - - companion object { - fun mergeToMessageComponent( - component: TextDisplayComponent, - index: Int - ): TextDisplayMessageComponent { - return component.run { - TextDisplayMessageComponent( - type, - index, - id, - content - ) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/ThumbnailMessageComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/ThumbnailMessageComponent.kt deleted file mode 100644 index d4bd625..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/models/ThumbnailMessageComponent.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.models - -import com.discord.api.botuikit.* - -data class ThumbnailMessageComponent( - private val type: ComponentType, - private val index: Int, - - override val id: Int, - val media: UnfurledMediaItem, - val description: String?, - override val spoiler: Boolean, -) : SpoilableMessageComponent { - override fun getType() = type - override fun getIndex() = index - - companion object { - fun mergeToMessageComponent( - component: ThumbnailComponent, - index: Int - ): ThumbnailMessageComponent { - return component.run { - ThumbnailMessageComponent( - type, - index, - id, - media, - description, - spoiler, - ) - } - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheet.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheet.kt deleted file mode 100644 index bc731af..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheet.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.selectsheet - -import android.os.Bundle -import android.view.View -import android.widget.TextView -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.SimpleItemAnimator -import com.aliucord.Utils -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.models.SelectV2MessageComponent -import com.discord.app.AppBottomSheet -import com.discord.utilities.view.extensions.ViewExtensions -import com.discord.utilities.view.recycler.MaxHeightRecyclerView -import com.discord.widgets.botuikit.views.select.`SelectComponentBottomSheet$binding$2` -import com.lytefast.flexinput.R -import b.a.k.b as FormatUtils - -internal class SelectSheet : AppBottomSheet { - val entry: BotUiComponentV2Entry? - val component: SelectV2MessageComponent? - - private lateinit var header: ConstraintLayout - private lateinit var placeholder: TextView - private lateinit var recycler: MaxHeightRecyclerView - private lateinit var select: TextView - private lateinit var subtitle: TextView - - private lateinit var adapter: SelectSheetAdapter - - constructor(entry: BotUiComponentV2Entry, component: SelectV2MessageComponent) { - this.entry = entry - this.component = component - } - constructor() { - this.entry = null - this.component = null - } - - override fun getContentViewResId() = Utils.getResId("widget_select_component_bottom_sheet", "layout") - - override fun onViewCreated(view: View, bundle: Bundle?) { - super.onViewCreated(view, bundle) - - val viewModel = ViewModelProvider(this).get(SelectSheetViewModel::class.java) - - `SelectComponentBottomSheet$binding$2`.INSTANCE.invoke(view).run { - header = a - placeholder = b - recycler = c - select = d - subtitle = e - } - adapter = SelectSheetAdapter(recycler, viewModel) - recycler.adapter = adapter - (recycler.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false - - select.setOnClickListener { viewModel.submit() } - viewModel.onUpdate = ::configureUI - viewModel.onRequestDismiss = ::dismiss - if (entry != null && component != null) - viewModel.configure(entry, component) - else - viewModel.state?.let { configureUI(it) } - } - - private fun configureUI(state: SelectSheetViewModel.ViewState) { - placeholder.text = state.placeholder - subtitle.visibility = if (state.isMultiSelect) View.VISIBLE else View.GONE - - if (state.isMultiSelect) { - subtitle.text = - FormatUtils.k( - this, - R.h.message_select_component_select_requirement, - arrayOf(state.minSelections), - null, - 4 - ) - } - select.visibility = if (state.isMultiSelect) View.VISIBLE else View.INVISIBLE - select.isClickable = state.isValidSelection - ViewExtensions.setEnabledAlpha(select, state.isValidSelection, 0.3f) - adapter.setData(state.items) - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetAdapter.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetAdapter.kt deleted file mode 100644 index d63a58a..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.selectsheet - -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.discord.utilities.mg_recycler.MGRecyclerAdapterSimple -import com.discord.utilities.mg_recycler.MGRecyclerViewHolder - -internal class SelectSheetAdapter(recycler: RecyclerView, val viewModel: SelectSheetViewModel) - : MGRecyclerAdapterSimple(recycler) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MGRecyclerViewHolder<*, SelectSheetItem> { - return SelectSheetItemViewHolder(this) - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetItem.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetItem.kt deleted file mode 100644 index 414de8d..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetItem.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.selectsheet - -import com.aliucord.wrappers.ChannelWrapper.Companion.id -import com.discord.api.channel.Channel -import com.discord.api.role.GuildRole -import com.discord.models.member.GuildMember -import com.discord.models.user.User -import com.discord.utilities.mg_recycler.MGRecyclerDataPayload - -sealed class SelectSheetItem( - private val type: Int, - val id: Long, -) : MGRecyclerDataPayload { - override fun getKey() = id.toString() - override fun getType() = type - - abstract val checked: Boolean - abstract val disabled: Boolean - abstract fun copy(checked: Boolean = this.checked, disabled: Boolean = this.disabled) : SelectSheetItem - - internal data class UserSelectItem( - val user: User, - val member: GuildMember, - override val checked: Boolean, - override val disabled: Boolean = false, - ) : SelectSheetItem(1, user.id) { - override fun copy(checked: Boolean, disabled: Boolean): SelectSheetItem = copy(checked = checked, disabled = disabled, user = user) - } - - internal data class RoleSelectItem( - val role: GuildRole, - override val checked: Boolean, - override val disabled: Boolean = false, - ) : SelectSheetItem(2, role.id) { - override fun copy(checked: Boolean, disabled: Boolean): SelectSheetItem = copy(checked = checked, disabled = disabled, role = role) - } - - internal data class ChannelSelectItem( - val channel: Channel, - override val checked: Boolean, - override val disabled: Boolean = false, - ) : SelectSheetItem(3, channel.id) { - override fun copy(checked: Boolean, disabled: Boolean): SelectSheetItem = copy(checked = checked, disabled = disabled, channel = channel) - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetItemViewHolder.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetItemViewHolder.kt deleted file mode 100644 index 700c95c..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetItemViewHolder.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.selectsheet - -import android.annotation.SuppressLint -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.Utils -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.wrappers.ChannelWrapper.Companion.name -import com.aliucord.wrappers.ChannelWrapper.Companion.type -import com.aliucord.wrappers.GuildRoleWrapper.Companion.name -import com.discord.api.channel.Channel -import com.discord.models.member.GuildMember -import com.discord.utilities.color.ColorCompat -import com.discord.utilities.drawable.DrawableCompat -import com.discord.utilities.guilds.RoleUtils -import com.discord.utilities.icon.IconUtils -import com.discord.utilities.images.MGImages -import com.discord.utilities.mg_recycler.MGRecyclerViewHolder -import com.discord.utilities.user.UserUtils -import com.discord.utilities.view.extensions.ViewExtensions -import com.facebook.drawee.view.SimpleDraweeView -import com.google.android.material.checkbox.MaterialCheckBox -import com.google.android.material.textview.MaterialTextView -import com.lytefast.flexinput.R - -@SuppressLint("SetTextI18n") -internal class SelectSheetItemViewHolder(adapter: SelectSheetAdapter) - : MGRecyclerViewHolder(Utils.getResId("widget_select_component_bottom_sheet_item", "layout"), adapter) { - - private val description = itemView.findViewById(Utils.getResId("select_component_sheet_item_description", "id"))!! - private val divider = itemView.findViewById(Utils.getResId("select_component_sheet_item_divider", "id"))!! - private val dividerWithIcon = itemView.findViewById(Utils.getResId("select_component_sheet_item_divider_icon", "id"))!! - private val icon = itemView.findViewById(Utils.getResId("select_component_sheet_item_icon", "id"))!! - private val checkbox = itemView.findViewById(Utils.getResId("select_component_sheet_item_selected", "id"))!! - private val title = itemView.findViewById(Utils.getResId("select_component_sheet_item_title", "id"))!! - - init { - (itemView as ConstraintLayout).minHeight = 62.dp - divider.visibility = View.GONE - dividerWithIcon.visibility = View.VISIBLE - dividerWithIcon.layoutParams = (dividerWithIcon.layoutParams as ConstraintLayout.LayoutParams).apply { - marginStart = 56.dp - } - description.setPadding(0, 0, 0, 12.dp) - icon.visibility = View.VISIBLE - icon.layoutParams = (icon.layoutParams as ConstraintLayout.LayoutParams).apply { - width = 24.dp - height = 24.dp - } - MGImages.setRoundingParams( - icon, - Float.MAX_VALUE, - false, - null, - null, - null, - ) - } - - override fun onConfigure(viewType: Int, item: SelectSheetItem) { - super.onConfigure(viewType, item) - - description.visibility = View.GONE - checkbox.visibility = if (adapter.viewModel.state?.isMultiSelect == true) VISIBLE else GONE - checkbox.isChecked = item.checked - title.setPadding(0, 12.dp, 0, 12.dp) - - when (item) { - is SelectSheetItem.ChannelSelectItem -> configureChannel(item) - is SelectSheetItem.RoleSelectItem -> configureRole(item) - is SelectSheetItem.UserSelectItem -> configureUser(item) - } - itemView.setOnClickListener { adapter.viewModel.onItemSelect(item) } - ViewExtensions.setEnabledAlpha(itemView, !item.disabled, 0.3f); - itemView.isEnabled = !item.disabled - } - - private fun configureChannel(item: SelectSheetItem.ChannelSelectItem) { - title.text = "#${item.channel.name}" - val res = when (item.channel.type) { - Channel.GUILD_ANNOUNCEMENT -> R.e.ic_channel_announcements - Channel.GUILD_VOICE -> R.e.ic_channel_voice - Channel.CATEGORY -> DrawableCompat.getThemedDrawableRes(adapter.context, R.b.ic_category) - else -> DrawableCompat.getThemedDrawableRes(adapter.context, R.b.ic_channel_text) - } - icon.setImageResource(res) - } - - private fun configureRole(item: SelectSheetItem.RoleSelectItem) { - title.text = item.role.name - - val opaqueColor: Int = RoleUtils.getOpaqueColor(item.role, ColorCompat.getColor(adapter.context, R.c.status_grey_500)) - icon.setImageResource(R.e.ic_role_24dp) - icon.setColorFilter(opaqueColor) - } - - private fun configureUser(item: SelectSheetItem.UserSelectItem) { - IconUtils.`setIcon$default`( - icon, - item.user, - R.d.avatar_size_standard, - null, - null, - item.member, - 24, - null - ) - title.text = GuildMember.Companion!!.getNickOrUsername(item.member, item.user) - val descText = item.user.username + if (item.user.discriminator != 0) - UserUtils.INSTANCE.getDiscriminatorWithPadding(item.user) - else - "" - if (title.text != descText) { - title.setPadding(0, 12.dp, 0, 0) - description.visibility = View.VISIBLE - description.text = descText - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetViewModel.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetViewModel.kt deleted file mode 100644 index 16c8271..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/selectsheet/SelectSheetViewModel.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.selectsheet - -import androidx.lifecycle.ViewModel -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.SelectV2MessageComponent -import com.aliucord.wrappers.ChannelWrapper.Companion.id -import com.discord.api.botuikit.ComponentType -import com.discord.restapi.RestAPIParams.ComponentInteractionData.SelectComponentInteractionData -import com.discord.stores.StoreStream - -const val ENTRY_LIMIT = 15 - -internal class SelectSheetViewModel() : ViewModel() { - data class ViewState( - val placeholder: String, - val items: List, - val isMultiSelect: Boolean, - val minSelections: Int, - val maxSelections: Int, - val isValidSelection: Boolean, - ) - - private data class SubmissionData( - val applicationId: Long, - val guildId: Long?, - val channelId: Long, - val messageId: Long, - val messageFlags: Long, - val index: Int, - val customId: String, - val type: ComponentType, - ) - - var onUpdate: ((ViewState) -> Unit)? = null - var onRequestDismiss: (() -> Unit)? = null - - var state: ViewState? = null - set(value) { - field = value - value?.let { onUpdate?.invoke(it) } - } - - private var submissionData: SubmissionData? = null - - fun configure(entry: BotUiComponentV2Entry, component: SelectV2MessageComponent) { - var entryCount = 0 - val items = mutableListOf() - val users = StoreStream.getUsers().users - if (component.type in listOf(ComponentV2Type.USER_SELECT, ComponentV2Type.MENTIONABLE_SELECT)) { - for (member in entry.guildMembers.values) { - entryCount += 1 - if (entryCount > ENTRY_LIMIT) - break - val user = users[member.userId]!! - val isDefault = component.defaultValues.any { it.id == member.userId } - items.add(SelectSheetItem.UserSelectItem(user, member, isDefault)) - } - } - if (component.type in listOf(ComponentV2Type.ROLE_SELECT, ComponentV2Type.MENTIONABLE_SELECT)) { - for (role in entry.guildRoles.values) { - entryCount += 1 - if (entryCount > ENTRY_LIMIT) - break - val isDefault = component.defaultValues.any { it.id == role.id } - items.add(SelectSheetItem.RoleSelectItem(role, isDefault)) - } - } - // TODO: is the guildID check needed? as in, can server side allow this component? - if (component.type == ComponentV2Type.CHANNEL_SELECT && entry.guildId != null) { - val channels = StoreStream.getChannels().getChannelsForGuild(entry.guildId!!)!! - for (channel in channels.values) { - entryCount += 1 - if (entryCount > ENTRY_LIMIT) - break - val isDefault = component.defaultValues.any { it.id == channel.id } - items.add(SelectSheetItem.ChannelSelectItem(channel, isDefault)) - } - } - - val min = component.minValues - val max = component.maxValues - state = ViewState( - component.placeholder, - items, - isMultiSelect = max > 1, - minSelections = min, - maxSelections = max, - isValidSelection = false, - ) - submissionData = SubmissionData( - entry.applicationId, - entry.guildId, - entry.message.channelId, - entry.message.id, - entry.message.flags, - component.index, - component.customId, - component.type, - ) - } - - fun onItemSelect(item: SelectSheetItem) { - val state = state ?: return - var checkedCount = 0 - var newItems = state.items.map { - val res = if (it == item) - item.copy(checked = !item.checked) - else - it - if (res.checked) - checkedCount += 1 - res - } - val isMaxed = checkedCount == state.maxSelections - newItems = newItems.map { - it.copy(disabled = isMaxed && !it.checked) - } - this.state = state.copy( - items = newItems, - isValidSelection = checkedCount in state.minSelections..state.maxSelections - ) - - if (!state.isMultiSelect) - submit() - } - - fun submit() { - // val companion = StoreStream.Companion - // companion.localActionComponentState.setSelectComponentSelection(this.componentContext.getMessageId(), this.componentIndex, u.toList(set)) - val state = state ?: return - val submissionData = submissionData ?: return - - val selected = state.items.filter { it.checked }.map { it.id.toString() } - submissionData.run { - StoreStream.getInteractions().sendComponentInteraction( - applicationId, - guildId, - channelId, - messageId, - index, - SelectComponentInteractionData( - type, - customId, - selected, - ), - messageFlags - ) - } - onRequestDismiss?.invoke() - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/ContainerComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/ContainerComponentView.kt deleted file mode 100644 index 36d9d4d..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/ContainerComponentView.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.views - -import android.content.Context -import android.view.View -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID -import androidx.core.graphics.ColorUtils -import com.aliucord.Logger -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.ContainerMessageComponent -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.aliucord.widgets.LinearLayout -import com.discord.utilities.color.ColorCompat -import com.discord.widgets.botuikit.ComponentProvider -import com.discord.widgets.botuikit.views.ComponentActionListener -import com.discord.widgets.botuikit.views.ComponentView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRow -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRowKt -import com.google.android.material.card.MaterialCardView -import com.lytefast.flexinput.R - -class ContainerComponentView(ctx: Context) : ConstraintLayout(ctx), ComponentView { - override fun type() = ComponentV2Type.CONTAINER - - companion object { - private val accentDividerId = View.generateViewId() - } - - private lateinit var accentDivider: View - private lateinit var contentView: LinearLayout - private lateinit var spoilerView: SpoilerView - - init { - MaterialCardView(ctx).addTo(this) { - radius = 8.dp.toFloat() - elevation = 0f - setCardBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.colorBackgroundSecondary)) - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - topToTop = PARENT_ID - bottomToBottom = PARENT_ID - startToStart = PARENT_ID - } - ConstraintLayout(ctx).addTo(this) { - accentDivider = View(ctx).addTo(this) { - id = accentDividerId - layoutParams = LayoutParams(3.dp, 0).apply { - bottomToBottom = PARENT_ID - startToStart = PARENT_ID - topToTop = PARENT_ID - } - } - contentView = LinearLayout(ctx).addTo(this) { - setPadding(8.dp, 8.dp, 8.dp, 8.dp) - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - startToEnd = accentDividerId - endToEnd = PARENT_ID - topToTop = PARENT_ID - constrainedWidth = true - } - } - spoilerView = SpoilerView(ctx, 1).addTo(this) { - layoutParams = SpoilerView.constraintLayoutParamsAround(PARENT_ID) - } - } - } - } - - override fun configure(component: ContainerMessageComponent, provider: ComponentProvider, listener: ComponentActionListener) { - val item = listener as WidgetChatListAdapterItemBotComponentRow - val entry = item.entry - if (entry !is BotUiComponentV2Entry) { - Logger("ComponentsV2").warn("configured container with non-v2 entry") - return - } - - val configuredViews = component.components.mapIndexed { index, child -> - provider.getConfiguredComponentView(listener, child, contentView, index) - }.filterNotNull() - WidgetChatListAdapterItemBotComponentRowKt.replaceViews(contentView, configuredViews) - - val color = component.accentColor?.let { ColorUtils.setAlphaComponent(it, 255) } - ?: ColorCompat.getThemedColor(context, R.b.colorBackgroundModifierAccent) - accentDivider.setBackgroundColor(color) - - spoilerView.configure(entry, component) - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/MediaGalleryComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/MediaGalleryComponentView.kt deleted file mode 100644 index 64c44d4..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/MediaGalleryComponentView.kt +++ /dev/null @@ -1,131 +0,0 @@ -@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") - -package com.aliucord.coreplugins.componentsv2.views - -import android.content.Context -import android.view.View -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID -import com.aliucord.Logger -import com.aliucord.coreplugins.CV2Compat -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.MediaGalleryMessageComponent -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.aliucord.widgets.LinearLayout -import com.aliucord.wrappers.messages.AttachmentWrapper.Companion.height -import com.aliucord.wrappers.messages.AttachmentWrapper.Companion.width -import com.discord.api.message.attachment.MessageAttachment -import com.discord.utilities.color.ColorCompat -import com.discord.utilities.display.DisplayUtils -import com.discord.utilities.embed.EmbedResourceUtils -import com.discord.widgets.botuikit.ComponentProvider -import com.discord.widgets.botuikit.views.ComponentActionListener -import com.discord.widgets.botuikit.views.ComponentView -import com.discord.widgets.chat.list.InlineMediaView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRow -import com.discord.widgets.media.WidgetMedia -import com.google.android.material.card.MaterialCardView -import com.lytefast.flexinput.R - -class MediaGalleryComponentView(ctx: Context) : ConstraintLayout(ctx), ComponentView { - override fun type() = ComponentV2Type.MEDIA_GALLERY - - companion object { - private val mediaViewId = View.generateViewId() - private val maxEmbedHeight = EmbedResourceUtils.INSTANCE.maX_IMAGE_VIEW_HEIGHT_PX - } - - private val layout = LinearLayout(ctx).addTo(this) { - layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { - topToTop = PARENT_ID - startToStart = PARENT_ID - endToEnd = PARENT_ID - } - } - private var mediaViews: List>? = null - - // This isn't pretty, but Discord actually does this in their code (EmbedResourceUtils.computeMaximumImageWidthPx) - private fun calculateMaxWidth(contained: Boolean): Int { - var maxPossibleWidth = DisplayUtils.getScreenSize(context).width() - - resources.getDimensionPixelSize(R.d.uikit_guideline_chat) - - resources.getDimensionPixelSize(R.d.chat_cell_horizontal_spacing_total) - - if (contained) - maxPossibleWidth -= 15.dp - - return maxPossibleWidth.coerceAtMost(1440) - } - - // Reference: WidgetChatListAdapterItemAttachment.configureUI - override fun configure(component: MediaGalleryMessageComponent, provider: ComponentProvider, listener: ComponentActionListener) { - val item = listener as WidgetChatListAdapterItemBotComponentRow - val entry = item.entry - if (entry !is BotUiComponentV2Entry) { - Logger("ComponentsV2").warn("configured media gallery with non-v2 entry") - return - } - - val maxEmbedWidth = calculateMaxWidth(component.markedContained) - layout.removeAllViews() - val pendingViews = mutableListOf>() - component.items.forEachIndexed { index, it -> - val media = it.media - // TODO: there's probably a utility to extract filename from url - val name = media.url.split("/").last().split("?").first() - val attachment = CV2Compat.createAttachment( - name, - 0, - media.proxyUrl, - media.url, - media.width, - media.height, - ) - - val (width, height) = EmbedResourceUtils.INSTANCE.calculateScaledSize( - attachment.width!!, - attachment.height!!, - maxEmbedWidth, - maxEmbedHeight, - resources, - 0, - ) - MaterialCardView(context).addTo(layout) { - radius = 8.dp.toFloat() - elevation = 0f - setCardBackgroundColor(ColorCompat.getThemedColor(context, R.b.colorBackgroundPrimary)) - layoutParams = android.widget.LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - topMargin = 8.dp - } - ConstraintLayout(context).addTo(this) { - layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT) - val mediaView = InlineMediaView(context).addTo(this) { - radius = 8.dp.toFloat() - elevation = 0f - setCardBackgroundColor(ColorCompat.getThemedColor(context, R.b.colorBackgroundPrimary)) - id = mediaViewId - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - topToTop = PARENT_ID - startToStart = PARENT_ID - } - setOnClickListener { - WidgetMedia.Companion!!.launch(context, attachment); - } - updateUIWithAttachment(attachment, width, height, true) - } - val spoilerView = SpoilerView(context, 1).addTo(this) { - translationZ = 10f - layoutParams = SpoilerView.constraintLayoutParamsAround(mediaViewId) - } - pendingViews.add(attachment to mediaView) - spoilerView.configure(it.spoiler, entry.state, entry.message.id, Pair(component.id, "media:$index")) - } - } - } - mediaViews = pendingViews.toList() - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SectionComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SectionComponentView.kt deleted file mode 100644 index 750c28b..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SectionComponentView.kt +++ /dev/null @@ -1,57 +0,0 @@ -@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") - -package com.aliucord.coreplugins.componentsv2.views - -import android.content.Context -import android.view.View -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.SectionMessageComponent -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.aliucord.widgets.LinearLayout -import com.discord.widgets.botuikit.ComponentProvider -import com.discord.widgets.botuikit.views.ComponentActionListener -import com.discord.widgets.botuikit.views.ComponentView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRowKt - -class SectionComponentView(ctx: Context) : ConstraintLayout(ctx), ComponentView { - override fun type() = ComponentV2Type.SECTION - - companion object { - private val accessoryViewId = View.generateViewId() - } - - private val mainView = LinearLayout(ctx).addTo(this) { - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - constrainedWidth = true - horizontalBias = 0f - topToTop = PARENT_ID - startToStart = PARENT_ID - endToStart = accessoryViewId - marginEnd = 16.dp - } - } - private var accessoryView = FrameLayout(ctx).addTo(this) { - id = accessoryViewId - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - topToTop = PARENT_ID - endToEnd = PARENT_ID - } - } - - override fun configure(component: SectionMessageComponent, provider: ComponentProvider, listener: ComponentActionListener) { - val configuredViews = component.components.mapIndexed { index, child -> - provider.getConfiguredComponentView(listener, child, mainView, index) - }.filterNotNull() - WidgetChatListAdapterItemBotComponentRowKt.replaceViews(mainView, configuredViews) - - val accessoryComponent = provider.getConfiguredComponentView(listener, component.accessory, accessoryView, 0) - accessoryComponent?.let { - WidgetChatListAdapterItemBotComponentRowKt.replaceViews(accessoryView, listOf(accessoryComponent)) - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SelectV2ComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SelectV2ComponentView.kt deleted file mode 100644 index 79026ba..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SelectV2ComponentView.kt +++ /dev/null @@ -1,85 +0,0 @@ -@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") - -package com.aliucord.coreplugins.componentsv2.views - -import android.annotation.SuppressLint -import android.content.Context -import android.widget.ImageView -import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.Logger -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.models.SelectV2MessageComponent -import com.aliucord.coreplugins.componentsv2.selectsheet.SelectSheet -import com.aliucord.utils.ViewUtils.addTo -import com.discord.api.botuikit.ComponentType -import com.discord.models.botuikit.SelectMessageComponent -import com.discord.views.typing.TypingDots -import com.discord.widgets.botuikit.ComponentProvider -import com.discord.widgets.botuikit.views.ComponentActionListener -import com.discord.widgets.botuikit.views.ComponentView -import com.discord.widgets.botuikit.views.select.SelectComponentView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRow -import com.facebook.drawee.view.SimpleDraweeView -import com.google.android.flexbox.FlexboxLayout -import com.google.android.material.textview.MaterialTextView - -@SuppressLint("ViewConstructor") -internal class SelectV2ComponentView(context: Context, private val type: ComponentType) - : ConstraintLayout(context), ComponentView { - override fun type(): ComponentType = type - - private val componentView: SelectComponentView - private val chevron: ImageView - private val loadingDots: TypingDots - private val selectionIcon: SimpleDraweeView - private val selectionText: MaterialTextView - private val selectionsRoot: FlexboxLayout - - init { - val view = SelectComponentView.Companion!!.inflateComponent(context, this).addTo(this) - layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) - maxWidth = view.maxWidth - b.a.i.b5.a(view).run { - componentView = a - chevron = b - loadingDots = c - selectionIcon = d - selectionText = e - selectionsRoot = f - } - } - - override fun configure( - component: SelectV2MessageComponent, - provider: ComponentProvider, - listener: ComponentActionListener, - ) { - val item = listener as WidgetChatListAdapterItemBotComponentRow - val entry = item.entry - if (entry !is BotUiComponentV2Entry) { - Logger("ComponentsV2").warn("configured v2 select with non-v2 entry") - return - } - - val proxyComponent = component.run { - SelectMessageComponent( - type, - index, - stateInteraction, - customId, - placeholder, - minValues, - maxValues, - listOf(), - listOf(), - emojiAnimationsEnabled, - ) - } - - componentView.configure(proxyComponent, provider, listener) - componentView.setOnClickListener { - val sh = SelectSheet(entry, component) - sh.show(item.adapter.fragmentManager, SelectSheet::class.java.name) - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SeparatorComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SeparatorComponentView.kt deleted file mode 100644 index 855e7ef..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SeparatorComponentView.kt +++ /dev/null @@ -1,42 +0,0 @@ -@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") - -package com.aliucord.coreplugins.componentsv2.views - -import android.content.Context -import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.Logger -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.SeparatorMessageComponent -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.aliucord.views.Divider -import com.discord.utilities.color.ColorCompat -import com.discord.widgets.botuikit.ComponentProvider -import com.discord.widgets.botuikit.views.ComponentActionListener -import com.discord.widgets.botuikit.views.ComponentView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRow -import com.lytefast.flexinput.R - -class SeparatorComponentView(ctx: Context) : ConstraintLayout(ctx), ComponentView { - override fun type() = ComponentV2Type.SEPARATOR - - private val divider = Divider(ctx).addTo(this) { - setBackgroundColor(ColorCompat.getThemedColor(context, R.b.colorTextMuted)); - } - - override fun configure(component: SeparatorMessageComponent, provider: ComponentProvider, listener: ComponentActionListener) { - val item = listener as WidgetChatListAdapterItemBotComponentRow - val entry = item.entry - if (entry !is BotUiComponentV2Entry) { - Logger("ComponentsV2").warn("configured separator with non-v2 entry") - return - } - - divider.visibility = if (component.divider) VISIBLE else INVISIBLE - divider.layoutParams = (divider.layoutParams as LayoutParams).apply { - val padding = 6.dp * component.spacing - setPadding(paddingLeft, padding, paddingRight, padding) - } - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SpoilerView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SpoilerView.kt deleted file mode 100644 index 24ba988..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/SpoilerView.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.aliucord.coreplugins.componentsv2.views - -import android.annotation.SuppressLint -import android.content.Context -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.* -import androidx.cardview.widget.CardView -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.models.SpoilableMessageComponent -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.discord.stores.StoreMessageState -import com.discord.stores.StoreStream -import com.discord.utilities.color.ColorCompat -import com.lytefast.flexinput.R - -/** - * A view that can be spoilered. - * - * @param ctx Context - * @param type 1 for full (spoiler text and button), 2 for mini (eye icon) - */ -@SuppressLint("ViewConstructor") -internal class SpoilerView(ctx: Context, type: Int) : ConstraintLayout(ctx) { - companion object { - fun constraintLayoutParamsAround(viewId: Int) = - LayoutParams(0, 0).apply { - topToTop = viewId - bottomToBottom = viewId - startToStart = viewId - endToEnd = viewId - } - } - - private val spoilerView = ConstraintLayout(ctx).addTo(this) { - visibility = GONE - setBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.theme_chat_spoiler_bg)) - layoutParams = LayoutParams(0, 0).apply { - bottomToBottom = PARENT_ID - endToEnd = PARENT_ID - startToStart = PARENT_ID - topToTop = PARENT_ID - } - isClickable = true - - when (type) { - 1 -> { - CardView(ctx).addTo(this) { - elevation = ctx.resources.getDimension(R.d.app_elevation) - setCardBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.colorBackgroundFloating)) - radius = 16.dp.toFloat() - - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - startToStart = PARENT_ID - endToEnd = PARENT_ID - topToTop = PARENT_ID - bottomToBottom = PARENT_ID - } - - TextView(ctx, null, 0, R.i.UiKit_TextView_H2).addTo(this) { - setText(R.h.spoiler) - isAllCaps = true - setPadding(8.dp, 4.dp, 8.dp, 4.dp) - setTextColor(ColorCompat.getThemedColor(ctx, R.b.colorTextNormal)) - layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - marginStart = 4.dp - marginEnd = 4.dp - } - } - } - } - 2 -> { - ImageView(ctx).addTo(this) { - setImageResource(R.e.ic_spoiler) - layoutParams = LayoutParams(0, 0).apply { - startToStart = PARENT_ID - endToEnd = PARENT_ID - topToTop = PARENT_ID - bottomToBottom = PARENT_ID - dimensionRatio = "1:1" - matchConstraintPercentWidth = 0.5f - } - } - } - else -> throw IllegalArgumentException("Invalid spoiler view type") - } - } - - fun configure(entry: BotUiComponentV2Entry, component: SpoilableMessageComponent, key: String? = null) { - configure(component.spoiler, entry.state, entry.message.id, Pair(component.id, key)) - } - - fun configure( - isSpoiler: Boolean, - state: StoreMessageState.State?, - messageId: Long, - key: Pair, - ) { - val (id, strKey) = key - val spoiled = if (strKey != null) - state?.visibleSpoilerEmbedMap?.get(id)?.contains(strKey) ?: false - else - state?.visibleSpoilerEmbedMap?.containsKey(id) ?: false - - spoilerView.setOnClickListener { - spoilerView.setOnClickListener(null) - spoilerView.animate() - .withEndAction { - if (strKey != null) - StoreStream.getMessageState().revealSpoilerEmbedData(messageId, id, strKey) - else - StoreStream.getMessageState().revealSpoilerEmbed(messageId, id) - } - .alpha(0f) - } - spoilerView.visibility = if (isSpoiler && !spoiled) VISIBLE else GONE - spoilerView.alpha = 1f - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/TextDisplayComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/TextDisplayComponentView.kt deleted file mode 100644 index 8db35ca..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/TextDisplayComponentView.kt +++ /dev/null @@ -1,86 +0,0 @@ -@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") - -package com.aliucord.coreplugins.componentsv2.views - -import android.content.Context -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import androidx.appcompat.view.ContextThemeWrapper -import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.Logger -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.TextDisplayMessageComponent -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.discord.stores.StoreStream -import com.discord.utilities.color.ColorCompat -import com.discord.utilities.message.MessageUtils -import com.discord.utilities.textprocessing.* -import com.discord.utilities.textprocessing.node.SpoilerNode -import com.discord.utilities.view.text.LinkifiedTextView -import com.discord.widgets.botuikit.ComponentProvider -import com.discord.widgets.botuikit.views.ComponentActionListener -import com.discord.widgets.botuikit.views.ComponentView -import com.discord.widgets.chat.list.adapter.* -import com.lytefast.flexinput.R - -class TextDisplayComponentView(ctx: Context) : ConstraintLayout(ctx), ComponentView { - override fun type() = ComponentV2Type.TEXT_DISPLAY - - private val textView = LinkifiedTextView(ContextThemeWrapper(ctx, R.i.UiKit_Chat_Text)).addTo(this) { - layoutParams = LayoutParams(0, WRAP_CONTENT).apply { - topMargin = 2.dp - bottomMargin = 2.dp - } - } - - override fun configure(component: TextDisplayMessageComponent, provider: ComponentProvider, listener: ComponentActionListener) { - val item = listener as WidgetChatListAdapterItemBotComponentRow - val entry = item.entry - if (entry !is BotUiComponentV2Entry) { - Logger("ComponentsV2").warn("configured text display with non-v2 entry") - return - } - - render(component.id, component.content, item.adapter, entry) - } - - private fun render(id: Int, content: String, adapter: WidgetChatListAdapter, entry: BotUiComponentV2Entry) { - val data = adapter.data - @Suppress("UNCHECKED_CAST") - val spoilers = entry.state?.visibleSpoilerEmbedMap?.let { - WidgetChatListAdapterItemEmbed.Companion.`access$getEmbedFieldVisibleIndices`( - WidgetChatListAdapterItemEmbed.Companion, - it, - id, - "comp" - ) - } as List? - val processor = MessagePreprocessor(entry.meId, spoilers, null, false, 50) - val nickOrUsernames = MessageUtils.getNickOrUsernames(entry.message, entry.channel, entry.guildMembers, entry.channel.q()) - val parseChannelMessage = DiscordParser.parseChannelMessage( - context, - content, - MessageRenderContext( - context, - entry.meId, - false, - nickOrUsernames, - StoreStream.getChannels().channelNames, // TODO, does not change - entry.guildRoles, - R.b.colorTextLink, - `WidgetChatListAdapterItemMessage$getMessageRenderContext$1`.INSTANCE, - { s: String -> adapter.eventHandler.onUrlLongClicked(s) }, - ColorCompat.getThemedColor(context, R.b.theme_chat_spoiler_bg), - ColorCompat.getThemedColor(context, R.b.theme_chat_spoiler_bg_visible), - { node: SpoilerNode<*> -> StoreStream.getMessageState().revealSpoilerEmbedData(entry.message.id, id, "comp:${node.id}") }, - { l: Long -> adapter.eventHandler.onUserMentionClicked(l, data.channelId, data.guildId) }, - `WidgetChatListAdapterItemMessage$getMessageRenderContext$4`(context) - ), - processor, - DiscordParser.ParserOptions.DEFAULT, - false - ) - textView.setDraweeSpanStringBuilder(parseChannelMessage); - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/ThumbnailComponentView.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/ThumbnailComponentView.kt deleted file mode 100644 index 1c043dd..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/coreplugins/componentsv2/views/ThumbnailComponentView.kt +++ /dev/null @@ -1,95 +0,0 @@ -@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") - -package com.aliucord.coreplugins.componentsv2.views - -import android.content.Context -import android.view.View -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout -import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.Logger -import com.aliucord.coreplugins.componentsv2.BotUiComponentV2Entry -import com.aliucord.coreplugins.componentsv2.ComponentV2Type -import com.aliucord.coreplugins.componentsv2.models.ThumbnailMessageComponent -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.discord.utilities.color.ColorCompat -import com.discord.utilities.embed.EmbedResourceUtils -import com.discord.utilities.images.MGImages -import com.discord.widgets.botuikit.ComponentProvider -import com.discord.widgets.botuikit.views.ComponentActionListener -import com.discord.widgets.botuikit.views.ComponentView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemBotComponentRow -import com.facebook.drawee.view.SimpleDraweeView -import com.google.android.material.card.MaterialCardView -import com.lytefast.flexinput.R - -class ThumbnailComponentView(ctx: Context) : ConstraintLayout(ctx), ComponentView { - override fun type() = ComponentV2Type.THUMBNAIL - - private val embedThumbnailMaxSize = (ctx.resources.getDimension(R.d.embed_thumbnail_max_size) * 1.5).toInt() - - companion object { - private val imageViewId = View.generateViewId() - } - private lateinit var imageView: SimpleDraweeView - private lateinit var spoilerView: SpoilerView - - init { - MaterialCardView(ctx).addTo(this) { - radius = 8.dp.toFloat() - elevation = 0f - setCardBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.colorBackgroundPrimary)) - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT) - ConstraintLayout(ctx).addTo(this) { - layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT) - imageView = SimpleDraweeView(ctx, null, 0, R.i.UiKit_ImageView).addTo(this) { - id = imageViewId - } - spoilerView = SpoilerView(ctx, 2).addTo(this) { - layoutParams = SpoilerView.constraintLayoutParamsAround(imageViewId) - } - } - } - } - - // Reference: WidgetChatListAdapterItemEmbed.configureEmbedThumbnail - override fun configure(component: ThumbnailMessageComponent, provider: ComponentProvider, listener: ComponentActionListener) { - val item = listener as WidgetChatListAdapterItemBotComponentRow - val entry = item.entry - if (entry !is BotUiComponentV2Entry) { - Logger("ComponentsV2").warn("configured thumbnail with non-v2 entry") - return - } - - val (width, height) = EmbedResourceUtils.INSTANCE.calculateScaledSize( - component.media.width, - component.media.height, - embedThumbnailMaxSize, - embedThumbnailMaxSize, - resources, - 0 - ) - imageView.apply { - if (layoutParams.width != width || layoutParams.height != height) - layoutParams = layoutParams.apply { - this.width = width - this.height = height - } - MGImages.`setImage$default`( - this, - EmbedResourceUtils.INSTANCE.getPreviewUrls(component.media.proxyUrl, width, height, true), // z2: shouldAnimate - 0, - 0, - false, - null, - null, - null, - 252, - null - ) - } - - spoilerView.configure(entry, component) - } -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/aliucord/utils/ViewUtils.kt b/canary/ComponentsV2/src/main/kotlin/com/aliucord/utils/ViewUtils.kt deleted file mode 100644 index 2ed27e1..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/aliucord/utils/ViewUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.aliucord.utils - -import android.view.View -import android.view.ViewGroup -import androidx.constraintlayout.widget.ConstraintLayout -import com.discord.views.CheckedSetting - -object ViewUtils { - /** - * Shorthand extension function to add a View into a ViewGroup, and then - * run a scoped function - * - * @param group ViewGroup to add this View into - * @param block A scoped function, with the View as its receiver - * @return The View - */ - fun T.addTo(group: ViewGroup, block: (T.() -> Unit)? = null): T = apply { group.addView(this); block?.invoke(this) } - - /** - * Shorthand extension function to add a View into a ViewGroup at specified - * index, and then run a scoped function - * - * @param group ViewGroup to add this View into - * @param index Index to insert this View at - * @param block A scoped function, with the View as its receiver - * @return The View - */ - fun T.addTo(group: ViewGroup, index: Int, block: (T.() -> Unit)? = null): T = apply { group.addView(this, index); block?.invoke(this) } - - /** Main layout of the setting */ - val CheckedSetting.layout get() = l.b() as ConstraintLayout - - /** Main text/label of the setting */ - val CheckedSetting.label get() = l.a() - - /** Checkbox button at the end of the setting */ - val CheckedSetting.checkbox get() = l.c() - - /** Subtext of the setting */ - val CheckedSetting.subtext get() = l.f() -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ChannelSelectComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ChannelSelectComponent.kt deleted file mode 100644 index 05ec6a2..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ChannelSelectComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.discord.api.botuikit - -data class ChannelSelectComponent( - private val type: ComponentType, - override val id: Int, - @b.i.d.p.b("custom_id") override val customId: String, - override val placeholder: String, - override val defaultValues: List?, - override val minValues: Int, - override val maxValues: Int, - override val disabled: Boolean, -) : SelectV2Component() { - override fun getType() = type -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ContainerComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ContainerComponent.kt deleted file mode 100644 index a52183b..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ContainerComponent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.discord.api.botuikit - -data class ContainerComponent( - private val type: ComponentType, - val id: Int, - val components: List, - @b.i.d.p.b("accent_color") val accentColor: Int?, - val spoiler: Boolean, -): LayoutComponent() { - override fun getType() = type - override fun a() = components -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ContentComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ContentComponent.kt deleted file mode 100644 index f1e520b..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ContentComponent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.discord.api.botuikit - -import java.io.Serializable - -abstract class ContentComponent : LayoutComponent(), Serializable { - final override fun a(): List = listOf() -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/FileComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/FileComponent.kt deleted file mode 100644 index 14b7701..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/FileComponent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.discord.api.botuikit - -data class FileComponent( - private val type: ComponentType, - val id: Int, - val file: UnfurledMediaItem, - val spoiler: Boolean, - val name: String, - val size: Int, -) : ContentComponent() { - override fun getType() = type -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MediaGalleryComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MediaGalleryComponent.kt deleted file mode 100644 index f14a0d2..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MediaGalleryComponent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.discord.api.botuikit - -data class MediaGalleryComponent( - private val type: ComponentType, - val id: Int, - val items: List, -) : ContentComponent() { - override fun getType() = type -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MediaGalleryItem.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MediaGalleryItem.kt deleted file mode 100644 index 3cd1390..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MediaGalleryItem.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.discord.api.botuikit - -data class MediaGalleryItem( - val media: UnfurledMediaItem, - val description: String?, - val spoiler: Boolean, -) diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MentionableSelectComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MentionableSelectComponent.kt deleted file mode 100644 index 7e76023..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/MentionableSelectComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.discord.api.botuikit - -data class MentionableSelectComponent( - private val type: ComponentType, - override val id: Int, - @b.i.d.p.b("custom_id") override val customId: String, - override val placeholder: String, - override val defaultValues: List?, - override val minValues: Int, - override val maxValues: Int, - override val disabled: Boolean, -) : SelectV2Component() { - override fun getType() = type -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/RoleSelectComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/RoleSelectComponent.kt deleted file mode 100644 index eca7f18..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/RoleSelectComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.discord.api.botuikit - -data class RoleSelectComponent( - private val type: ComponentType, - override val id: Int, - @b.i.d.p.b("custom_id") override val customId: String, - override val placeholder: String, - override val defaultValues: List?, - override val minValues: Int, - override val maxValues: Int, - override val disabled: Boolean, -) : SelectV2Component() { - override fun getType() = type -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SectionComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SectionComponent.kt deleted file mode 100644 index 3175618..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SectionComponent.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.discord.api.botuikit - -data class SectionComponent( - private val type: ComponentType, - val id: Int, - val components: List, - val accessory: Component, -): LayoutComponent() { - override fun getType() = type - - // This property will be accessed by ComponentStateMapper to be processed into MessageComponents, - // so we pass in the accessory component to be processed too. - // Back in SectionMessageComponent.mergeToMessageComponent, we will separate this back correctly. - override fun a() = components + accessory -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2Component.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2Component.kt deleted file mode 100644 index 4c22334..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2Component.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.discord.api.botuikit - -abstract class SelectV2Component() : ActionComponent() { - abstract val id: Int - abstract val customId: String - abstract val placeholder: String - abstract val defaultValues: List? - abstract val minValues: Int - abstract val maxValues: Int - abstract val disabled: Boolean -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2DefaultValue.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2DefaultValue.kt deleted file mode 100644 index c6f6992..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2DefaultValue.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.discord.api.botuikit - -data class SelectV2DefaultValue( - val id: Long, - val type: SelectV2DefaultValueType, -) diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2DefaultValueType.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2DefaultValueType.kt deleted file mode 100644 index 2caae0e..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SelectV2DefaultValueType.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.discord.api.botuikit - -enum class SelectV2DefaultValueType { - @b.i.d.p.b("user") USER, - @b.i.d.p.b("role") ROLE, - @b.i.d.p.b("channel") CHANNEL, -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SeparatorComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SeparatorComponent.kt deleted file mode 100644 index b773f49..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/SeparatorComponent.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.discord.api.botuikit - -data class SeparatorComponent( - private val type: ComponentType, - val id: Int, - val divider: Boolean, - val spacing: Int, // 1 = small padding, 2 = large padding -): LayoutComponent() { - override fun getType() = type - override fun a(): List = listOf() -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/TextDisplayComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/TextDisplayComponent.kt deleted file mode 100644 index c8d190d..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/TextDisplayComponent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.discord.api.botuikit - -data class TextDisplayComponent( - private val type: ComponentType, - val id: Int, - val content: String, -) : ContentComponent() { - override fun getType() = type -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ThumbnailComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ThumbnailComponent.kt deleted file mode 100644 index 7fbea89..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/ThumbnailComponent.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.discord.api.botuikit - -data class ThumbnailComponent( - private val type: ComponentType, - val id: Int, - val media: UnfurledMediaItem, - val description: String?, - val spoiler: Boolean, -) : ContentComponent() { - override fun getType() = type -} diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/UnfurledMediaItem.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/UnfurledMediaItem.kt deleted file mode 100644 index d9bdc8d..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/UnfurledMediaItem.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.discord.api.botuikit - -data class UnfurledMediaItem( - val url: String, - @b.i.d.p.b("proxy_url") val proxyUrl: String, - val height: Int, - val width: Int, - @b.i.d.p.b("content_type") val contentType: String?, - @b.i.d.p.b("attachment_id") val attachmentId: Long?, -) diff --git a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/UserSelectComponent.kt b/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/UserSelectComponent.kt deleted file mode 100644 index fc0a370..0000000 --- a/canary/ComponentsV2/src/main/kotlin/com/discord/api/botuikit/UserSelectComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.discord.api.botuikit - -data class UserSelectComponent( - private val type: ComponentType, - override val id: Int, - @b.i.d.p.b("custom_id") override val customId: String, - override val placeholder: String, - override val defaultValues: List?, - override val minValues: Int, - override val maxValues: Int, - override val disabled: Boolean, -) : SelectV2Component() { - override fun getType() = type -} diff --git a/canary/LICENSE b/canary/LICENSE deleted file mode 100644 index 95230e3..0000000 --- a/canary/LICENSE +++ /dev/null @@ -1,172 +0,0 @@ -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of -authorship (the "Original Work") whose owner (the "Licensor") has placed the -following licensing notice adjacent to the copyright notice for the Original -Work: - -Licensed under the Open Software License version 3.0 - -1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, -non-exclusive, sublicensable license, for the duration of the copyright, to do -the following: - - a) to reproduce the Original Work in copies, either alone or as part of a - collective work; - - b) to translate, adapt, alter, transform, modify, or arrange the Original - Work, thereby creating derivative works ("Derivative Works") based upon the - Original Work; - - c) to distribute or communicate copies of the Original Work and Derivative - Works to the public, with the proviso that copies of Original Work or - Derivative Works that You distribute or communicate shall be licensed under - this Open Software License; - - d) to perform the Original Work publicly; and - - e) to display the Original Work publicly. - -2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, -non-exclusive, sublicensable license, under patent claims owned or controlled -by the Licensor that are embodied in the Original Work as furnished by the -Licensor, for the duration of the patents, to make, use, sell, offer for sale, -have made, and import the Original Work and Derivative Works. - -3) Grant of Source Code License. The term "Source Code" means the preferred -form of the Original Work for making modifications to it and all available -documentation describing how to modify the Original Work. Licensor agrees to -provide a machine-readable copy of the Source Code of the Original Work along -with each copy of the Original Work that Licensor distributes. Licensor -reserves the right to satisfy this obligation by placing a machine-readable -copy of the Source Code in an information repository reasonably calculated to -permit inexpensive and convenient access by You for as long as Licensor -continues to distribute the Original Work. - -4) Exclusions From License Grant. Neither the names of Licensor, nor the names -of any contributors to the Original Work, nor any of their trademarks or -service marks, may be used to endorse or promote products derived from this -Original Work without express prior permission of the Licensor. Except as -expressly stated herein, nothing in this License grants any license to -Licensor's trademarks, copyrights, patents, trade secrets or any other -intellectual property. No patent license is granted to make, use, sell, offer -for sale, have made, or import embodiments of any patent claims other than the -licensed claims defined in Section 2. No license is granted to the trademarks -of Licensor even if such marks are included in the Original Work. Nothing in -this License shall be interpreted to prohibit Licensor from licensing under -terms different from this License any Original Work that Licensor otherwise -would have a right to license. - -5) External Deployment. The term "External Deployment" means the use, -distribution, or communication of the Original Work or Derivative Works in any -way such that the Original Work or Derivative Works may be used by anyone -other than You, whether those works are distributed or communicated to those -persons or made available as an application intended for use over a network. -As an express condition for the grants of license hereunder, You must treat -any External Deployment by You of the Original Work or a Derivative Work as a -distribution under section 1(c). - -6) Attribution Rights. You must retain, in the Source Code of any Derivative -Works that You create, all copyright, patent, or trademark notices from the -Source Code of the Original Work, as well as any notices of licensing and any -descriptive text identified therein as an "Attribution Notice." You must cause -the Source Code for any Derivative Works that You create to carry a prominent -Attribution Notice reasonably calculated to inform recipients that You have -modified the Original Work. - -7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that -the copyright in and to the Original Work and the patent rights granted herein -by Licensor are owned by the Licensor or are sublicensed to You under the -terms of this License with the permission of the contributor(s) of those -copyrights and patent rights. Except as expressly stated in the immediately -preceding sentence, the Original Work is provided under this License on an "AS -IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without -limitation, the warranties of non-infringement, merchantability or fitness for -a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK -IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this -License. No license to the Original Work is granted by this License except -under this disclaimer. - -8) Limitation of Liability. Under no circumstances and under no legal theory, -whether in tort (including negligence), contract, or otherwise, shall the -Licensor be liable to anyone for any indirect, special, incidental, or -consequential damages of any character arising as a result of this License or -the use of the Original Work including, without limitation, damages for loss -of goodwill, work stoppage, computer failure or malfunction, or any and all -other commercial damages or losses. This limitation of liability shall not -apply to the extent applicable law prohibits such limitation. - -9) Acceptance and Termination. If, at any time, You expressly assented to this -License, that assent indicates your clear and irrevocable acceptance of this -License and all of its terms and conditions. If You distribute or communicate -copies of the Original Work or a Derivative Work, You must make a reasonable -effort under the circumstances to obtain the express assent of recipients to -the terms of this License. This License conditions your rights to undertake -the activities listed in Section 1, including your right to create Derivative -Works based upon the Original Work, and doing so without honoring these terms -and conditions is prohibited by copyright law and international treaty. -Nothing in this License is intended to affect copyright exceptions and -limitations (including "fair use" or "fair dealing"). This License shall -terminate immediately and You may no longer exercise any of the rights granted -to You by this License upon your failure to honor the conditions in Section -1(c). - -10) Termination for Patent Action. This License shall terminate automatically -and You may no longer exercise any of the rights granted to You by this -License as of the date You commence an action, including a cross-claim or -counterclaim, against Licensor or any licensee alleging that the Original Work -infringes a patent. This termination provision shall not apply for an action -alleging patent infringement by combinations of the Original Work with other -software or hardware. - -11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this -License may be brought only in the courts of a jurisdiction wherein the -Licensor resides or in which Licensor conducts its primary business, and under -the laws of that jurisdiction excluding its conflict-of-law provisions. The -application of the United Nations Convention on Contracts for the -International Sale of Goods is expressly excluded. Any use of the Original -Work outside the scope of this License or after its termination shall be -subject to the requirements and penalties of copyright or patent law in the -appropriate jurisdiction. This section shall survive the termination of this -License. - -12) Attorneys' Fees. In any action to enforce the terms of this License or -seeking damages relating thereto, the prevailing party shall be entitled to -recover its costs and expenses, including, without limitation, reasonable -attorneys' fees and costs incurred in connection with such action, including -any appeal of such action. This section shall survive the termination of this -License. - -13) Miscellaneous. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent necessary -to make it enforceable. - -14) Definition of "You" in This License. "You" throughout this License, -whether in upper or lower case, means an individual or a legal entity -exercising rights under, and complying with all of the terms of, this License. -For legal entities, "You" includes any entity that controls, is controlled by, -or is under common control with you. For purposes of this definition, -"control" means (i) the power, direct or indirect, to cause the direction or -management of such entity, whether by contract or otherwise, or (ii) ownership -of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial -ownership of such entity. - -15) Right to Use. You may use the Original Work in all ways not otherwise -restricted or conditioned by this License or by law, and Licensor promises not -to interfere with or be responsible for such uses by You. - -16) Modification of This License. This License is Copyright © 2005 Lawrence -Rosen. Permission is granted to copy, distribute, or communicate this License -without modification. Nothing in this License permits You to modify this -License as applied to the Original Work or to Derivative Works. However, You -may modify the text of this License and copy, distribute or communicate your -modified version (the "Modified License") and apply it to other original works -of authorship subject to the following conditions: (i) You may not indicate in -any way that your Modified License is the "Open Software License" or "OSL" and -you may not use those names in the name of your Modified License; (ii) You -must replace the notice specified in the first paragraph above with the notice -"Licensed under " or with a notice of your own -that is not confusingly similar to the notice in this License; and (iii) You -may not claim that your original works are open source software unless your -Modified License has been approved by Open Source Initiative (OSI) and You -comply with its license review and certification process. diff --git a/canary/SlashCommandsFix/build.gradle.kts b/canary/SlashCommandsFix/build.gradle.kts deleted file mode 100644 index 4ace2f2..0000000 --- a/canary/SlashCommandsFix/build.gradle.kts +++ /dev/null @@ -1,52 +0,0 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - -version = "8.18.0" -description = "Beta backport of SlashCommandsFix" - -aliucord { - changelog.set(""" - # 8.18.0 - * Don't use custom props anymore (core has them) - - # 7.16.2 - * Use new props - - # 7.16.1 - * Prompt restarts - - # 7.16.0 - * Initial port >w< thanks @jedenastka - """.trimIndent()) - - deploy.set(true) -} - -apply { - plugin(libs.plugins.shadow.get().pluginId) -} - -val shadowDir = File(buildDir, "intermediates/shadowed") - -tasks.register("relocateJar") { - val javaTask = tasks.findByName("compileDebugJavaWithJavac")!! - val kotlinTask = tasks.findByName("compileDebugKotlin")!! - from(javaTask.outputs, kotlinTask.outputs) - relocate("com.aliucord.coreplugins.slashcommandsfix", "moe.lava.corenary.slashcommandsfix") - archiveClassifier.set("shadowed") - destinationDirectory.set(File(buildDir, "intermediates")) -} - -tasks.register("copyShadowed") { - val reloc = tasks.findByName("relocateJar")!! as ShadowJar - dependsOn(reloc) - from(zipTree(reloc.archiveFile)) - into(shadowDir) -} - -project.afterEvaluate { - tasks.compileDex { - val copyShadowed = tasks.findByName("copyShadowed")!! as Sync - dependsOn(copyShadowed) - input.setFrom(shadowDir) - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplication.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplication.java deleted file mode 100644 index d1778d9..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplication.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import com.discord.models.user.User; -import com.discord.stores.StoreStream; -import java.util.Optional; - -class ApiApplication { - public final long id; - public final String name; - public final String icon; - public final ApiPermissions permissions; - public final Long botId; - - public ApiApplication() { - this.id = 0; - this.name = null; - this.icon = null; - this.permissions = null; - this.botId = null; - } - - public Application toModel() { - Permissions permissions = null; - if (this.permissions != null) { - permissions = this.permissions.toModel(Optional.empty()); - } else { - permissions = new Permissions(null, null, null, null); - } - var usersStore = StoreStream.getUsers(); - Optional botUser = Optional.ofNullable(this.botId).map(userId -> usersStore.getUsers().get(userId)); - return new Application(this.id, this.name, this.icon, permissions, botUser); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplicationCommand.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplicationCommand.java deleted file mode 100644 index 62e129b..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplicationCommand.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import com.discord.models.commands.ApplicationCommand; -import com.discord.stores.StoreApplicationCommandsKt; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -class ApiApplicationCommand { - public final long id; - public final long applicationId; - public final String name; - public final String description; - public final List options; - public final ApiPermissions permissions; - public final Long defaultMemberPermissions; - public final Long guildId; - public final String version; - public final int type; - - public ApiApplicationCommand() { - this.id = 0; - this.applicationId = 0; - this.name = null; - this.description = null; - this.options = null; - this.permissions = null; - this.defaultMemberPermissions = null; - this.guildId = null; - this.version = null; - this.type = 0; - } - - public RemoteApplicationCommand toModel() { - var apiOptions = this.options; - if (apiOptions == null) { - apiOptions = new ArrayList<>(); - } - var options = apiOptions - .stream() - .map(option -> StoreApplicationCommandsKt.toSlashCommandOption(option)) - .collect(Collectors.toList()); - Permissions permissions = null; - var defaultMemberPermissions = Optional.ofNullable(this.defaultMemberPermissions); - if (this.permissions != null) { - permissions = this.permissions.toModel(defaultMemberPermissions); - } else { - permissions = new Permissions(null, null, null, defaultMemberPermissions); - } - return new RemoteApplicationCommand(String.valueOf(this.id), this.applicationId, this.name, this.description, options, permissions, this.guildId, this.version, this.type); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplicationIndex.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplicationIndex.java deleted file mode 100644 index 188b6c1..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiApplicationIndex.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -class ApiApplicationIndex { - public List applications; - public List applicationCommands; - - public ApiApplicationIndex() { - this.applications = null; - this.applicationCommands = null; - } - - public ApplicationIndex toModel() { - var applications = new HashMap(); - for (var application: this.applications) { - applications.put(application.id, application.toModel()); - } - var applicationCommands = new HashMap(); - for (var applicationCommand: this.applicationCommands) { - applicationCommands.put(applicationCommand.id, applicationCommand.toModel()); - } - - return new ApplicationIndex(applications, applicationCommands); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiGuildApplicationCommandIndexUpdate.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiGuildApplicationCommandIndexUpdate.java deleted file mode 100644 index 91c5b4e..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiGuildApplicationCommandIndexUpdate.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -class ApiGuildApplicationCommandIndexUpdate { - public long guildId; - - public ApiGuildApplicationCommandIndexUpdate() { - this.guildId = 0; - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiPermissions.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiPermissions.java deleted file mode 100644 index 2bbe6e8..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApiPermissions.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.util.Map; -import java.util.Optional; - -class ApiPermissions { - public Boolean user; - public Map roles; - public Map channels; - - public ApiPermissions() { - this.user = null; - this.roles = null; - this.channels = null; - } - - public Permissions toModel(Optional defaultMemberPermissions) { - return new Permissions(Optional.ofNullable(user), roles, channels, defaultMemberPermissions); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Application.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Application.java deleted file mode 100644 index 67ecf2b..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Application.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import com.aliucord.Logger; -import com.discord.models.user.User; -import com.discord.utilities.user.UserUtils; -import java.util.Optional; - -class Application extends com.discord.models.commands.Application { - public Permissions permissions_; - - public Application(long id, String name, String icon, Permissions permissions, Optional botUser) { - super(id, name, icon, null, -1, botUser.map(user -> UserUtils.INSTANCE.synthesizeApiUser(user)).orElse(null), false); - this.permissions_ = permissions; - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndex.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndex.java deleted file mode 100644 index a3fab96..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndex.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.lang.IllegalAccessException; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -class ApplicationIndex { - public Map applications; - public Map applicationCommands; - - public ApplicationIndex(Map applications, Map applicationCommands) { - this.applications = applications; - this.applicationCommands = applicationCommands; - } - - public ApplicationIndex(List applicationIndexes) { - this.applications = new HashMap(); - this.applicationCommands = new HashMap(); - for (var applicationIndex: applicationIndexes) { - this.applications.putAll(applicationIndex.applications); - this.applicationCommands.putAll(applicationIndex.applicationCommands); - } - } - - public void populateCommandCounts(Field applicationCommandCountField) throws IllegalAccessException { - var applicationCommandCounts = new HashMap(); - for (var applicationCommand: this.applicationCommands.values()) { - var count = applicationCommandCounts.getOrDefault(applicationCommand.getApplicationId(), 0); - count += 1; - applicationCommandCounts.put(applicationCommand.getApplicationId(), count); - } - for (var application: this.applications.values()) { - applicationCommandCountField.setInt(application, applicationCommandCounts.getOrDefault(application.getId(), 0)); - } - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexCache.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexCache.java deleted file mode 100644 index 87022ce..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexCache.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -class ApplicationIndexCache { - public Map guild; - public Map dm; - public Optional user; - - public ApplicationIndexCache() { - this.guild = new HashMap<>(); - this.dm = new HashMap<>(); - this.user = Optional.empty(); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSource.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSource.java deleted file mode 100644 index 84b11a4..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSource.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.util.Optional; - -interface ApplicationIndexSource { - String getEndpoint(); - Optional getFromCache(ApplicationIndexCache cache); - void insertIntoCache(ApplicationIndexCache cache, ApplicationIndex index); - void removeFromCache(ApplicationIndexCache cache); -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceDm.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceDm.java deleted file mode 100644 index 3fb0c94..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceDm.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.util.Optional; - -class ApplicationIndexSourceDm implements ApplicationIndexSource { - long channelId; - - public ApplicationIndexSourceDm(long channelId) { - this.channelId = channelId; - } - - @Override - public String getEndpoint() { - return String.format("/channels/%d/application-command-index", this.channelId); - } - - @Override - public Optional getFromCache(ApplicationIndexCache cache) { - return Optional.ofNullable( - cache.dm.get(this.channelId) - ); - } - - @Override - public void insertIntoCache(ApplicationIndexCache cache, ApplicationIndex index) { - cache.dm.put(this.channelId, index); - } - - @Override - public void removeFromCache(ApplicationIndexCache cache) { - cache.dm.remove(this.channelId); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceGuild.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceGuild.java deleted file mode 100644 index 5588134..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceGuild.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.util.Map; -import java.util.Optional; - -class ApplicationIndexSourceGuild implements ApplicationIndexSource { - long guildId; - - public ApplicationIndexSourceGuild(long guildId) { - this.guildId = guildId; - } - - @Override - public String getEndpoint() { - return String.format("/guilds/%d/application-command-index", this.guildId); - } - - @Override - public Optional getFromCache(ApplicationIndexCache cache) { - return Optional.ofNullable( - cache.guild.get(this.guildId) - ); - } - - @Override - public void insertIntoCache(ApplicationIndexCache cache, ApplicationIndex index) { - cache.guild.put(this.guildId, index); - } - - @Override - public void removeFromCache(ApplicationIndexCache cache) { - cache.guild.remove(this.guildId); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceUser.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceUser.java deleted file mode 100644 index 19cbf66..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ApplicationIndexSourceUser.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import java.util.Optional; - -class ApplicationIndexSourceUser implements ApplicationIndexSource { - public ApplicationIndexSourceUser() {} - - @Override - public String getEndpoint() { - return "/users/@me/application-command-index"; - } - - @Override - public Optional getFromCache(ApplicationIndexCache cache) { - return cache.user; - } - - @Override - public void insertIntoCache(ApplicationIndexCache cache, ApplicationIndex index) { - cache.user = Optional.of(index); - } - - @Override - public void removeFromCache(ApplicationIndexCache cache) { - cache.user = Optional.empty(); - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ConflictCheck.kt b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ConflictCheck.kt deleted file mode 100644 index 7b3bf0c..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/ConflictCheck.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.aliucord.coreplugins.slashcommandsfix - -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import com.aliucord.* -import com.aliucord.fragments.ConfirmDialog -import java.io.File -import kotlin.system.exitProcess - -object ConflictCheck { - @SuppressLint("SetTextI18n") - @JvmStatic - fun run(context: Context): Boolean { - val hasFix = PluginManager.plugins.containsKey("SlashCommandsFix") - val hasForcedFix = PluginManager.plugins.containsKey("ForceSlashCommandsFixNOW") - val fromStorage = Main.settings.getBool("AC_from_storage", false) - - if (hasFix) { - Logger("SlashCommandsFixBeta").warn("conflict detected") - if (hasForcedFix || fromStorage) { - Utils.threadPool.execute { - Thread.sleep(5000) // wait for app to load guh - Utils.mainThread.post { - val dialog = ConfirmDialog() - dialog - .setTitle("SlashCommandsFix Conflict") - .setDescription("You have another variant of SlashCommandsFix installed. Do you want to disable it?") - .setIsDangerous(true) - .setOnOkListener { - File(context.codeCacheDir, "Aliucord.zip").delete() - if (fromStorage) - Main.settings.setBool("AC_from_storage", false) - if (hasForcedFix) - PluginManager.disablePlugin("ForceSlashCommandsFixNOW") - val ctx = it.context - val intent = ctx.packageManager.getLaunchIntentForPackage(ctx.packageName) - Utils.appActivity.startActivity(Intent.makeRestartActivityTask(intent!!.component)) - exitProcess(0) - } - .apply { isCancelable = false } - .show(Utils.appActivity.supportFragmentManager, "SlashCommandsFix conflict") - } - } - } else { - Logger("SlashCommandsFixBeta").warn("removing myself... bye!") - File("${Constants.PLUGINS_PATH}/SlashCommandsFixBeta.zip").delete() - } - } - - return hasFix - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Patches.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Patches.java deleted file mode 100644 index 740e528..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Patches.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import android.content.Context; -import android.util.Base64; - -import com.aliucord.api.GatewayAPI; -import com.aliucord.Http; -import com.aliucord.Logger; -import com.aliucord.patcher.InsteadHook; -import com.aliucord.patcher.Patcher; -import com.aliucord.patcher.PreHook; -import com.aliucord.Utils; -import com.aliucord.utils.GsonUtils; -import com.aliucord.utils.RNSuperProperties; -import com.discord.api.channel.Channel; -import com.discord.models.commands.Application; -import com.discord.models.commands.ApplicationCommand; -import com.discord.models.commands.ApplicationCommandKt; -import com.discord.models.commands.ApplicationCommandLocalSendData; -import com.discord.stores.BuiltInCommandsProvider; -import com.discord.stores.StoreApplicationCommands; -import com.discord.stores.StoreApplicationCommands$requestApplicationCommands$1; -import com.discord.stores.StoreApplicationCommands$requestApplicationCommandsQuery$1; -import com.discord.stores.StoreApplicationInteractions; -import com.discord.stores.StoreChannelsSelected; -import com.discord.stores.StoreStream; -import com.discord.utilities.error.Error; -import com.discord.utilities.messagesend.MessageResult; -import com.discord.utilities.permissions.PermissionUtils; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import kotlin.jvm.functions.Function0; -import kotlin.jvm.functions.Function1; - -final class Patches { - private ApplicationIndexCache applicationIndexCache; - private Logger logger; - private Method handleGuildApplicationsUpdateMethod; - private Method handleDiscoverCommandsUpdateMethod; - private Method handleQueryCommandsUpdateMethod; - private Field applicationCommandCountField; - private Field storeApplicationCommandsQueryField; - private Field errorResponseErrorField; - private Field skemaErrorSubErrorsField; - private Field skemaErrorErrorsField; - private Field skemaErrorItemCodeField; - private Field skemaErrorItemMessageField; - private Field storeApplicationCommandsBuiltInCommandsProviderField; - - Patches(Logger logger) throws Throwable { - this.logger = logger; - this.applicationIndexCache = new ApplicationIndexCache(); - } - - @SuppressWarnings("unchecked") - public void loadPatches(Context context) throws Throwable { - this.handleGuildApplicationsUpdateMethod = StoreApplicationCommands.class.getDeclaredMethod("handleGuildApplicationsUpdate", List.class); - this.handleGuildApplicationsUpdateMethod.setAccessible(true); - this.handleDiscoverCommandsUpdateMethod = StoreApplicationCommands.class.getDeclaredMethod("handleDiscoverCommandsUpdate", List.class); - this.handleDiscoverCommandsUpdateMethod.setAccessible(true); - this.handleQueryCommandsUpdateMethod = StoreApplicationCommands.class.getDeclaredMethod("handleQueryCommandsUpdate", List.class); - this.handleQueryCommandsUpdateMethod.setAccessible(true); - this.applicationCommandCountField = Application.class.getDeclaredField("commandCount"); - this.applicationCommandCountField.setAccessible(true); - this.storeApplicationCommandsQueryField = StoreApplicationCommands.class.getDeclaredField("query"); - this.storeApplicationCommandsQueryField.setAccessible(true); - this.errorResponseErrorField = Error.Response.class.getDeclaredField("skemaError"); - this.errorResponseErrorField.setAccessible(true); - this.skemaErrorSubErrorsField = Error.SkemaError.class.getDeclaredField("subErrors"); - this.skemaErrorSubErrorsField.setAccessible(true); - this.skemaErrorErrorsField = Error.SkemaError.class.getDeclaredField("errors"); - this.skemaErrorErrorsField.setAccessible(true); - this.skemaErrorItemCodeField = Error.SkemaErrorItem.class.getDeclaredField("code"); - this.skemaErrorItemCodeField.setAccessible(true); - this.skemaErrorItemMessageField = Error.SkemaErrorItem.class.getDeclaredField("message"); - this.skemaErrorItemMessageField.setAccessible(true); - this.storeApplicationCommandsBuiltInCommandsProviderField = StoreApplicationCommands.class.getDeclaredField("builtInCommandsProvider"); - this.storeApplicationCommandsBuiltInCommandsProviderField.setAccessible(true); - - var storeApplicationCommands = StoreStream.getApplicationCommands(); - var storeChannelsSelected = StoreStream.getChannelsSelected(); - var storeUsers = StoreStream.getUsers(); - var storePermissions = StoreStream.getPermissions(); - var storeGuilds = StoreStream.getGuilds(); - - // Browsing commands (when just a '/' is typed) - Patcher.addPatch( - StoreApplicationCommands$requestApplicationCommands$1.class.getDeclaredMethod("invoke"), - new PreHook(param -> { - var this_ = (StoreApplicationCommands$requestApplicationCommands$1) param.thisObject; - - if (this_.$guildId == null) { - return; - } - - var applicationIndexSource = Patches.applicationIndexSourceFromContext(this_.$guildId, storeChannelsSelected); - try { - this.passCommandData(this_.this$0, applicationIndexSource, RequestSource.BROWSE); - } catch (Exception e) { - throw new RuntimeException(e); - } - - param.setResult(null); - }) - ); - - // Completing commands - Patcher.addPatch( - StoreApplicationCommands$requestApplicationCommandsQuery$1.class.getDeclaredMethod("invoke"), - new PreHook(param -> { - var this_ = (StoreApplicationCommands$requestApplicationCommandsQuery$1) param.thisObject; - - if (this_.$guildId == null) { - return; - } - - var applicationIndexSource = Patches.applicationIndexSourceFromContext(this_.$guildId, storeChannelsSelected); - try { - storeApplicationCommandsQueryField.set(this_.this$0, this_.$query); - this.passCommandData(this_.this$0, applicationIndexSource, RequestSource.QUERY); - } catch (Exception e) { - throw new RuntimeException(e); - } - - param.setResult(null); - }) - ); - - // Command permission check - Patcher.addPatch( - ApplicationCommandKt.class.getDeclaredMethod("hasPermission", ApplicationCommand.class, long.class, List.class), - new InsteadHook(param -> { - var applicationCommand = (ApplicationCommand) param.args[0]; - var roleIds = (List) param.args[2]; - - if (!(applicationCommand instanceof RemoteApplicationCommand)) { - // Allow all builtin commands - return true; - } - var remoteApplicationCommand = (RemoteApplicationCommand) applicationCommand; - - var channel = storeChannelsSelected.getSelectedChannel(); - var guildId = channel.i(); - - if (guildId == 0) { - // Allow all commands in DMs - return true; - } - - var applicationId = remoteApplicationCommand.getApplicationId(); - var isUser = this.requestApplicationIndex(new ApplicationIndexSourceUser()) - .applications - .containsKey(applicationId); - if (isUser) { - // Allow all user application commands - return true; - } - var application = this.requestApplicationIndex(new ApplicationIndexSourceGuild(guildId)) - .applications - .get(applicationId); - if (application == null) { - // Discord requested checking a command from the previous guild - ignore - // Some such requests are still processed (if the command exists in both guilds), but it's not an issue as the result doesn't matter for them anyways. - return false; - } - var user = storeUsers.getMe(); - var memberPermissions = storePermissions.getGuildPermissions() - .get(guildId); - var guild = storeGuilds.getGuild(guildId); - - var applicationPermission = application.permissions_.checkFor(roleIds, channel, guild, memberPermissions, user, true); - var commandPermission = remoteApplicationCommand.permissions_.checkFor(roleIds, channel, guild, memberPermissions, user, applicationPermission); - - return commandPermission; - }) - ); - - // Command error handling - Patcher.addPatch( - StoreApplicationInteractions.class.getDeclaredMethod("handleApplicationCommandResult", MessageResult.class, ApplicationCommandLocalSendData.class, Function0.class, Function1.class), - new PreHook(param -> { - var result = (MessageResult) param.args[0]; - var localSendData = (ApplicationCommandLocalSendData) param.args[1]; - - if (result instanceof MessageResult.UnknownFailure) { - boolean invalidCommandVersion = false; - - try { - var errorResponse = ((MessageResult.UnknownFailure) result) - .getError() - .getResponse(); - var error = this.errorResponseErrorField.get(errorResponse); - var subErrors = ((Map) skemaErrorSubErrorsField.get(error)); - var dataErrors = (List) skemaErrorErrorsField.get(subErrors.get("data")); - - for (var dataError: dataErrors) { - var errorCode = (String) this.skemaErrorItemCodeField.get(dataError); - if (errorCode.equals("INTERACTION_APPLICATION_COMMAND_INVALID_VERSION")) { - ApplicationIndexSource applicationIndexSource = null; - var guildId = localSendData.component3(); - if (guildId != null) { - applicationIndexSource = new ApplicationIndexSourceGuild(guildId); - } else { - var channelId = localSendData.component2(); - applicationIndexSource = new ApplicationIndexSourceDm(channelId); - } - this.cleanApplicationIndexCache(applicationIndexSource); - - var errorMessage = (String) this.skemaErrorItemMessageField.get(dataError); - Utils.showToast(errorMessage); - - break; - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }) - ); - - GatewayAPI.onEvent("GUILD_APPLICATION_COMMAND_INDEX_UPDATE", ApiGuildApplicationCommandIndexUpdate.class, guildApplicationCommandIndexUpdate -> { - this.cleanApplicationIndexCache(new ApplicationIndexSourceGuild(guildApplicationCommandIndexUpdate.guildId)); - return null; - }); - } - - private void passCommandData(StoreApplicationCommands storeApplicationCommands, Optional applicationIndexSource, RequestSource requestSource) throws Exception { - var applicationIndexes = new ArrayList(); - if (applicationIndexSource.isPresent()) { - applicationIndexes.add(this.requestApplicationIndex(applicationIndexSource.get())); - } - applicationIndexes.add(this.requestApplicationIndex(new ApplicationIndexSourceUser())); - var applicationIndex = new ApplicationIndex(applicationIndexes); - applicationIndex - .applicationCommands - .entrySet() - .removeIf(applicationCommand -> applicationCommand.getValue().type != RemoteApplicationCommand.TYPE_CHAT_INPUT); - applicationIndex.populateCommandCounts(this.applicationCommandCountField); - - var applications = new ArrayList(applicationIndex.applications.values()); - Collections.sort(applications, (left, right) -> left.getName().compareTo(right.getName())); - applications.add(((BuiltInCommandsProvider) this.storeApplicationCommandsBuiltInCommandsProviderField.get(storeApplicationCommands)).getBuiltInApplication()); - this.handleGuildApplicationsUpdateMethod.invoke(storeApplicationCommands, applications); - - switch (requestSource) { - case BROWSE: - this.handleDiscoverCommandsUpdateMethod.invoke(storeApplicationCommands, new ArrayList(applicationIndex.applicationCommands.values())); - break; - - case QUERY: - this.handleQueryCommandsUpdateMethod.invoke(storeApplicationCommands, new ArrayList(applicationIndex.applicationCommands.values())); - break; - } - } - - private ApplicationIndex requestApplicationIndex(ApplicationIndexSource source) { - // Reuse application index from cache - var applicationIndex = source.getFromCache(applicationIndexCache); - if (!applicationIndex.isPresent()) { - try { - // Request application index from API - applicationIndex = Optional.of( - Http.Request.newDiscordRNRequest(source.getEndpoint()) - .execute() - .json(GsonUtils.getGsonRestApi(), ApiApplicationIndex.class) - .toModel() - ); - } catch (Exception e) { - throw new RuntimeException(e); - } - - source.insertIntoCache(applicationIndexCache, applicationIndex.get()); - } - return applicationIndex.get(); - } - - private void cleanApplicationIndexCache(ApplicationIndexSource source) { - source.removeFromCache(applicationIndexCache); - } - - private static Optional applicationIndexSourceFromContext(long guildId, StoreChannelsSelected storeChannelsSelected) { - Optional applicationIndexSource = Optional.empty(); - // guildId being 0 means this is a DM or a DM group - if (guildId != 0) { - applicationIndexSource = Optional.of(new ApplicationIndexSourceGuild(guildId)); - } else { - // Only create a DM index source for bots - var channel = storeChannelsSelected.getSelectedChannel(); - var channelType = channel.D(); - if (channelType == Channel.DM) { - var user = channel.z().get(0); - var userIsBot = Optional.ofNullable(user.e()) - .orElse(false); - if (userIsBot) { - var channelId = channel.k(); - applicationIndexSource = Optional.of(new ApplicationIndexSourceDm(channelId)); - } - } - } - - return applicationIndexSource; - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Permissions.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Permissions.java deleted file mode 100644 index d80d0e7..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/Permissions.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import com.discord.api.channel.Channel; -import com.discord.api.permission.Permission; -import com.discord.models.guild.Guild; -import com.discord.models.user.MeUser; -import com.discord.utilities.permissions.PermissionUtils; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -class Permissions { - public Optional user; - public Map roles; - public Map channels; - public Optional defaultMemberPermissions; - - public Permissions(Optional user, Map roles, Map channels, Optional defaultMemberPermissions) { - this.user = Optional.ofNullable(user).orElse(Optional.empty()); - this.roles = Optional.ofNullable(roles).orElse(new HashMap<>()); - this.channels = Optional.ofNullable(channels).orElse(new HashMap<>()); - this.defaultMemberPermissions = Optional.ofNullable(defaultMemberPermissions).orElse(Optional.empty()); - } - - public boolean checkFor(List roleIds, Channel channel, Guild guild, long memberPermissions, MeUser user, boolean defaultPermission) { - var guildId = guild.component7(); - var defaultChannelPermissionId = guildId - 1; - var defaultChannelPermission = this.channels.getOrDefault(defaultChannelPermissionId, defaultPermission); - var channelType = channel.D(); - var channelId = channel.k(); - var permissionChannelId = channelId; - // Threads inherit permissions from their parent channels - if (channelType == Channel.ANNOUNCEMENT_THREAD || channelType == Channel.PUBLIC_THREAD || channelType == Channel.PRIVATE_THREAD) { - var channelParentId = channel.u(); - permissionChannelId = channelParentId; - } - var channelPermission = Optional.ofNullable(this.channels.get(permissionChannelId)) - .orElse(defaultChannelPermission); - var defaultMemberPermission = this.defaultMemberPermissions - .map( - defaultMemberPermissions -> defaultMemberPermissions != 0 - && PermissionUtils.canAndIsElevated( - defaultMemberPermissions, - memberPermissions, - user.getMfaEnabled(), - guild.getMfaLevel() - ) - ) - .orElse(defaultPermission); - var everyoneRoleId = guildId; - var defaultRolePermission = this.roles.getOrDefault(everyoneRoleId, defaultMemberPermission); - var rolePermission = this.calculateRolePermission(roleIds, defaultRolePermission); - var userPermission = this.user.orElse(defaultMemberPermission); - var administratorPermission = PermissionUtils.canAndIsElevated( - Permission.ADMINISTRATOR, - memberPermissions, - user.getMfaEnabled(), - guild.getMfaLevel() - ); - - return administratorPermission || (channelPermission && (userPermission || rolePermission)); - } - - private boolean calculateRolePermission(List roleIds, boolean defaultPermission) { - var calculatedRolePermission = defaultPermission; - for (var roleId: roleIds) { - var rolePermission = this.roles.get(roleId); - if (rolePermission != null) { - calculatedRolePermission = rolePermission; - if (rolePermission) { - break; - } - } - } - return calculatedRolePermission; - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/RemoteApplicationCommand.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/RemoteApplicationCommand.java deleted file mode 100644 index 995a856..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/RemoteApplicationCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import com.discord.models.commands.ApplicationCommandOption; -import java.util.List; - -class RemoteApplicationCommand extends com.discord.models.commands.RemoteApplicationCommand { - public Permissions permissions_; - public int type; - - public static final int TYPE_CHAT_INPUT = 1; - - public RemoteApplicationCommand(String id, long applicationId, String name, String description, List options, Permissions permissions, Long guildId, String version, int type) { - super(id, applicationId, name, description, options, guildId, version, null, null, null); - this.permissions_ = permissions; - this.type = type; - } -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/RequestSource.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/RequestSource.java deleted file mode 100644 index 1422bf5..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/RequestSource.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -enum RequestSource { - BROWSE, - QUERY; -} diff --git a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/SlashCommandsFix.java b/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/SlashCommandsFix.java deleted file mode 100644 index 9b1280a..0000000 --- a/canary/SlashCommandsFix/src/main/java/com/aliucord/coreplugins/slashcommandsfix/SlashCommandsFix.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of Aliucord, an Android Discord client mod. - * Copyright (c) 2021 Juby210 & Vendicated - * Licensed under the Open Software License version 3.0 - */ - -package com.aliucord.coreplugins.slashcommandsfix; - -import android.content.Context; - -import com.aliucord.annotations.AliucordPlugin; -import com.aliucord.entities.Plugin; - -import de.robv.android.xposed.XposedBridge; - -@AliucordPlugin(requiresRestart = true) -public final class SlashCommandsFix extends Plugin { - public SlashCommandsFix() { - super(); - } - - @Override - public void start(Context context) throws Throwable { - if (ConflictCheck.run(context)) return; - - XposedBridge.makeClassInheritable(com.discord.models.commands.Application.class); - XposedBridge.makeClassInheritable(com.discord.models.commands.RemoteApplicationCommand.class); - - new Patches(this.logger).loadPatches(context); - } - - @Override - public void stop(Context context) {} -} diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index de388ea..0000000 --- a/gradle.properties +++ /dev/null @@ -1,12 +0,0 @@ -# Gradle -org.gradle.caching=true -org.gradle.configuration-cache=true -org.gradle.configureondemand=true -org.gradle.parallel=true -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 - -# Kotlin -kotlin.code.style=official - -# Android -android.useAndroidX=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index b0247db..0000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,25 +0,0 @@ -[versions] -aliucord = "2.6.0" -aliuhook = "1.1.4" -aliucord-gradle = "2.3.0" -android = "8.13.0" -discord = "126021" -kotlin = "2.2.20" -#noinspection GradleDependency -kotlin-stdlib = "1.5.21" -ktlint = "1.7.1" -ktlint-plugin = "13.1.0" -shadow = "8.3.8" - -[libraries] -aliucord = { module = "com.aliucord:Aliucord", version.ref = "aliucord" } -aliuhook = { module = "com.aliucord:Aliuhook", version.ref = "aliuhook" } -discord = { module = "com.discord:discord", version.ref = "discord" } -kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-stdlib" } - -[plugins] -aliucord-plugin = { id = "com.aliucord.plugin", version.ref = "aliucord-gradle" } -android-library = { id = "com.android.library", version.ref = "android" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-plugin" } -shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index f6b961f..0000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index c7ed4bd..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,8 +0,0 @@ -#Wed May 28 17:22:29 GMT 2025 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index 005bcde..0000000 --- a/gradlew +++ /dev/null @@ -1,234 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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 -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100755 index 6a68175..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/plugins/Bocchi/build.gradle.kts b/plugins/Bocchi/build.gradle.kts deleted file mode 100644 index 1803a3c..0000000 --- a/plugins/Bocchi/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -version = "1.0.3" -description = "More lenient message grouping" - -android { - namespace = "moe.lava.awoocord.bocchi" -} - -aliucord { - // Changelog of your plugin - changelog.set(""" - # 1.0.3 - * Clump more than 6 messages together - - # 1.0.2 - * Fix (inverted) webhook clumping - - # 1.0.1 - * Hide blank space w.r.t attachments and embeds - - # 1.0.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} diff --git a/plugins/Bocchi/src/main/kotlin/moe/lava/awoocord/bocchi/Bocchi.kt b/plugins/Bocchi/src/main/kotlin/moe/lava/awoocord/bocchi/Bocchi.kt deleted file mode 100644 index d4d28da..0000000 --- a/plugins/Bocchi/src/main/kotlin/moe/lava/awoocord/bocchi/Bocchi.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.lava.awoocord.bocchi - -import android.content.Context -import android.view.View -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.entities.Plugin -import com.aliucord.patcher.* -import com.aliucord.utils.accessField -import com.discord.api.message.MessageTypes -import com.discord.models.message.Message -import com.discord.utilities.view.text.SimpleDraweeSpanTextView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage -import com.discord.widgets.chat.list.entries.ChatListEntry -import com.discord.widgets.chat.list.entries.MessageEntry -import com.discord.widgets.chat.list.model.WidgetChatListModelMessages - -private val WidgetChatListAdapterItemMessage.itemText by accessField() - -@AliucordPlugin(requiresRestart = true) -@Suppress("unused") -class Bocchi : Plugin() { - override fun start(context: Context) { - patcher.after( - "onConfigure", - Int::class.java, - ChatListEntry::class.java, - ) { (_, _: Int, entry: MessageEntry) -> - if (entry.type == ChatListEntry.MESSAGE_MINIMAL && entry.message.content.isNullOrEmpty()) { - itemText.visibility = View.GONE - } - } - patcher.instead( - "shouldConcatMessage", - WidgetChatListModelMessages.Items::class.java, - Message::class.java, - Message::class.java, - ) { (_, items: WidgetChatListModelMessages.Items, message: Message, message2: Message?) -> - val timeDiff = (message.timestamp?.g() ?: 0L) - (message2?.timestamp?.g() ?: 0L) - return@instead !( - message2 == null || - message2.isSystemMessage || - message.hasThread() || - message2.hasThread() || - message.type !in arrayOf(MessageTypes.DEFAULT, MessageTypes.LOCAL) || - message.author.id != message2.author.id || - timeDiff >= 420000 || // WidgetChatListModelMessages.MESSAGE_CONCAT_TIMESTAMP_DELTA_THRESHOLD -// items.listItemMostRecentlyAdded.type !in arrayOf(0, 1, 4, 21) || -// message2.hasAttachments() || -// message2.hasEmbeds() || -// message2.mentions?.isNotEmpty() == true || -// message.mentions?.isNotEmpty() == true || -// message.hasAttachments() || -// message.hasEmbeds() || -// items.concatCount >= 5 || - (message.isWebhook && message.author?.username != message2.author.username) - ) - } - } -} diff --git a/plugins/Crocosmia/build.gradle.kts b/plugins/Crocosmia/build.gradle.kts deleted file mode 100644 index a3aaca9..0000000 --- a/plugins/Crocosmia/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -version = "1.0.0" -description = "Bubbled messages" - -aliucord { - // Changelog of your plugin - changelog.set(""" - # 1.0.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} diff --git a/plugins/Crocosmia/src/main/kotlin/moe/lava/awoocord/crocosmia/Crocosmia.kt b/plugins/Crocosmia/src/main/kotlin/moe/lava/awoocord/crocosmia/Crocosmia.kt deleted file mode 100644 index 0cd9eca..0000000 --- a/plugins/Crocosmia/src/main/kotlin/moe/lava/awoocord/crocosmia/Crocosmia.kt +++ /dev/null @@ -1,475 +0,0 @@ -package moe.lava.awoocord.crocosmia - -import android.content.Context -import android.graphics.Color -import android.view.View -import android.view.View.GONE -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout -import android.widget.LinearLayout -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID -import com.aliucord.PluginManager -import com.aliucord.Utils -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.api.SettingsAPI -import com.aliucord.entities.Plugin -import com.aliucord.patcher.* -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.ViewUtils.addTo -import com.aliucord.utils.ViewUtils.findViewById -import com.aliucord.utils.accessField -import com.discord.databinding.WidgetChatListAdapterItemBotComponentRowBinding -import com.discord.databinding.WidgetChatListAdapterItemEmbedBinding -import com.discord.utilities.color.ColorCompat -import com.discord.utilities.display.DisplayUtils -import com.discord.utilities.embed.EmbedResourceUtils -import com.discord.widgets.chat.list.adapter.* -import com.discord.widgets.chat.list.entries.* -import com.google.android.material.card.MaterialCardView -import com.google.android.material.shape.CornerFamily -import com.lytefast.flexinput.R -import de.robv.android.xposed.XC_MethodHook -import java.util.WeakHashMap -import kotlin.math.min - -private val padding get() = 12.dp -//private val topPad get() = 14.dp -private val topPad get() = 6.dp -private val bigCorner get() = 24.dp.toFloat() -private val smallCorner get() = 4.dp.toFloat() -private val ChatListEntry.connectBefore get() = this.type in arrayOf( - ChatListEntry.MESSAGE_MINIMAL, - ChatListEntry.MESSAGE_EMBED, - ChatListEntry.MESSAGE_ATTACHMENT, - ChatListEntry.STICKER, - ChatListEntry.BOT_UI_COMPONENT, - 101, -) -private val ChatListEntry.excepted get() = this.type in arrayOf( - ChatListEntry.REACTIONS, -) - -private val WidgetChatListAdapterItemBotComponentRow.binding by accessField() -private val WidgetChatListAdapterItemAttachment.binding get() = WidgetChatListAdapterItemAttachment.`access$getBinding$p`(this) -private val WidgetChatListAdapterItemEmbed.binding by accessField() -private val WidgetChatListAdapterItemSticker.binding get() = WidgetChatListAdapterItemSticker.`access$getBinding$p`(this) - -private var MessageEntry.keyField by accessField() - -private val fullId = Utils.getResId("widget_chat_list_adapter_item_text", "layout") -private val minimalId = Utils.getResId("widget_chat_list_adapter_item_minimal", "layout") -private val bubbleId = View.generateViewId() - -private const val messageLayoutTag = R.f.message // Just some random id - -@Suppress("UNUSED") -@AliucordPlugin -class Crocosmia : Plugin() { - private fun createBubble(context: Context, parentHandler: View? = null): MaterialCardView { - return MaterialCardView(context).apply { - id = bubbleId - setCardBackgroundColor( - ColorCompat.getThemedColor( - this, - R.b.colorBackgroundSecondary - ) - ) - parentHandler?.let { parent -> - setOnClickListener { parent.performClick() } - setOnLongClickListener { parent.performLongClick() } - } - elevation = 0f - } - } - - private fun WidgetChatListItem.configBubble(entry: ChatListEntry) { - itemView.findViewById(bubbleId)?.let { - configBubble(it, entry) - } - } - - private fun WidgetChatListItem.configBubble(view: MaterialCardView, entry: ChatListEntry) { - val idx = adapter.data.list.indexOf(entry) - val previousEntry = adapter.data.list.getOrNull(idx + 1) - val nextEntry = if (idx < 1) null else adapter.data.list[idx - 1] - view.shapeAppearanceModel = view.shapeAppearanceModel.toBuilder().run { - setAllCorners(CornerFamily.ROUNDED, bigCorner) - if (entry.connectBefore && previousEntry?.excepted != true) { - setTopLeftCornerSize(smallCorner) - setTopRightCornerSize(smallCorner) - } - if (nextEntry?.connectBefore == true) { - setBottomLeftCornerSize(smallCorner) - setBottomRightCornerSize(smallCorner) - } - build() - } - view.clipToOutline = true - } - - override fun load(context: Context) { - hasCompactMode = PluginManager.isPluginEnabled("CompactMode") - hasHighlightMessages = PluginManager.isPluginEnabled("HighlightOwnMessages") - if (hasCompactMode) { - logger.info("Enabling compatibility with CompactMode") - compactCompatOverride = SettingsAPI("CompactMode").getInt("contentMargin", 8) - } - } - - private fun compatHighlightMessages() { - val cls = try { - val cl = PluginManager.plugins["HighlightOwnMessages"]!!.javaClass - val loader = cl.classLoader!! - loader.loadClass( - $$$"cloudburst.plugins.highlightownmessages.HighlightOwnMessages$$ExternalSyntheticLambda0" - ) - } catch(e: Throwable) { - logger.warn("Tried to enable compatibility with HighlightOwnMessages, but no lambda class found", e) - return - } - logger.info("Enabling compatibility with HighlightOwnMessages") - val method = cls.getDeclaredMethod("call", Object::class.java) - patcher.patch(method) { mparam -> - val param = mparam.args[0] as XC_MethodHook.MethodHookParam - val self = param.thisObject as? WidgetChatListAdapterItemMessage - ?: return@patch logger.warn("Failed to cast thisObject (found: ${param.thisObject.javaClass.name})") - self.run { - val isFull = itemView.getTag(messageLayoutTag) as? Boolean - ?: return@patch - itemView.findViewById("chat_list_adapter_item_text").apply { - layoutParams = (layoutParams as ConstraintLayout.LayoutParams).apply { - if (isFull) { - setPadding(padding, 0, padding, padding) - } else { - setPadding(padding, padding + 2.dp, padding, padding) - } - } - } - } - } - } - - override fun stop(context: Context) { patcher.unpatchAll() } - - var hasCompactMode = false - var compactCompatOverride: Int? = null - var hasHighlightMessages = false - - override fun start(context: Context) { - patcher.after( - "setData", - WidgetChatListAdapter.Data::class.java, - ) { - notifyItemChanged(1, Unit.a) - } - - patcher.after( - WidgetChatListAdapter::class.java, - ) { - binding.a.layoutParams = binding.a.layoutParams.apply { - width = MATCH_PARENT - } - (binding.f.getChildAt(0) as? ConstraintLayout)?.run { - layoutParams = (layoutParams as FrameLayout.LayoutParams).apply { - width = WRAP_CONTENT - } - } - binding.f.setPadding(padding, padding, padding, padding) - binding.f.layoutParams = (binding.f.layoutParams as ConstraintLayout.LayoutParams).apply { - marginEnd = binding.f.resources.getDimension(R.d.chat_cell_horizontal_spacing_total).toInt() - } - } - - patcher.instead( - "computeMaximumImageWidthPx", - Context::class.java, - ) { (_, context: Context) -> - val res = context.resources - val screenWidth = DisplayUtils.getScreenSize(context).width() - val space = res.getDimensionPixelSize(R.d.uikit_guideline_chat) + res.getDimensionPixelSize(R.d.chat_cell_horizontal_spacing_total) + padding * 2 - return@instead min(1440, screenWidth - space); - } - - patchEmbed() - patchAttachmentInit() - patchAttachmentConfig() - patchComponentsConfig() - patchMessageInit() - patchMessageConfig() - patchStickerInit() - patchStickerConfig() - patchPollConfig() - - if (hasHighlightMessages) { - compatHighlightMessages() - } - } - - private fun patchAttachmentConfig() { - patcher.after( - "onConfigure", - Int::class.javaPrimitiveType!!, - ChatListEntry::class.java, - ) { (_, _: Int, entry: AttachmentEntry) -> - configBubble(entry) - } - } - - private fun patchAttachmentInit() { - patcher.after( - WidgetChatListAdapter::class.java, - ) { - val mediaView = binding.h - mediaView.layoutParams = - (mediaView.layoutParams as ConstraintLayout.LayoutParams).apply { - topMargin = padding - bottomMargin = padding - marginStart = padding - marginEnd = padding - } - itemView.layoutParams = (itemView.layoutParams as ViewGroup.MarginLayoutParams).apply { - bottomMargin = 2.dp - } - - binding.d.radius = 0f - binding.d.elevation = 0f - binding.d.strokeWidth = 0 - binding.d.setCardBackgroundColor(Color.TRANSPARENT) - - createBubble(itemView.context, binding.a).addTo(itemView as ConstraintLayout, 1) { - layoutParams = ConstraintLayout.LayoutParams(0, 0).apply { - startToStart = PARENT_ID - topToTop = PARENT_ID - bottomToBottom = PARENT_ID - endToEnd = PARENT_ID - marginStart = compactCompatOverride?.dp - ?: resources.getDimension(R.d.uikit_guideline_chat).toInt() - marginEnd = resources.getDimension(R.d.chat_cell_horizontal_spacing_total).toInt() - } - } - } - } - - private val marked = WeakHashMap() - private fun patchComponentsConfig() { - patcher.after( - "onConfigure", - Int::class.javaPrimitiveType!!, - ChatListEntry::class.java, - ) { (_, _: Int, entry: BotUiComponentEntry) -> - var i = 0 - val layout = binding.b - layout.layoutParams = (layout.layoutParams as ConstraintLayout.LayoutParams).apply { - marginEnd = layout.resources.getDimension(R.d.chat_cell_horizontal_spacing_total).toInt() - } - while (i < layout.childCount) { - val child = layout.getChildAt(i) - ?: break - val bubble: MaterialCardView - if (child.javaClass.simpleName == "ContainerComponentView") { - bubble = (child as? ConstraintLayout)?.getChildAt(0) as? MaterialCardView - ?: continue - if (i == (layout.childCount - 1)) { - ((bubble.getChildAt(0) as? ConstraintLayout)?.getChildAt(1) as? LinearLayout)?.run { - if (!marked.contains(this)) { - marked[this] = Unit.a - setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom + padding) - } - } - } - } else { - layout.removeViewAt(i) - bubble = createBubble(itemView.context).addTo(layout, i) { - layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT) - child.addTo(this) { - layoutParams = (layoutParams as LinearLayout.LayoutParams).apply { - topMargin += padding - bottomMargin += padding - rightMargin += padding - leftMargin += padding - } - } - } - bubble.setOnClickListener { - adapter.eventHandler.onMessageClicked(entry.message, false) - } - bubble.setOnLongClickListener { - adapter.eventHandler.onMessageLongClicked(entry.message, "", false) - true - } - } - - bubble.shapeAppearanceModel = bubble.shapeAppearanceModel.toBuilder().run { - setAllCorners(CornerFamily.ROUNDED, smallCorner) - if (i == (layout.childCount - 1)) { - setBottomLeftCornerSize(bigCorner) - setBottomRightCornerSize(bigCorner) - } - build() - } - bubble.clipToOutline = true - i++ - } - } - } - - private fun patchEmbed() { - patcher.after( - WidgetChatListAdapter::class.java, - ) { (_) -> - binding.t.layoutParams = - (binding.t.layoutParams as ConstraintLayout.LayoutParams).apply { - topMargin = padding - bottomMargin = padding - marginStart = padding - marginEnd = padding - } - createBubble(itemView.context, binding.a).addTo(itemView as ConstraintLayout, 1) { - visibility = GONE - layoutParams = ConstraintLayout.LayoutParams(0, 0).apply { - startToStart = PARENT_ID - topToTop = PARENT_ID - bottomToBottom = PARENT_ID - endToEnd = PARENT_ID - marginStart = compactCompatOverride?.dp - ?: resources.getDimension(R.d.uikit_guideline_chat).toInt() - marginEnd = resources.getDimension(R.d.chat_cell_horizontal_spacing_total).toInt() - } - } - } - patcher.after( - "onConfigure", - Int::class.javaPrimitiveType!!, - ChatListEntry::class.java, - ) { (_, _: Int, entry: EmbedEntry) -> - if (EmbedResourceUtils.INSTANCE.isInlineEmbed(entry.embed)) { - itemView.findViewById(bubbleId).visibility = View.VISIBLE - configBubble(entry) - } else { - itemView.findViewById(bubbleId).visibility = View.GONE - configBubble(binding.f, entry) - } - } - } - - private fun patchMessageInit() { - patcher.after( - Int::class.javaPrimitiveType!!, - WidgetChatListAdapter::class.java, - ) { (_, layoutId: Int) -> - val isFull = when (layoutId) { - fullId -> !hasCompactMode - minimalId -> false - else -> return@after - } - - itemView.layoutParams = (itemView.layoutParams as ViewGroup.MarginLayoutParams).apply { - bottomMargin = 2.dp - } - itemView.setTag(messageLayoutTag, isFull) - if (isFull) { - itemView.findViewById("chat_list_adapter_item_text_header")?.apply { - layoutParams = (layoutParams as ConstraintLayout.LayoutParams).apply { - setPadding( - paddingLeft + padding, - paddingTop + topPad, - paddingRight + padding, - paddingBottom - ) - } - } - } - itemView.findViewById("chat_list_adapter_item_text").apply { - layoutParams = (layoutParams as ConstraintLayout.LayoutParams).apply { - if (isFull) { - setPadding(padding, 0, padding, padding) - } else { - setPadding(padding, padding + 2.dp, padding, padding) - } - } - } - createBubble(itemView.context, itemView).addTo(itemView as ConstraintLayout, 2) { - layoutParams = ConstraintLayout.LayoutParams(0, 0).apply { - if (isFull) { - startToStart = Utils.getResId("uikit_chat_guideline", "id") - topToTop = Utils.getResId("chat_list_adapter_item_text_header", "id") - } else { - startToStart = PARENT_ID - topToTop = Utils.getResId("chat_list_adapter_item_text", "id") - marginStart = compactCompatOverride?.dp - ?: resources.getDimension(R.d.uikit_guideline_chat).toInt() - } - bottomToBottom = PARENT_ID - endToEnd = PARENT_ID - marginEnd = resources.getDimension(R.d.chat_cell_horizontal_spacing_total).toInt() - } - } - } - } - - private fun patchMessageConfig() { - patcher.after( - "onConfigure", - Int::class.javaPrimitiveType!!, - ChatListEntry::class.java, - ) { (_, _: Int, entry: MessageEntry) -> - if (entry.message.content.isNullOrEmpty()) { - itemView.findViewById("chat_list_adapter_item_text").visibility = View.GONE - } - configBubble(entry) - } - } - - private fun patchStickerInit() { - patcher.after( - WidgetChatListAdapter::class.java, - ) { - binding.b.layoutParams = (binding.b.layoutParams as FrameLayout.LayoutParams).apply { - topMargin = padding - bottomMargin = padding - marginStart = padding - marginEnd = padding - } - binding.a.layoutParams = binding.a.layoutParams.apply { - width = MATCH_PARENT - } - binding.a.removeView(binding.b) - createBubble(itemView.context, binding.b).addTo(binding.a, 0) { - layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) - binding.b.addTo(this) - } - } - } - private fun patchStickerConfig() { - patcher.after( - "onConfigure", - Int::class.javaPrimitiveType!!, - ChatListEntry::class.java, - ) { (_, _: Int, entry: StickerEntry) -> - configBubble(entry) - } - } - - private val pollClass = try { - Class.forName("com.aliucord.coreplugins.polls.chatview.WidgetChatListAdapterItemPoll") - } catch(_: Throwable) { - null - } - private val pollField = pollClass?.getDeclaredField("pollView")?.apply { isAccessible = true } - private fun patchPollConfig() { - if (pollClass == null) return - patcher.patch(pollClass.getDeclaredMethod( - "onConfigure", - Int::class.javaPrimitiveType!!, - ChatListEntry::class.java, - )) { (param, _: Int, entry: ChatListEntry) -> - val view = pollField?.get(param.thisObject) as? MaterialCardView - view?.let { - (param.thisObject as WidgetChatListItem).configBubble(it, entry) - } - } - } -} diff --git a/plugins/Myosotis/build.gradle.kts b/plugins/Myosotis/build.gradle.kts deleted file mode 100644 index 7bd110c..0000000 --- a/plugins/Myosotis/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -version = "1.0.0" -description = "Backports DM previews" - -aliucord { - // Changelog of your plugin - changelog.set(""" - # 1.0.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} diff --git a/plugins/Myosotis/src/main/kotlin/moe/lava/awoocord/myosotis/Myosotis.kt b/plugins/Myosotis/src/main/kotlin/moe/lava/awoocord/myosotis/Myosotis.kt deleted file mode 100644 index 60ed811..0000000 --- a/plugins/Myosotis/src/main/kotlin/moe/lava/awoocord/myosotis/Myosotis.kt +++ /dev/null @@ -1,205 +0,0 @@ -package moe.lava.awoocord.myosotis - -import android.annotation.SuppressLint -import android.content.Context -import android.view.View -import androidx.fragment.app.FragmentManager -import androidx.recyclerview.widget.RecyclerView -import com.aliucord.Http -import com.aliucord.Utils -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.api.GatewayAPI -import com.aliucord.entities.Plugin -import com.aliucord.patcher.after -import com.aliucord.patcher.before -import com.aliucord.patcher.component1 -import com.aliucord.patcher.component2 -import com.aliucord.patcher.component3 -import com.aliucord.utils.ChannelUtils -import com.aliucord.utils.GsonUtils -import com.aliucord.utils.SerializedName -import com.aliucord.utils.accessField -import com.aliucord.wrappers.ChannelWrapper.Companion.id -import com.aliucord.wrappers.users.globalName -import com.discord.api.message.Message -import com.discord.databinding.WidgetChannelsListItemChannelPrivateBinding -import com.discord.models.domain.ModelMessageDelete -import com.discord.stores.StoreStream -import com.discord.utilities.color.ColorCompat -import com.discord.utilities.textprocessing.DiscordParser -import com.discord.utilities.textprocessing.MessagePreprocessor -import com.discord.utilities.textprocessing.MessageRenderContext -import com.discord.utilities.view.text.SimpleDraweeSpanTextView -import com.discord.widgets.channels.list.WidgetChannelsListAdapter -import com.discord.widgets.channels.list.items.ChannelListItem -import com.discord.widgets.channels.list.items.ChannelListItemPrivate -import com.discord.widgets.chat.list.adapter.`WidgetChatListAdapterItemMessage$getMessageRenderContext$1` -import com.discord.widgets.chat.list.adapter.`WidgetChatListAdapterItemMessage$getMessageRenderContext$4` -import com.google.gson.reflect.TypeToken -import com.lytefast.flexinput.R -import java.lang.ref.WeakReference - -private val WidgetChannelsListAdapter.ItemChannelPrivate.binding - by accessField() - -private val responseType = TypeToken.getParameterized(List::class.java, Message::class.java).type - -data class ChannelIdsPayload( - @SerializedName("channel_ids") val channelIds: List, -) - -data class MessageItem( - val id: Long, - val content: String?, -) - -fun Message.wrap(): MessageItem { - val author = this.e() - val authorName = if (author.id == StoreStream.getUsers().me.id) { - "You" - } else { - author.globalName ?: author.username - } - val content = this.i() - .takeIf { it.isNotEmpty() } - ?.let { content -> "$authorName: ${content.takeWhile { it != '\n' }}" } - - return MessageItem( - id = this.o(), - content = content, - ) -} - -fun SimpleDraweeSpanTextView.renderText(content: String, other: Pair) { - val me = StoreStream.getUsers().me - val meId = me.id - val meName = me.globalName ?: me.username - val processor = MessagePreprocessor(meId, listOf(), null, false, 50) - val parseChannelMessage = DiscordParser.parseChannelMessage( - context, - content, - MessageRenderContext( - context, - meId, - false, - mapOf(meId to meName, other), - StoreStream.getChannels().channelNames, - mapOf(), - R.b.colorTextLink, - `WidgetChatListAdapterItemMessage$getMessageRenderContext$1`.INSTANCE, - { }, - ColorCompat.getThemedColor(context, R.b.theme_chat_spoiler_bg), - ColorCompat.getThemedColor(context, R.b.theme_chat_spoiler_bg_visible), - { }, - { }, - `WidgetChatListAdapterItemMessage$getMessageRenderContext$4`(context) - ), - processor, - DiscordParser.ParserOptions.DEFAULT, - false - ) - setDraweeSpanStringBuilder(parseChannelMessage); -} - -@AliucordPlugin -class Myosotis : Plugin() { - var cache = mutableMapOf() - var adapterRef: WeakReference? = null - - override fun stop(context: Context) { patcher.unpatchAll() } - - override fun start(context: Context) { - GatewayAPI.onEvent("READY") { refreshAll() } - GatewayAPI.onEvent("RESUMED") { refreshAll() } - - patcher.after( - "onConfigure", - Int::class.java, - ChannelListItem::class.java, - ) { (_, _: Int, item: ChannelListItemPrivate) -> - cache[item.channel.id]?.let { msg -> - val content = msg.content - ?: return@let - - val descView = binding.d - descView.visibility = View.VISIBLE - val user = ChannelUtils.getDMRecipient(item.channel) - descView.renderText(content, user.id to (user.globalName ?: user.username)) - } - } - patcher.after( - RecyclerView::class.java, - FragmentManager::class.java, - ) { - adapterRef = WeakReference(this) - } - - patcher.before( - "handleMessageCreate", - Message::class.java - ) { (_, msg: Message) -> - handleMessageUpdate(msg) - } - - patcher.before( - "handleMessageUpdate", - Message::class.java - ) { (_, msg: Message) -> - handleMessageUpdate(msg) - } - - patcher.before( - "handleMessageDelete", - ModelMessageDelete::class.java - ) { (_, deleteModel: ModelMessageDelete) -> - cache[deleteModel.channelId]?.let { msg -> - if (msg.id in deleteModel.messageIds) { - cache.remove(deleteModel.channelId) - rerender(deleteModel.channelId) - } - } - } - } - - private fun handleMessageUpdate(msg: Message) { - val gid = msg.m() - if (gid == null) { - val channelId = msg.g() - - val oldMsgId = cache[channelId]?.id ?: 0 - if (msg.o() > oldMsgId) { - cache[channelId] = msg.wrap() - rerender(channelId) - } - } - } - - @OptIn(ExperimentalStdlibApi::class) - private fun refreshAll() { - val channels = StoreStream.getChannels().getChannelsForGuild(0) - .filterValues { it.D() == 1 } // type == Type.DM - .keys.take(100) - Utils.threadPool.execute { - val res = Http.Request.newDiscordRNRequest("/channels/preload-messages", "POST") - .executeWithJson(ChannelIdsPayload(channels)) - .json>(GsonUtils.gsonRestApi, responseType) - cache = mutableMapOf(*res.map { it.g() to it.wrap() }.toTypedArray()) - - Utils.mainThread.post { - @SuppressLint("NotifyDataSetChanged") // I DONT CARE HAHAHAAHJAHAAJHDLAHD - adapterRef?.get()?.notifyDataSetChanged() - } - } - } - - private fun rerender(id: Long) { - val adapter = adapterRef?.get() ?: return - val idx = adapter.internalData.indexOfFirst { it.key == "3$id" } - logger.info("found $idx for $id") - if (idx != -1) { - Utils.mainThread.post { - adapter.notifyItemChanged(idx) - } - } - } -} diff --git a/plugins/Scout/build.gradle.kts b/plugins/Scout/build.gradle.kts deleted file mode 100644 index 5f7859a..0000000 --- a/plugins/Scout/build.gradle.kts +++ /dev/null @@ -1,57 +0,0 @@ -version = "1.4.0" -description = "Backported and improved search functionality" - -android { - namespace = "moe.lava.awoocord.scout" -} - -aliucord { - // Changelog of your plugin - changelog.set(""" - !!! Minimum Aliucord version requirement {fixed} - ====================== - * Scout now requires Aliucord 2.4.0, please update before reporting issues. - - Changelog {added marginTop} - ====================== - # 1.4.0 - Scout is searching for clues about the elusive MvM update - * Added the authorType filter option to search by user, bot, or webhook - * Moved sort filter to the top of the new ones - * Fixes a Discord bug where typing "mentions" would also suggest "has" - * Some people said the options were getting bloated, so they're all hidden behind a "Show all" button now. They'll still show up in auto suggestions. - - # 1.3.0 - * Removes empty discriminator when searching with users - - # 1.2.2 - * Fix possible rare crash related to thread searching - - # 1.2.1 - * Fixes off-looking thread icon - Only Discord will name an icon "thread_white_24dp", and it's neither white nor 24dp. Seriously, what were they thinking? - - # 1.2.0 - Scout is in:to knitting - * Adds support for searching threads; simply use in: - - # 1.1.3 - * Patch to fix the biggggg top padding in results - - # 1.1.2 - * Fix month being one month behind after using the date picker - - # 1.1.1 - * Use proper icons for search filter suggestions - - # 1.1.0 - Look out, Scout has:updates - * Add "has:forward" and "has:poll" filters - * Add "exclude:" filter. It is the opposite of "has:" and filters out matching elements - - # 1.0.1 - * Fix not being able to search more than one page with sort:old - - # 1.0.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/FilterTypeExtension.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/FilterTypeExtension.kt deleted file mode 100644 index 58afef0..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/FilterTypeExtension.kt +++ /dev/null @@ -1,17 +0,0 @@ -package moe.lava.awoocord.scout - -import com.discord.utilities.search.query.FilterType - -object FilterTypeExtension { - lateinit var EXPAND: FilterType - lateinit var SORT: FilterType - lateinit var BEFORE: FilterType - lateinit var DURING: FilterType - lateinit var AFTER: FilterType - lateinit var EXCLUDE: FilterType - lateinit var AUTHOR_TYPE: FilterType - lateinit var dates: Array - lateinit var filters: Array - lateinit var values: Array -} - diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/HasAnswerOptionExtension.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/HasAnswerOptionExtension.kt deleted file mode 100644 index b23ec8f..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/HasAnswerOptionExtension.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.lava.awoocord.scout - -import com.discord.utilities.search.query.node.answer.HasAnswerOption - -object HasAnswerOptionExtension { - lateinit var POLL: HasAnswerOption - lateinit var SNAPSHOT: HasAnswerOption - lateinit var values: Array -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt deleted file mode 100644 index c32ddda..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt +++ /dev/null @@ -1,940 +0,0 @@ -@file:Suppress("EnumValuesSoftDeprecate", "CanConvertToMultiDollarString") - -/** - * Hi to anyone who might be reading this; I am sorry for the atrocious code in this plugin - * but I promise I'll be fixing it up soon :3 - */ - -package moe.lava.awoocord.scout - -import android.annotation.SuppressLint -import android.content.Context -import android.content.res.Resources -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import androidx.core.content.res.ResourcesCompat -import com.aliucord.Utils -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.entities.Plugin -import com.aliucord.patcher.PreHook -import com.aliucord.patcher.after -import com.aliucord.patcher.before -import com.aliucord.patcher.component1 -import com.aliucord.patcher.component2 -import com.aliucord.patcher.component3 -import com.aliucord.patcher.component4 -import com.aliucord.patcher.component5 -import com.aliucord.patcher.instead -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.RxUtils.subscribe -import com.aliucord.utils.ViewUtils.findViewById -import com.aliucord.utils.accessField -import com.aliucord.wrappers.ChannelWrapper.Companion.id -import com.discord.BuildConfig -import com.discord.api.channel.Channel -import com.discord.api.channel.ChannelUtils -import com.discord.api.channel.`ChannelUtils$getSortByNameAndType$1` -import com.discord.api.permission.Permission -import com.discord.databinding.WidgetSearchSuggestionItemHeaderBinding -import com.discord.databinding.WidgetSearchSuggestionsItemHasBinding -import com.discord.databinding.WidgetSearchSuggestionsItemSuggestionBinding -import com.discord.models.member.GuildMember -import com.discord.models.user.User -import com.discord.restapi.RequiredHeadersInterceptor -import com.discord.restapi.RestAPIBuilder -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Parser -import com.discord.simpleast.core.parser.Rule -import com.discord.stores.StoreSearch -import com.discord.stores.StoreSearchInput -import com.discord.stores.StoreStream -import com.discord.utilities.mg_recycler.MGRecyclerDataPayload -import com.discord.utilities.mg_recycler.SingleTypePayload -import com.discord.utilities.rest.RestAPI.AppHeadersProvider -import com.discord.utilities.search.network.`SearchFetcher$getRestObservable$3` -import com.discord.utilities.search.network.SearchQuery -import com.discord.utilities.search.query.FilterType -import com.discord.utilities.search.query.node.QueryNode -import com.discord.utilities.search.query.node.answer.ChannelNode -import com.discord.utilities.search.query.node.answer.HasAnswerOption -import com.discord.utilities.search.query.node.answer.HasNode -import com.discord.utilities.search.query.node.answer.UserNode -import com.discord.utilities.search.query.node.content.ContentNode -import com.discord.utilities.search.query.node.filter.FilterNode -import com.discord.utilities.search.query.parsing.QueryParser -import com.discord.utilities.search.query.parsing.`QueryParser$Companion$getInAnswerRule$1` -import com.discord.utilities.search.strings.ContextSearchStringProvider -import com.discord.utilities.search.strings.SearchStringProvider -import com.discord.utilities.search.suggestion.SearchSuggestionEngine -import com.discord.utilities.search.suggestion.entries.ChannelSuggestion -import com.discord.utilities.search.suggestion.entries.FilterSuggestion -import com.discord.utilities.search.suggestion.entries.HasSuggestion -import com.discord.utilities.search.suggestion.entries.SearchSuggestion -import com.discord.utilities.search.validation.SearchData -import com.discord.widgets.search.results.WidgetSearchResults -import com.discord.widgets.search.suggestions.WidgetSearchSuggestions -import com.discord.widgets.search.suggestions.`WidgetSearchSuggestions$configureUI$1` -import com.discord.widgets.search.suggestions.WidgetSearchSuggestionsAdapter -import com.franmontiel.persistentcookiejar.PersistentCookieJar -import com.franmontiel.persistentcookiejar.cache.SetCookieCache -import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor -import com.lytefast.flexinput.R -import moe.lava.awoocord.scout.api.SearchAPIInterface -import moe.lava.awoocord.scout.entries.AuthorTypeSuggestion -import moe.lava.awoocord.scout.entries.AuthorTypeViewHolder -import moe.lava.awoocord.scout.parsing.AuthorType -import moe.lava.awoocord.scout.parsing.AuthorTypeNode -import moe.lava.awoocord.scout.parsing.DateNode -import moe.lava.awoocord.scout.parsing.SimpleParserRule -import moe.lava.awoocord.scout.parsing.SortNode -import moe.lava.awoocord.scout.parsing.UserIdNode -import moe.lava.awoocord.scout.ui.DatePickerFragment -import moe.lava.awoocord.scout.ui.ScoutResource -import moe.lava.awoocord.scout.ui.ScoutSearchStringProvider -import java.util.regex.Pattern -import b.a.k.b as FormatUtils - -private val WidgetSearchSuggestionsAdapter.FilterViewHolder.binding - by accessField() - -private val WidgetSearchSuggestionsAdapter.HeaderViewHolder.binding - by accessField() - -@AliucordPlugin -@Suppress("unused", "unchecked_cast") -class Scout : Plugin() { - lateinit var scoutRes: ScoutResource - lateinit var ssProvider: ScoutSearchStringProvider - lateinit var searchApi: SearchAPIInterface - - var optionsExpanded = false - - init { - @Suppress("DEPRECATION") - needsResources = true - } - - override fun load(context: Context) { - scoutRes = ScoutResource(resources!!) - ssProvider = ScoutSearchStringProvider(context) - searchApi = buildSearchApi(context) - } - - override fun start(context: Context) { - extendFilterType() - extendHasAnswerOption() - extendSuggestionCategory() - fixFiltersKeying() - fixHasFilterSuggestion() - fixSearchPadding() - patchHasAnswerOption() - patchHasNode() - patchQuery() - patchQueryParser() - patchSearchUI(context) - patchThreadSupport() - patchUsernameDiscriminator() - } - - override fun stop(context: Context) { - patcher.unpatchAll() - resetFilterType() - resetHasAnswerOption() - resetSuggestionCategory() - } - - // Creates a new custom search API implementation, for the extra `min_id` param in search queries - private fun buildSearchApi(context: Context): SearchAPIInterface { - val appHeadersProvider = AppHeadersProvider.INSTANCE - val requiredHeadersInterceptor = RequiredHeadersInterceptor(appHeadersProvider) - val persistentCookieJar = PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(context)) - val restAPIBuilder = RestAPIBuilder(BuildConfig.HOST_API, persistentCookieJar) - - return RestAPIBuilder.`build$default`( - restAPIBuilder, - SearchAPIInterface::class.java, - false, - 0L, - listOf(requiredHeadersInterceptor), - "client_base", - false, - null, - 102, - null - ) as SearchAPIInterface - } - - private var origFilterTypes: Array? = null - // Creates new pseudo-values of the `FilterType` enum for date filters - @Suppress("LocalVariableName", "AssignedValueIsNeverRead") - private fun extendFilterType() { - val cls = FilterType::class.java - val constructor = cls.declaredConstructors[0] - constructor.isAccessible = true - - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - val values = field.get(null) as Array - origFilterTypes = origFilterTypes ?: values - var nextIdx = values.size - - val EXPAND = constructor.newInstance("EXPAND", nextIdx++) as FilterType - val SORT = constructor.newInstance("SORT", nextIdx++) as FilterType - val EXCLUDE = constructor.newInstance("EXCLUDE", nextIdx++) as FilterType - val AUTHOR_TYPE = constructor.newInstance("AUTHOR_TYPE", nextIdx++) as FilterType - val BEFORE = constructor.newInstance("BEFORE", nextIdx++) as FilterType - val DURING = constructor.newInstance("DURING", nextIdx++) as FilterType - val AFTER = constructor.newInstance("AFTER", nextIdx++) as FilterType - FilterTypeExtension.EXPAND = EXPAND - FilterTypeExtension.SORT = SORT - FilterTypeExtension.EXCLUDE = EXCLUDE - FilterTypeExtension.AUTHOR_TYPE = AUTHOR_TYPE - FilterTypeExtension.BEFORE = BEFORE - FilterTypeExtension.DURING = DURING - FilterTypeExtension.AFTER = AFTER - FilterTypeExtension.dates = arrayOf(BEFORE, DURING, AFTER) - FilterTypeExtension.filters = arrayOf(SORT, AUTHOR_TYPE, EXCLUDE) + FilterTypeExtension.dates - FilterTypeExtension.values = arrayOf(EXPAND) + FilterTypeExtension.filters - - val newValues = values.toMutableList() - newValues.addAll(FilterTypeExtension.values) - field.set(null, newValues.toTypedArray()) - } - - private fun resetFilterType() { - if (origFilterTypes == null) - return logger.error("No unpatched filter types?", null) - - val cls = FilterType::class.java - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - field.set(null, origFilterTypes) - origFilterTypes = null - } - - private var origHasAnswerOptions: Array? = null - // Creates new pseudo-values of the `HasAnswerOption` enum for poll and forwarded filters - @Suppress("LocalVariableName", "AssignedValueIsNeverRead") - private fun extendHasAnswerOption() { - val cls = HasAnswerOption::class.java - val constructor = cls.declaredConstructors[0] - constructor.isAccessible = true - - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - val values = field.get(null) as Array - origHasAnswerOptions = origHasAnswerOptions ?: values - var nextIdx = values.size - - val POLL = constructor.newInstance("POLL", nextIdx++, "poll") as HasAnswerOption - val SNAPSHOT = constructor.newInstance("SNAPSHOT", nextIdx++, "snapshot") as HasAnswerOption - HasAnswerOptionExtension.POLL = POLL - HasAnswerOptionExtension.SNAPSHOT = SNAPSHOT - HasAnswerOptionExtension.values = arrayOf(POLL, SNAPSHOT) - - val newValues = values.toMutableList() - newValues.addAll(HasAnswerOptionExtension.values) - field.set(null, newValues.toTypedArray()) - } - - private fun resetHasAnswerOption() { - if (origHasAnswerOptions == null) - return logger.error("No unpatched 'has' options?", null) - - val cls = HasAnswerOption::class.java - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - field.set(null, origHasAnswerOptions) - origHasAnswerOptions = null - } - - private var origSuggestionCategories: Array? = null - // Creates new pseudo-values of the suggestion categories to add correct headers - @Suppress("LocalVariableName", "AssignedValueIsNeverRead") - private fun extendSuggestionCategory() { - val cls = SearchSuggestion.Category::class.java - val constructor = cls.declaredConstructors[0] - constructor.isAccessible = true - - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - val values = field.get(null) as Array - origSuggestionCategories = origSuggestionCategories ?: values - var nextIdx = values.size - - val AUTHOR_TYPE = constructor.newInstance("AUTHOR_TYPE", nextIdx++) as SearchSuggestion.Category - SuggestionCategoryExtension.AUTHOR_TYPE = AUTHOR_TYPE - SuggestionCategoryExtension.values = arrayOf(AUTHOR_TYPE) - - val newValues = values.toMutableList() - newValues.addAll(SuggestionCategoryExtension.values) - field.set(null, newValues.toTypedArray()) - } - - private fun resetSuggestionCategory() { - if (origSuggestionCategories == null) - return logger.error("No unpatched suggestion categories?", null) - - val cls = SearchSuggestion.Category::class.java - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - field.set(null, origSuggestionCategories) - origSuggestionCategories = null - } - - // Patch to key filters properly for smoother recycling - // Thank u discord for keying every filter type the same thing!! /s - private fun fixFiltersKeying() { - patcher.instead( - "getFilterItem", - FilterSuggestion::class.java, - ) { (_, suggestion: FilterSuggestion) -> - SingleTypePayload(suggestion, suggestion.filterType.name, 2) // 2 = WidgetSearchSuggestionsAdapter.TYPE_FILTER - } - } - - // YES DISCORD TYPO'ED THIS HAHAHAHAHAHAFAUHFAIUFHAIFBHUKFHYRISFSUOIRN - private fun fixHasFilterSuggestion() { - patcher.before( - "getStringRepresentation", - FilterType::class.java, - SearchStringProvider::class.java, - ) { (param, filter: FilterType, provider: SearchStringProvider) -> - if (filter == FilterType.HAS) { - param.result = provider.hasFilterString + ":" - } - } - } - - // Patch out the gigantic padding in search results - private fun fixSearchPadding() { - patcher.after("onViewBound", View::class.java) { - view?.run { - fitsSystemWindows = false - setPadding(paddingLeft, 16.dp, paddingRight, paddingBottom) - } - } - - patcher.after("onViewBound", View::class.java) { - // Being a bit sneaky and reset the expanded flag here - optionsExpanded = false - view?.run { - fitsSystemWindows = false - setPadding(paddingLeft, 16.dp, paddingRight, paddingBottom) - } - } - } - - // Patches various methods that use HasAnswerOption to include our new options - private fun patchHasAnswerOption() { - patcher.before( - "getOptionFromString", - String::class.java, - SearchStringProvider::class.java - ) { param -> - val str = param.args[0] as String - if (str == ssProvider.hasPollString) - param.result = HasAnswerOptionExtension.POLL - else if (str == ssProvider.hasForwardString) - param.result = HasAnswerOptionExtension.SNAPSHOT - } - - patcher.before( - "getLocalizedInputText", - SearchStringProvider::class.java - ) { param -> - if (this == HasAnswerOptionExtension.POLL) - param.result = ssProvider.hasPollString - else if (this == HasAnswerOptionExtension.SNAPSHOT) - param.result = ssProvider.hasForwardString - } - - patcher.instead( - "createHasAnswerRegex", - SearchStringProvider::class.java - ) { param -> - val ossProvider = param.args[0] as SearchStringProvider - - val matches = HasAnswerOption.values().joinToString("|") { it.getLocalizedInputText(ossProvider) } - "^\\s*($matches)" - } - - // Patch to set icons - patcher.before( - "onConfigure", - Int::class.java, - MGRecyclerDataPayload::class.java, - ) { param -> - val suggestion = (param.args[1] as SingleTypePayload).data - val option = suggestion.hasAnswerOption - - val resID = when (option) { - HasAnswerOptionExtension.POLL -> "baseline_poll_24" - HasAnswerOptionExtension.SNAPSHOT -> "baseline_forward_to_inbox_24" - else -> null - } - - resID?.let { - val bindingField = this::class.java.getDeclaredField("binding") - bindingField.isAccessible = true - val binding = bindingField.get(this) as WidgetSearchSuggestionsItemHasBinding - - binding.d.text = option.getLocalizedInputText(null) - binding.b.setOnClickListener { - WidgetSearchSuggestionsAdapter.HasViewHolder.`access$getAdapter$p`(this).onHasClicked.invoke(option) - } - - binding.c.setImageDrawable(scoutRes.getDrawable(it)) - - param.result = null - } - } - - patcher.instead( - "getHasSuggestions", - CharSequence::class.java, - FilterType::class.java, - SearchStringProvider::class.java, - ) { (_, query: CharSequence, type: FilterType, provider: SearchStringProvider) -> - // Generate entries for author type - if (type == FilterTypeExtension.AUTHOR_TYPE) { - return@instead AuthorType.values() - .filter { it.value.contains(query) } - .map { AuthorTypeSuggestion(it) } - } - - // Generate entries for has options, including new ones - if (type == FilterType.HAS || type == FilterTypeExtension.EXCLUDE) - return@instead HasAnswerOption.values() - .filter { it.getLocalizedInputText(provider).contains(query) } - .map { HasSuggestion(it) } - - listOf() - } - } - - // Patching HasNode related methods for our exclude: filter type - private fun patchHasNode() { - patcher.instead("getValidFilters") { - setOf(FilterTypeExtension.EXCLUDE, FilterType.HAS) - } - - // Patch updateQuery to either include or exclude our has option - patcher.instead( - "updateQuery", - SearchQuery.Builder::class.java, - SearchData::class.java, - FilterType::class.java, - ) { param -> - val builder = param.args[0] as SearchQuery.Builder? - val filterType = param.args[2] as FilterType - - checkNotNull(builder) { "queryBuilder" } - - val field = HasNode::class.java.getDeclaredField("hasAnswerOption") - field.isAccessible = true - val opt = field.get(this) as HasAnswerOption - - if (filterType == FilterType.HAS) - builder.appendParam("has", opt.restParamValue) - else if (filterType == FilterTypeExtension.EXCLUDE) - builder.appendParam("has", "-" + opt.restParamValue) - } - - // Patching the behaviour when the has suggestion is clicked - patcher.before( - "onHasClicked", - HasAnswerOption::class.java, - CharSequence::class.java, - CharSequence::class.java, - List::class.java, - ) { param -> - val opt = param.args[0] as HasAnswerOption - val hasFilterText = param.args[1] as CharSequence - val filterAnswer = param.args[2] as CharSequence - val query = param.args[3] as List - - val replaceAndPublish = StoreSearchInput::class.java.getDeclaredMethod( - "replaceAndPublish", - Int::class.javaPrimitiveType!!, - List::class.java, - List::class.java - ) - replaceAndPublish.isAccessible = true - - val getAnswerReplacementStart = StoreSearchInput::class.java.getDeclaredMethod( - "getAnswerReplacementStart", - List::class.java, - ) - getAnswerReplacementStart.isAccessible = true - - val replacementIdx = getAnswerReplacementStart.invoke(this, query) as Int - val previousFilterText = query[replacementIdx] - val filterNode = if (previousFilterText.text == ssProvider.excludeFilterString) - FilterNode(FilterTypeExtension.EXCLUDE, ssProvider.excludeFilterString) - else - FilterNode(FilterType.HAS, hasFilterText) - - replaceAndPublish.invoke(this, replacementIdx, listOf(filterNode, HasNode(opt, filterAnswer)), query) - } - } - - // Patches the search query to also insert `min_id`, required for searching "after:" and "during:" - private fun patchQuery() { - patcher.patch( - `SearchFetcher$getRestObservable$3`::class.java.getDeclaredMethod("call", Integer::class.java), - PreHook { param -> - val self = param.thisObject as `SearchFetcher$getRestObservable$3`<*, *> - val retryAttempts = param.args[0] as Int? - val params = self.`$searchQuery`.params - - var minID = params["min_id"] - var maxID = params["max_id"] - val sortOrder = params["sort_order"] - val authorType = params["author_type"] - self.`$oldestMessageId`?.let { - if (sortOrder?.getOrNull(0) == "asc") - minID = listOf(it.toString()) - else - maxID = listOf(it.toString()) - } - - param.result = if (self.`$searchTarget`.type == StoreSearch.SearchTarget.Type.GUILD) - searchApi.searchGuildMessages( - self.`$searchTarget`.id, - minID, - maxID, - params["author_id"], - params["mentions"], - params["channel_id"], - params["has"], - params["content"], - retryAttempts, - self.`$searchQuery`.includeNsfw, - listOf("timestamp"), - sortOrder, - authorType, - ) - else - searchApi.searchChannelMessages( - self.`$searchTarget`.id, - minID, - maxID, - params["author_id"], - params["mentions"], - params["has"], - params["content"], - retryAttempts, - self.`$searchQuery`.includeNsfw, - listOf("timestamp"), - sortOrder, - authorType, - ) - } - ) - } - - // Patch parser for date parsing - private fun patchQueryParser() { - patcher.after(SearchStringProvider::class.java) { - // We need to access and insert into the rules before the rest - val field = Parser::class.java.getDeclaredField("rules").apply { isAccessible = true } - val rules = field.get(this) as ArrayList> - rules.addAll(0, listOf( - UserIdNode.getUserIdRule(), - DateNode.getBeforeRule(ssProvider.beforeFilterString), - DateNode.getDuringRule(ssProvider.duringFilterString), - DateNode.getAfterRule(ssProvider.afterFilterString), - DateNode.getDateRule(), - SortNode.getFilterRule(ssProvider.sortFilterString), - SortNode.getSortRule(ssProvider), - AuthorTypeNode.getFilterRule(ssProvider.authorTypeFilter), - AuthorTypeNode.getAuthorTypesRule(), - SimpleParserRule(Pattern.compile("^\\s*?${ssProvider.excludeFilterString}:", 64)) { _, _, obj -> - ParseSpec(FilterNode(FilterTypeExtension.EXCLUDE, ssProvider.excludeFilterString), obj) - } - )) - } - } - - // This is probably the worst bit of this plugin - @SuppressLint("SetTextI18n") - private fun patchSearchUI(context: Context) { - // Run when a filter suggestion is clicked - // Most of the code is copied from its implementation - // Patch needed to support the new filter types - patcher.before( - "onFilterClicked", - FilterType::class.java, - SearchStringProvider::class.java, - List::class.java, - ) { param -> - val filter = param.args[0] as FilterType - if (filter !in FilterTypeExtension.values) - return@before // Exit if not an extended filter type - - val replaceAndPublish = StoreSearchInput::class.java.getDeclaredMethod( - "replaceAndPublish", - Int::class.javaPrimitiveType!!, - List::class.java, - List::class.java - ) - replaceAndPublish.isAccessible = true - - val getAnswerReplacementStart = StoreSearchInput::class.java.getDeclaredMethod( - "getAnswerReplacementStart", - List::class.java, - ) - getAnswerReplacementStart.isAccessible = true - - // Original implementation - val filterNode = FilterNode(filter, ssProvider.stringFor(filter)) - val list = (param.args[2] as List).toMutableList() - val lastIndex = if (list.isEmpty()) { - 0 - } else if (list.last() is ContentNode) - list.lastIndex - else - list.size - - // Open a Date Picker - if (filter in FilterTypeExtension.dates) { - replaceAndPublish.invoke(this, lastIndex, listOf(filterNode), list) - DatePickerFragment.open(Utils.appActivity.supportFragmentManager) { - replaceAndPublish.invoke(this, - getAnswerReplacementStart.invoke(this, list), - listOf(filterNode, DateNode(it)), - list - ) - } - } - - if (filter == FilterTypeExtension.SORT) - replaceAndPublish.invoke(this, - lastIndex, - listOf(filterNode, SortNode(ssProvider.sortOldString)), - list - ) - - if (filter == FilterTypeExtension.EXCLUDE) - replaceAndPublish.invoke(this, - lastIndex, - listOf(filterNode), - list - ) - - if (filter == FilterTypeExtension.AUTHOR_TYPE) - replaceAndPublish.invoke(this, - lastIndex, - listOf(filterNode), - list - ) - - param.result = null - } - - // Patch to set icons - @Suppress("ResourceType") - patcher.before( - "getIconDrawable", - Context::class.java, - FilterType::class.java - ) { param -> - val type = param.args[1] as FilterType - val (isDiscord, resID) = when (type) { - FilterTypeExtension.BEFORE -> true to R.e.ic_history_white_24dp - FilterTypeExtension.DURING -> false to scoutRes.getDrawableId("baseline_clock_24") - FilterTypeExtension.AFTER -> false to scoutRes.getDrawableId("baseline_update_24") - FilterTypeExtension.SORT -> true to R.e.ic_sort_white_24dp - FilterTypeExtension.EXCLUDE -> false to scoutRes.getDrawableId("baseline_do_disturb_on_24") - FilterTypeExtension.AUTHOR_TYPE -> true to R.e.ic_members_24dp - else -> false to null - } - - resID?.let { - val res = if (isDiscord) context.resources else resources!! - param.result = ResourcesCompat.getDrawable(res, it, null) - } - } - - // Patch for retrieving sample filter answer/placeholder - patcher.before( - "getAnswerText", - FilterType::class.java - ) { param -> - val type = param.args[0] as FilterType - if (type in FilterTypeExtension.dates) - param.result = ssProvider.getIdentifier("search_answer_date") - if (type == FilterTypeExtension.SORT) - param.result = ScoutResource.SORT_ANSWER - if (type == FilterTypeExtension.EXCLUDE) - param.result = ssProvider.getIdentifier("search_answer_has") - if (type == FilterTypeExtension.AUTHOR_TYPE) - param.result = ScoutResource.AUTHOR_TYPE_ANSWER - } - - // Patch for retrieving filter name - patcher.before( - "getFilterText", - FilterType::class.java - ) { param -> - val type = param.args[0] as FilterType - val res = when (type) { - FilterTypeExtension.EXCLUDE -> ScoutResource.EXCLUDE_FILTER - FilterTypeExtension.BEFORE -> ssProvider.getIdentifier("search_filter_before") - FilterTypeExtension.DURING -> ssProvider.getIdentifier("search_filter_during") - FilterTypeExtension.AFTER -> ssProvider.getIdentifier("search_filter_after") - FilterTypeExtension.SORT -> ScoutResource.SORT_FILTER - FilterTypeExtension.AUTHOR_TYPE -> ScoutResource.AUTHOR_TYPE_FILTER - else -> null - } - res?.let { param.result = it } - } - - // Patch formatting utils to use our custom lowercase strings - // This is called by FilterViewHolder.onConfigure, using the results from getAnswerText and getFilterText - patcher.patch( - FormatUtils::class.java.getDeclaredMethod( - "c", - Resources::class.java, - Int::class.javaPrimitiveType!!, - Array::class.java, - Function1::class.java - ), - PreHook { param -> - val resID = param.args[1] as Int - val objArr = param.args[2] as Array<*> - val override = when (resID) { - ScoutResource.SORT_FILTER -> ssProvider.sortFilterString - ScoutResource.SORT_ANSWER -> ssProvider.sortOldString - ScoutResource.EXCLUDE_FILTER -> ssProvider.excludeFilterString - ScoutResource.AUTHOR_TYPE_FILTER -> ssProvider.authorTypeFilter - ScoutResource.AUTHOR_TYPE_ANSWER -> ssProvider.authorTypeAnswer - else -> null - } - override?.let { - param.result = FormatUtils.g(it, objArr.copyOf(), param.args[3] as b.a.k.`b$b`) - } - } - ) - - // Patch to manually configure expander, need to do this to update the suggestions widget - patcher.before( - "onConfigure", - Int::class.javaPrimitiveType!!, - MGRecyclerDataPayload::class.java, - ) { (param, _: Int, payload: SingleTypePayload) -> - val suggestion = payload.data - if (suggestion.filterType != FilterTypeExtension.EXPAND) { - return@before - } - param.result = null - - val sampleText = binding.b - val layout = binding.c - val filterText = binding.d - val icon = binding.e - layout.setOnClickListener { - val onFilter = adapter.onFilterClicked as `WidgetSearchSuggestions$configureUI$1` - val widget = onFilter.`this$0` - optionsExpanded = true - WidgetSearchSuggestions.Model.Companion!!.get(ContextSearchStringProvider(context)).z().subscribe { - WidgetSearchSuggestions.`access$configureUI`(widget, this) - } - } - sampleText.text = null - filterText.text = ssProvider.expandFilterString - val drawable = R.e.ic_chevron_right_primary_300_12dp - icon.setImageDrawable(ResourcesCompat.getDrawable(context.resources, drawable, null)) - } - - // Patch to add our new filters into the initial suggestions - patcher.after( - "getFilterSuggestions", - CharSequence::class.java, - SearchStringProvider::class.java, - Boolean::class.javaPrimitiveType!!, - ) { (param, query: CharSequence) -> - val res = (param.result as List).toMutableList() - - if (optionsExpanded || query != "") { - for (type in FilterTypeExtension.filters) { - val st = ssProvider.stringFor(type) + ":" - - if (st.contains(query)) - res.add(FilterSuggestion(type)) - } - } else { - res.add(FilterSuggestion(FilterTypeExtension.EXPAND)) - } - param.result = res.toList() - } - - // Patch to add header for new categories - patcher.before( - "onConfigure", - Int::class.javaPrimitiveType!!, - MGRecyclerDataPayload::class.java, - ) { (param, _: Int, payload: SingleTypePayload) -> - val category = payload.data - if (category == SuggestionCategoryExtension.AUTHOR_TYPE) { - binding.b.text = "Author Type" - param.result = null - } - } - - // Patch to add entries depending on category - patcher.after( - List::class.java, - List::class.java, - ) { (_, _: List, suggestions: List) -> - var lastCategory: SearchSuggestion.Category? = null - val newItems = mutableListOf() - suggestions.forEach { - if (it is AuthorTypeSuggestion) { - if (lastCategory != it.category) { - newItems.add( - SingleTypePayload(it.category, it.category.name, 0) - ) - lastCategory = it.category - } - newItems.add( - SingleTypePayload(it, it.type.value, SuggestionCategoryExtension.AdapterType.AUTHOR_TYPE) - ) - } - } - suggestionItems.removeAll { it in newItems } - suggestionItems.addAll(0, newItems) - } - - // Patch to add new types of suggestion entries - patcher.before( - "onCreateViewHolder", - ViewGroup::class.java, - Int::class.javaPrimitiveType!!, - ) { (param, _: ViewGroup, id: Int) -> - when (id) { - SuggestionCategoryExtension.AdapterType.AUTHOR_TYPE -> { - param.result = AuthorTypeViewHolder(this, scoutRes) - } - } - } - } - - // Adds support for searching in threads - private fun patchThreadSupport() { - // Patch query parser for in: to support names with spaces, by wrapping them in quotes - // This enables searching for threads which can have spaces in their names - patcher.instead("getInAnswerRule") { - val compile = Pattern.compile("^\\s*#(\".*?\"|[^ ]+)", 64) - `QueryParser$Companion$getInAnswerRule$1`(compile, compile) - } - - // Patch Search data model builder to also add in threads - patcher.before( - "buildForGuild", - Map::class.java, - Map::class.java, - Map::class.java, - Map::class.java - ) { ( - param, - /* members */ _: Map, - /* users*/ _: Map, - channels: Map, - permissions: Map - ) -> - val threads = StoreStream.getChannels().`getThreadsForGuildInternal$app_productionGoogleRelease`( - StoreStream.getGuildSelected().selectedGuildId - ) - val mergedChannels = channels.toMutableMap() - val mergedPermissions = permissions.toMutableMap() - for (thread in threads) { - mergedChannels[thread.id] = thread - mergedPermissions[thread.id] = Permission.VIEW_CHANNEL - } - param.args[2] = mergedChannels - param.args[3] = mergedPermissions - } - - // Post-process the name-id map to wrap the names in quotes if they have spaces - patcher.after( - "buildForGuild", - Map::class.java, - Map::class.java, - Map::class.java, - Map::class.java - ) { param -> - val res = param.result as SearchData - val nameMap = res.channelNameIndex as HashMap - nameMap - .filter { (name) -> name.contains(" ") } - .forEach { (name, value) -> - val wrapped = "\"${name}\"" - nameMap.remove(name) - nameMap[wrapped] = value - } - } - - // Patch the channel node to automatically insert quotes for names with spaces - patcher.before(String::class.java) { (param, name: String) -> - if (name.contains(" ") && !name.startsWith("\"")) - param.args[0] = "\"${name}\"" - } - - // Patch the search sorter to place threads last - patcher.before<`ChannelUtils$getSortByNameAndType$1`<*>>( - "compare", - Object::class.java, // ?? :sob: - Object::class.java, - ) { (param, ch1: Channel?, ch2: Channel?) -> - if (ch1 == null || ch2 == null) return@before - - // ChannelUtils.H <=> ChannelUtils.isThread - if (ChannelUtils.H(ch1) && !ChannelUtils.H(ch2)) { - param.result = 1 - } - if (!ChannelUtils.H(ch1) && ChannelUtils.H(ch2)) { - param.result = -1 - } - } - - // Patch search suggestions to set icon to thread icon if it is a thread - patcher.after( - "onConfigure", - Int::class.javaPrimitiveType!!, - MGRecyclerDataPayload::class.java - ) { (_, _: Int, payload: SingleTypePayload) -> - StoreStream.getChannels().getChannel(payload.data.channelId)?.let { - if (ChannelUtils.H(it)) { - itemView.findViewById("search_suggestions_item_channel_icon") - .setImageDrawable(scoutRes.getDrawable("ic_thread_actually_white_24dp")) - } - } - } - } - - // Removes the #0000 discriminator from usernames when searching - private fun patchUsernameDiscriminator() { - // Change the regex for the user rule - // Previously it matches something like # - // Now it matches something like @[#] (bots still have discriminators) - // The @ is required unfortunately, to distinguish it from literally any other word - patcher.instead("getUserRule") { - val regex = Pattern.compile("^\\s*@(?:([^@#:]+)#([0-9]{4})|([a-z0-9._]{2,32}))", 64) - - // Returns a new rule to support our optional second group (discriminator) - return@instead SimpleParserRule(regex) { matcher, _, obj -> - val username = matcher.group(3) ?: matcher.group(1)!! - val discrim = matcher.group(2)?.toInt() ?: 0 - ParseSpec(UserNode(username, discrim), obj) - } - } - - // Patches the node's string representation to add an @ and remove empty discriminators - patcher.after("getText") { param -> - param.result = "@" + (param.result as String).replace("#0000", "") - } - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/SuggestionCategoryExtension.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/SuggestionCategoryExtension.kt deleted file mode 100644 index 2fcc40b..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/SuggestionCategoryExtension.kt +++ /dev/null @@ -1,12 +0,0 @@ -package moe.lava.awoocord.scout - -import com.discord.utilities.search.suggestion.entries.SearchSuggestion - -object SuggestionCategoryExtension { - lateinit var AUTHOR_TYPE: SearchSuggestion.Category - lateinit var values: Array - - object AdapterType { - const val AUTHOR_TYPE = 7 - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/api/SearchAPIInterface.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/api/SearchAPIInterface.kt deleted file mode 100644 index c3b45e4..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/api/SearchAPIInterface.kt +++ /dev/null @@ -1,46 +0,0 @@ -package moe.lava.awoocord.scout.api - -import com.discord.models.domain.ModelSearchResponse -import i0.f0.f -import i0.f0.s -import i0.f0.t -import rx.Observable - -// io.f0.f = retrofit @GET -// io.f0.s = retrofit @Path -// io.f0.t = retrofit @Query - -interface SearchAPIInterface { - @f("channels/{channelId}/messages/search") - fun searchChannelMessages( - @s("channelId") channelId: Long, - @t("min_id") minId: List?, - @t("max_id") maxId: List?, - @t("author_id") authorId: List?, - @t("mentions") mentions: List?, - @t("has") has: List?, - @t("content") content: List?, - @t("attempts") attempts: Int?, - @t("include_nsfw") includeNsfw: Boolean?, - @t("sort_by") sortBy: List?, // "timestamp" is one, not sure about any other sort types - @t("sort_order") sortOrder: List?, // "asc" or "desc" - @t("author_type") authorType: List?, - ): Observable - - @f("guilds/{guildId}/messages/search") - fun searchGuildMessages( - @s("guildId") guildId: Long, - @t("min_id") minId: List?, - @t("max_id") maxId: List?, - @t("author_id") authorId: List?, - @t("mentions") mentions: List?, - @t("channel_id") channelId: List?, - @t("has") has: List?, - @t("content") content: List?, - @t("attempts") attempts: Int?, - @t("include_nsfw") includeNsfw: Boolean?, - @t("sort_by") sortBy: List?, - @t("sort_order") sortOrder: List?, - @t("author_type") authorType: List?, - ): Observable -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeSuggestion.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeSuggestion.kt deleted file mode 100644 index b989e03..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeSuggestion.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.lava.awoocord.scout.entries - -import com.discord.utilities.search.suggestion.entries.SearchSuggestion -import moe.lava.awoocord.scout.SuggestionCategoryExtension -import moe.lava.awoocord.scout.parsing.AuthorType - -data class AuthorTypeSuggestion(val type: AuthorType) : SearchSuggestion { - override fun getCategory() = SuggestionCategoryExtension.AUTHOR_TYPE -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeViewHolder.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeViewHolder.kt deleted file mode 100644 index afb1161..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeViewHolder.kt +++ /dev/null @@ -1,77 +0,0 @@ -package moe.lava.awoocord.scout.entries - -import android.widget.ImageView -import android.widget.TextView -import com.aliucord.Utils -import com.aliucord.utils.ViewUtils.findViewById -import com.discord.stores.StoreSearchInput -import com.discord.stores.StoreStream -import com.discord.utilities.mg_recycler.MGRecyclerDataPayload -import com.discord.utilities.mg_recycler.MGRecyclerViewHolder -import com.discord.utilities.mg_recycler.SingleTypePayload -import com.discord.utilities.search.query.node.filter.FilterNode -import com.discord.widgets.search.suggestions.`WidgetSearchSuggestions$configureUI$4` -import com.discord.widgets.search.suggestions.WidgetSearchSuggestionsAdapter -import com.lytefast.flexinput.R -import moe.lava.awoocord.scout.FilterTypeExtension -import moe.lava.awoocord.scout.parsing.AuthorType -import moe.lava.awoocord.scout.parsing.AuthorTypeNode -import moe.lava.awoocord.scout.ui.ScoutResource - -private val replaceAndPublish = StoreSearchInput::class.java.getDeclaredMethod( - "replaceAndPublish", - Int::class.javaPrimitiveType!!, - List::class.java, - List::class.java -).apply { isAccessible = true } - -private val getAnswerReplacementStart = StoreSearchInput::class.java.getDeclaredMethod( - "getAnswerReplacementStart", - List::class.java, -).apply { isAccessible = true } - -class AuthorTypeViewHolder( - adapter: WidgetSearchSuggestionsAdapter, - // This should be fine (?) - private val scoutRes: ScoutResource, -) : MGRecyclerViewHolder( - Utils.getResId("widget_search_suggestions_item_has", "layout"), - adapter, -) { - private val imageView = itemView.findViewById("search_suggestions_item_has_icon") - private val textView = itemView.findViewById("search_suggestions_item_has_text") - - override fun onConfigure(i: Int, oPayload: MGRecyclerDataPayload) { - super.onConfigure(i, oPayload) - - @Suppress("UNCHECKED_CAST") - val payload = oPayload as SingleTypePayload - val type = payload.data.type - textView.text = when (type) { - AuthorType.Bot -> "bot" - AuthorType.User -> "user" - AuthorType.Webhook -> "webhook" - } - when (type) { - AuthorType.Bot -> imageView.setImageDrawable(scoutRes.getDrawable("smart_toy_24px")) - AuthorType.User -> imageView.setImageResource(R.e.ic_members_24dp) - AuthorType.Webhook -> imageView.setImageDrawable(scoutRes.getDrawable("webhook_24px")) - } - - itemView.setOnClickListener { - val hasHandler = adapter.onHasClicked as `WidgetSearchSuggestions$configureUI$4` - val query = hasHandler.`$model`.query - - val storeInput = StoreStream.getSearch().storeSearchInput - replaceAndPublish.invoke( - storeInput, - getAnswerReplacementStart.invoke(storeInput, query) as Int, - listOf( - FilterNode(FilterTypeExtension.AUTHOR_TYPE, "authorType"), - AuthorTypeNode(type) - ), - query, - ) - } - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/AuthorTypeNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/AuthorTypeNode.kt deleted file mode 100644 index ecee669..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/AuthorTypeNode.kt +++ /dev/null @@ -1,64 +0,0 @@ -@file:Suppress("EnumValuesSoftDeprecate") - -package moe.lava.awoocord.scout.parsing - -import android.content.Context -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Rule -import com.discord.utilities.search.network.SearchQuery -import com.discord.utilities.search.query.FilterType -import com.discord.utilities.search.query.node.QueryNode -import com.discord.utilities.search.query.node.answer.AnswerNode -import com.discord.utilities.search.query.node.filter.FilterNode -import com.discord.utilities.search.validation.SearchData -import moe.lava.awoocord.scout.FilterTypeExtension -import java.util.regex.Pattern - -// TODO: not localised, maybe one day -enum class AuthorType(val value: String) { - User("user"), - Bot("bot"), - Webhook("webhook"), - ; - - companion object { - fun from(value: String) = when (value) { - "user" -> User - "bot" -> Bot - "webhook" -> Webhook - else -> throw IllegalArgumentException("Unknown author type $value") - } - } -} - -class AuthorTypeNode(val type: AuthorType): AnswerNode() { - companion object { - fun getAuthorTypesRule(): Rule { - val joined = AuthorType.values().joinToString("|") { it.value } - val regexStr = "^\\s*(${joined})" - val regex = Pattern.compile(regexStr, Pattern.UNICODE_CASE) - return SimpleParserRule(regex) { matcher, _, obj -> - ParseSpec(AuthorTypeNode(AuthorType.from(matcher.group())), obj) - } - } - - fun getFilterRule(str: String): ParserRule { - val regex = Pattern.compile("^\\s*?(${str}):", 64) - return SimpleParserRule(regex) { _, _, obj -> - ParseSpec(FilterNode(FilterTypeExtension.AUTHOR_TYPE, str), obj) - } - } - } - - override fun getValidFilters() = setOf(FilterTypeExtension.AUTHOR_TYPE) - override fun isValid(searchData: SearchData?) = true - override fun getText() = type.value - - override fun updateQuery( - builder: SearchQuery.Builder, - searchData: SearchData?, - filterType: FilterType? - ) { - builder.appendParam("author_type", type.value) - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/DateNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/DateNode.kt deleted file mode 100644 index f72084b..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/DateNode.kt +++ /dev/null @@ -1,73 +0,0 @@ -package moe.lava.awoocord.scout.parsing - -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.utilities.SnowflakeUtils -import com.discord.utilities.search.network.SearchQuery -import com.discord.utilities.search.query.FilterType -import com.discord.utilities.search.query.node.answer.AnswerNode -import com.discord.utilities.search.query.node.filter.FilterNode -import com.discord.utilities.search.validation.SearchData -import moe.lava.awoocord.scout.FilterTypeExtension -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.regex.Pattern - -class DateNode(private val date: Long?, private val unparsed: String) : AnswerNode() { - - constructor(unparsed: String) : this(fmt.parse(unparsed)?.time, unparsed) - - companion object { - val fmt = SimpleDateFormat("yyyy-MM-dd", Locale.US) - val regex: Pattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}", Pattern.UNICODE_CASE) - fun getDateRule(): ParserRule { - return SimpleParserRule(regex) { matcher, _, obj -> - val match = matcher.group() - val date = fmt.parse(match) - val node = DateNode(date?.time, match) - ParseSpec(node, obj) - } - } - - private fun getFilterRule(str: String, type: FilterType): ParserRule { - val regex = Pattern.compile("^\\s*?(${str}):", 64) - return SimpleParserRule(regex) { _, _, obj -> - ParseSpec(FilterNode(type, str), obj) - } - } - - fun getBeforeRule(str: String): ParserRule = getFilterRule(str, FilterTypeExtension.BEFORE) - fun getDuringRule(str: String): ParserRule = getFilterRule(str, FilterTypeExtension.DURING) - fun getAfterRule(str: String): ParserRule = getFilterRule(str, FilterTypeExtension.AFTER) - } - - override fun getValidFilters(): Set = FilterTypeExtension.dates.toSet() - override fun isValid(searchData: SearchData?): Boolean = date != null - override fun getText(): CharSequence = unparsed - - private val snowflake: String? - get() = date?.let { SnowflakeUtils.fromTimestamp(date).toString() } - private val nextDaySnowflake: String? - get() = date?.let { SnowflakeUtils.fromTimestamp(date + 86_400_000).toString() } - - override fun updateQuery( - builder: SearchQuery.Builder?, - searchData: SearchData?, - filterType: FilterType? - ) { - checkNotNull(builder) { "queryBuilder" } - checkNotNull(date) { "date" } - when (filterType) { - FilterTypeExtension.BEFORE -> { - builder.appendParam("max_id", snowflake) - } - FilterTypeExtension.AFTER -> { - builder.appendParam("min_id", nextDaySnowflake) - } - FilterTypeExtension.DURING -> { - builder.appendParam("min_id", snowflake) - builder.appendParam("max_id", nextDaySnowflake) - } - else -> return - } - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SimpleParserRule.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SimpleParserRule.kt deleted file mode 100644 index c78c23b..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SimpleParserRule.kt +++ /dev/null @@ -1,27 +0,0 @@ -package moe.lava.awoocord.scout.parsing - -import android.content.Context -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Parser -import com.discord.simpleast.core.parser.Rule -import com.discord.utilities.search.query.node.QueryNode -import java.util.regex.Matcher -import java.util.regex.Pattern - -internal typealias ParserRule = Rule -internal class SimpleParserRule( - regex: Pattern, - private val parseMethod: ( - matcher: Matcher, - parser: Parser, - obj: Any? - ) -> ParseSpec -) : ParserRule(regex) { - override fun parse( - matcher: Matcher, - parser: Parser, - obj: Any? - ): ParseSpec { - return parseMethod(matcher, parser, obj) - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SortNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SortNode.kt deleted file mode 100644 index e839712..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SortNode.kt +++ /dev/null @@ -1,45 +0,0 @@ -package moe.lava.awoocord.scout.parsing - -import android.content.Context -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Rule -import com.discord.utilities.search.network.SearchQuery -import com.discord.utilities.search.query.FilterType -import com.discord.utilities.search.query.node.QueryNode -import com.discord.utilities.search.query.node.answer.AnswerNode -import com.discord.utilities.search.query.node.filter.FilterNode -import com.discord.utilities.search.validation.SearchData -import moe.lava.awoocord.scout.FilterTypeExtension -import moe.lava.awoocord.scout.ui.ScoutSearchStringProvider -import java.util.regex.Pattern - -class SortNode(private val text: String): AnswerNode() { - companion object { - fun getSortRule(ssProvider: ScoutSearchStringProvider): Rule { - val regexStr = "^\\s*(${ssProvider.sortOldString})" - val regex = Pattern.compile(regexStr, Pattern.UNICODE_CASE) - return SimpleParserRule(regex) { _, _, obj -> - ParseSpec(SortNode(ssProvider.sortOldString), obj) - } - } - - fun getFilterRule(str: String): ParserRule { - val regex = Pattern.compile("^\\s*?(${str}):", 64) - return SimpleParserRule(regex) { _, _, obj -> - ParseSpec(FilterNode(FilterTypeExtension.SORT, str), obj) - } - } - } - - override fun getValidFilters() = setOf(FilterTypeExtension.SORT) - override fun isValid(searchData: SearchData?) = true - override fun getText() = this.text - - override fun updateQuery( - builder: SearchQuery.Builder, - searchData: SearchData?, - filterType: FilterType? - ) { - builder.appendParam("sort_order", "asc") - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/UserIdNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/UserIdNode.kt deleted file mode 100644 index 85ea6c1..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/UserIdNode.kt +++ /dev/null @@ -1,41 +0,0 @@ -package moe.lava.awoocord.scout.parsing - -import android.content.Context -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Rule -import com.discord.utilities.search.network.SearchQuery -import com.discord.utilities.search.query.FilterType -import com.discord.utilities.search.query.node.QueryNode -import com.discord.utilities.search.query.node.answer.AnswerNode -import com.discord.utilities.search.validation.SearchData -import java.util.regex.Pattern - -class UserIdNode(private val userID: String) : AnswerNode() { - companion object { - fun getUserIdRule(): Rule { - val regex = Pattern.compile("^\\d{17,19}", Pattern.UNICODE_CASE) - return SimpleParserRule(regex) { matcher, _, obj -> - ParseSpec(UserIdNode(matcher.group()), obj) - } - } - } - - override fun getValidFilters() = setOf(FilterType.FROM, FilterType.MENTIONS) - override fun isValid(searchData: SearchData?) = true - override fun getText() = userID - - override fun updateQuery( - builder: SearchQuery.Builder?, - searchData: SearchData?, - filterType: FilterType? - ) { - checkNotNull(builder) { "queryBuilder" } - checkNotNull(searchData) { "searchData" } - val str = when (filterType) { - FilterType.FROM -> "author_id" - FilterType.MENTIONS -> "mentions" - else -> return - } - builder.appendParam(str, userID) - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/DatePickerFragment.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/DatePickerFragment.kt deleted file mode 100644 index 43e967e..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/DatePickerFragment.kt +++ /dev/null @@ -1,35 +0,0 @@ -package moe.lava.awoocord.scout.ui - -import android.app.DatePickerDialog -import android.app.Dialog -import android.os.Bundle -import android.widget.DatePicker -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.FragmentManager -import java.util.Calendar - -class DatePickerFragment( - private val callback: (String) -> Unit -) : DialogFragment(), DatePickerDialog.OnDateSetListener { - companion object { - fun open(fragmentManager: FragmentManager, callback: (date: String) -> Unit) { - DatePickerFragment(callback).show(fragmentManager, "datePicker") - } - } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val calendar = Calendar.getInstance() - - val year = calendar.get(Calendar.YEAR) - val month = calendar.get(Calendar.MONTH) - val day = calendar.get(Calendar.DAY_OF_MONTH) - - return DatePickerDialog(requireContext(), android.R.style.Theme_DeviceDefault_Dialog, this, year, month, day).apply { - datePicker.maxDate = calendar.timeInMillis - } - } - - override fun onDateSet(picker: DatePicker, year: Int, month: Int, day: Int) { - callback("%04d-%02d-%02d".format(year, month + 1, day)) - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutResource.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutResource.kt deleted file mode 100644 index 59b9ed7..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutResource.kt +++ /dev/null @@ -1,28 +0,0 @@ -package moe.lava.awoocord.scout.ui - -import android.content.res.Resources -import android.view.View -import androidx.annotation.DrawableRes -import androidx.core.content.res.ResourcesCompat - -class ScoutResource(private val resources: Resources) { - companion object { - val SORT_FILTER = View.generateViewId() - val SORT_ANSWER = View.generateViewId() - val EXCLUDE_FILTER = View.generateViewId() - val AUTHOR_TYPE_FILTER = View.generateViewId() - val AUTHOR_TYPE_ANSWER = View.generateViewId() - } - - fun getId(name: String, type: String) = - resources.getIdentifier(name, type, "moe.lava.awoocord.scout") - - @DrawableRes fun getDrawableId(name: String) = - getId(name, "drawable") - - fun getDrawable(@DrawableRes id: Int) = - ResourcesCompat.getDrawable(resources, id, null) - - fun getDrawable(name: String) = - getDrawable(getDrawableId(name)) -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt deleted file mode 100644 index d4accb2..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt +++ /dev/null @@ -1,60 +0,0 @@ -package moe.lava.awoocord.scout.ui - -import android.content.Context -import com.discord.utilities.search.query.FilterType -import com.discord.utilities.search.query.node.answer.HasAnswerOption -import moe.lava.awoocord.scout.FilterTypeExtension -import moe.lava.awoocord.scout.HasAnswerOptionExtension - -private fun String.decapitalise(context: Context) = - this.replaceFirstChar { it.lowercase(context.resources.configuration.locales[0]) } - -class ScoutSearchStringProvider(private val context: Context) { - fun getIdentifier(name: String) = - context.resources.getIdentifier(name, "string", "com.discord") - fun getString(name: String) = - context.getString(getIdentifier(name)) - - fun stringFor(type: FilterType) = when (type) { - FilterTypeExtension.EXCLUDE -> excludeFilterString - FilterTypeExtension.BEFORE -> beforeFilterString - FilterTypeExtension.DURING -> duringFilterString - FilterTypeExtension.AFTER -> afterFilterString - FilterTypeExtension.SORT -> sortFilterString - FilterTypeExtension.AUTHOR_TYPE -> authorTypeFilter - else -> throw IllegalArgumentException("invalid extended filter type") - } - - fun stringFor(type: HasAnswerOption) = when (type) { - HasAnswerOptionExtension.POLL -> hasPollString - HasAnswerOptionExtension.SNAPSHOT -> hasForwardString - else -> throw IllegalArgumentException("invalid extended filter type") - } - - // Surprising!! Discord has localised strings of these - val beforeFilterString: String - get() = getString("search_filter_before") - val duringFilterString: String - get() = getString("search_filter_during") - val afterFilterString: String - get() = getString("search_filter_after") - val sortFilterString: String - get() = getString("sort").decapitalise(context) - val sortOldString: String - get() = getString("search_oldest_short").decapitalise(context) - val expandFilterString: String - get() = getString("friends_pending_request_expand") - - // Not localised - val hasPollString: String - get() = "poll" - val hasForwardString: String - get() = "forward" - val excludeFilterString: String - get() = "exclude" - val authorTypeFilter: String - get() = "authorType" - val authorTypeAnswer: String - // TODO, could probably be localisable by joining each part together - get() = "user, bot or webhook" -} diff --git a/plugins/Scout/src/main/res/drawable/baseline_clock_24.xml b/plugins/Scout/src/main/res/drawable/baseline_clock_24.xml deleted file mode 100644 index c95f574..0000000 --- a/plugins/Scout/src/main/res/drawable/baseline_clock_24.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/plugins/Scout/src/main/res/drawable/baseline_do_disturb_on_24.xml b/plugins/Scout/src/main/res/drawable/baseline_do_disturb_on_24.xml deleted file mode 100644 index 2c537f4..0000000 --- a/plugins/Scout/src/main/res/drawable/baseline_do_disturb_on_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/plugins/Scout/src/main/res/drawable/baseline_forward_to_inbox_24.xml b/plugins/Scout/src/main/res/drawable/baseline_forward_to_inbox_24.xml deleted file mode 100644 index 2ae86b4..0000000 --- a/plugins/Scout/src/main/res/drawable/baseline_forward_to_inbox_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/plugins/Scout/src/main/res/drawable/baseline_poll_24.xml b/plugins/Scout/src/main/res/drawable/baseline_poll_24.xml deleted file mode 100644 index 80380fb..0000000 --- a/plugins/Scout/src/main/res/drawable/baseline_poll_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/plugins/Scout/src/main/res/drawable/baseline_update_24.xml b/plugins/Scout/src/main/res/drawable/baseline_update_24.xml deleted file mode 100644 index 579fa47..0000000 --- a/plugins/Scout/src/main/res/drawable/baseline_update_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/plugins/Scout/src/main/res/drawable/ic_thread_actually_white_24dp.xml b/plugins/Scout/src/main/res/drawable/ic_thread_actually_white_24dp.xml deleted file mode 100644 index 560be79..0000000 --- a/plugins/Scout/src/main/res/drawable/ic_thread_actually_white_24dp.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/plugins/Scout/src/main/res/drawable/smart_toy_24px.xml b/plugins/Scout/src/main/res/drawable/smart_toy_24px.xml deleted file mode 100644 index 30dd4b2..0000000 --- a/plugins/Scout/src/main/res/drawable/smart_toy_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/plugins/Scout/src/main/res/drawable/webhook_24px.xml b/plugins/Scout/src/main/res/drawable/webhook_24px.xml deleted file mode 100644 index 74a3dc7..0000000 --- a/plugins/Scout/src/main/res/drawable/webhook_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/plugins/Zinnia/build.gradle.kts b/plugins/Zinnia/build.gradle.kts deleted file mode 100644 index eec7d36..0000000 --- a/plugins/Zinnia/build.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -version = "1.2.1" -description = "Coloured usernames to be a bit more pleasing on the eyes" - -aliucord { - // Changelog of your plugin - changelog.set(""" - # 1.2.1 - * Use correct default block colour in replies - * Use correct default block colour in "unchanged" mode - - # 1.2.0 - * Finally fixes the annoying padding issue in replies - * Adds nice preview blocks in settings with configurable hsv bars for all your previewing needs - * Tweaked constrast ratio a bit which may improve some colours' legibility - * Added transparency option, alongside "unchanged" colour option which pairs nicely together for a translucent glass effect - - # 1.1.1 - * Revert incorrect spacing fix, since it just breaks replies. Proper fix soon - - # 1.1.0 - * Fix incorrect spacing in replies - * Allow setting static text colours - - # 1.0.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} diff --git a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCA.kt b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCA.kt deleted file mode 100644 index e098626..0000000 --- a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCA.kt +++ /dev/null @@ -1,77 +0,0 @@ -package moe.lava.awoocord.zinnia - -import kotlin.math.abs -import kotlin.math.pow - -// https://github.com/Myndex/apca-w3/blob/c012257167d822f91bc417120bdb82e1b854b4a4/src/apca-w3.js -object APCA { - @Suppress("ConstPropertyName") - private object SA98G { - const val mainTRC = 2.4 - - const val sRco = 0.2126729 - const val sGco = 0.7151522 - const val sBco = 0.0721750 - - const val normBG = 0.56 - const val normTXT = 0.57 - const val revTXT = 0.62 - const val revBG = 0.65 - - const val blkThrs = 0.022 - const val blkClmp = 1.414 - const val scaleBoW = 1.14 - const val scaleWoB = 1.14 - const val loBoWoffset = 0.027 - const val loWoBoffset = 0.027 - const val deltaYmin = 0.0005 - const val loClip = 0.1 - } - - private fun exp(c: Int) = - (c.toDouble() / 255.0).pow(SA98G.mainTRC) - - private fun argbToY(color: Int): Double { - val r = (color shr 16) and 0xff - val g = (color shr 8) and 0xff - val b = color and 0xff - - return SA98G.run { - sRco * exp(r) + sGco * exp(g) + sBco * exp(b) - } - } - - fun contrast(fgC: Int, bgC: Int): Double { - var fg = argbToY(fgC) - var bg = argbToY(bgC) - - if (fg.coerceAtMost(bg) < 0 || fg.coerceAtLeast(bg) > 1.1) - return 0.0 - - if (fg <= SA98G.blkThrs) - fg += (SA98G.blkThrs - fg).pow(SA98G.blkClmp) - if (bg <= SA98G.blkThrs) - bg += (SA98G.blkThrs - bg).pow(SA98G.blkClmp) - - if (abs(bg - fg) < SA98G.deltaYmin) - return 0.0 - - val outputContrast = if (bg > fg) { - val sapc = (bg.pow(SA98G.normBG) - fg.pow(SA98G.normTXT)) * SA98G.scaleBoW - - if (sapc < SA98G.loClip) - 0.0 - else - sapc - SA98G.loBoWoffset - } else { - val sapc = (bg.pow(SA98G.revBG) - fg.pow(SA98G.revTXT)) * SA98G.scaleWoB - - if (sapc > -SA98G.loClip) - 0.0 - else - sapc + SA98G.loWoBoffset - } - - return outputContrast * 100 - } -} diff --git a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCAUtil.kt b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCAUtil.kt deleted file mode 100644 index fbf9466..0000000 --- a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCAUtil.kt +++ /dev/null @@ -1,117 +0,0 @@ -package moe.lava.awoocord.zinnia - -import android.graphics.Color -import android.graphics.drawable.GradientDrawable -import android.widget.TextView -import androidx.core.graphics.ColorUtils -import com.aliucord.utils.DimenUtils.dp -import com.discord.stores.StoreStream -import kotlin.math.abs - -enum class Threshold { - Large, - Medium, - Small -} - -internal object APCAUtil { - private val settings = ZinniaSettings - - internal fun configureOn(view: TextView, colour: Int?, threshold: Threshold) { - when (settings.mode) { - Mode.Block -> configureBlock(view, colour ?: Color.BLACK, threshold) - Mode.RoleDot -> configureRoleDot(view, colour ?: Color.BLACK) - } - } - - private fun configureRoleDot(view: TextView, colour: Int) { } - - private fun configureBlock(view: TextView, colourP: Int, threshold: Threshold) { - val isLight = StoreStream.getUserSettingsSystem().theme == "light" - var colour = colourP - val bcol = GradientDrawable() - bcol.cornerRadius = 4.dp.toFloat() - view.background = bcol - view.setPadding(4.dp, 0, 4.dp, 0) - - if (colour == Color.BLACK) { - if (settings.blockAlsoDefault) { - colour = if (isLight && (settings.blockInverted || settings.blockMode == BlockMode.Unchanged)) { - Color.BLACK - } else { - Color.WHITE - } - } else { - view.background = null - view.setPadding(0, 0, 0, 0) - return - } - } - - var (preferred, other) = if (isLight) { - Color.WHITE to Color.BLACK - } else { - Color.BLACK to Color.WHITE - } - when (settings.blockMode) { - BlockMode.InvertedThemeOnly -> preferred = other - BlockMode.WhiteOnly -> preferred = Color.WHITE - BlockMode.BlackOnly -> preferred = Color.BLACK - BlockMode.Unchanged -> preferred = colour - else -> {} - } - - val colours = if (!settings.blockInverted) { - Colours( - fgP = preferred, - fgO = other, - bgP = colour, - bgO = colour, - ) - } else { - Colours( - fgP = colour, - fgO = colour, - bgP = preferred, - bgO = other, - ) - } - - val usePreferred = when (settings.blockMode) { - BlockMode.ApcaOnly -> isApca(colours, threshold) - BlockMode.WcagOnly -> isWcag(colours) - BlockMode.ApcaLightWcagDark -> if (isLight) isApca(colours, threshold) else isWcag(colours) - BlockMode.WcagLightApcaDark -> if (isLight) isWcag(colours) else isApca(colours, threshold) - BlockMode.ThemeOnly, - BlockMode.InvertedThemeOnly, - BlockMode.WhiteOnly, - BlockMode.BlackOnly, - BlockMode.Unchanged -> true - } - - if (usePreferred) { - view.setTextColor(colours.fgP) - bcol.setColor(ColorUtils.setAlphaComponent(colours.bgP, settings.alpha)) - } else { - view.setTextColor(colours.fgO) - bcol.setColor(ColorUtils.setAlphaComponent(colours.bgO, settings.alpha)) - } - } - - private fun isApca(c: Colours, threshold: Threshold): Boolean { - val cPref = abs(APCA.contrast(c.fgP, c.bgP)) - val cOth = abs(APCA.contrast(c.fgO, c.bgO)) - val thresholdValue = when (threshold) { - Threshold.Large -> settings.blockApcaThresholdLarge - Threshold.Medium -> settings.blockApcaThresholdMedium - Threshold.Small -> settings.blockApcaThresholdSmall - } - return cPref > thresholdValue || cPref > cOth - } - - private fun isWcag(c: Colours): Boolean { - val cPref = ColorUtils.calculateContrast(c.fgP, c.bgP) - val cOth = ColorUtils.calculateContrast(c.fgO, c.bgO) - return cPref > settings.blockWcagThreshold || cPref > cOth - } -} diff --git a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/Zinnia.kt b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/Zinnia.kt deleted file mode 100644 index f70ebde..0000000 --- a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/Zinnia.kt +++ /dev/null @@ -1,106 +0,0 @@ -package moe.lava.awoocord.zinnia - -import android.content.Context -import android.graphics.Color -import android.view.View -import android.widget.TextView -import androidx.constraintlayout.widget.ConstraintLayout -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.entities.Plugin -import com.aliucord.patcher.after -import com.aliucord.patcher.component1 -import com.aliucord.patcher.component2 -import com.aliucord.patcher.component3 -import com.aliucord.patcher.instead -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.accessField -import com.discord.databinding.WidgetChannelMembersListItemUserBinding -import com.discord.models.member.GuildMember -import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListAdapter -import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListViewHolderMember -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage -import com.discord.widgets.chat.list.entries.ChatListEntry -import com.discord.widgets.chat.list.entries.MessageEntry - -private val ChannelMembersListViewHolderMember.binding - by accessField() -private val WidgetChatListAdapterItemMessage.itemName - by accessField() -private val WidgetChatListAdapterItemMessage.replyName - by accessField() - -data class Colours( - val fgP: Int, - val bgP: Int, - val fgO: Int, - val bgO: Int, -) - -@AliucordPlugin -class Zinnia : Plugin() { - companion object { const val NAME = "RoleBlocks" } - - init { - settingsTab = SettingsTab(ZinniaSettings.Page::class.java, SettingsTab.Type.PAGE) - } - - override fun start(context: Context) { - patchMemberList() - patchMessageAuthor() - } - - override fun stop(context: Context) { patcher.unpatchAll() } - - private fun patchMemberList() { - // Patches the method that configures the username in members list - patcher.after( - "bind", - ChannelMembersListAdapter.Item.Member::class.java, - Function0::class.java, - ) { (_, member: ChannelMembersListAdapter.Item.Member) -> - val presenceTextView = binding.d - val usernameView = binding.f - val usernameTextView = usernameView.j.c - - if (presenceTextView.visibility == View.VISIBLE) { - usernameView.layoutParams = (usernameView.layoutParams as ConstraintLayout.LayoutParams).apply { - bottomMargin = 2.dp - } - } - - APCAUtil.configureOn(usernameTextView, member.color, Threshold.Medium) - } - } - - private fun patchMessageAuthor() { - // Configures for message author username - patcher.after( - "onConfigure", - Int::class.javaPrimitiveType!!, - ChatListEntry::class.java, - ) { (_, _: Int, entry: MessageEntry) -> - itemName?.let { - APCAUtil.configureOn(it, entry.author?.color, Threshold.Large) - } - } - - patcher.instead( - "getAuthorTextColor", - GuildMember::class.java, - ) { (_, member: GuildMember?) -> - member?.color ?: Color.BLACK - } - - // Configures for reply preview username - patcher.after( - "configureReplyName", - String::class.java, - Int::class.javaPrimitiveType!!, - Boolean::class.javaPrimitiveType!!, - ) { (_, _: String, colour: Int) -> - replyName?.let { - APCAUtil.configureOn(it, colour, Threshold.Small) - } - } - } -} diff --git a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/ZinniaSettings.kt b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/ZinniaSettings.kt deleted file mode 100644 index 72ca84c..0000000 --- a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/ZinniaSettings.kt +++ /dev/null @@ -1,292 +0,0 @@ -package moe.lava.awoocord.zinnia - -import android.graphics.Color -import android.util.TypedValue -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.LinearLayout -import android.widget.SeekBar -import android.widget.TextView -import androidx.core.content.res.ResourcesCompat -import com.aliucord.Constants -import com.aliucord.Utils -import com.aliucord.api.SettingsAPI -import com.aliucord.fragments.SettingsPage -import com.aliucord.settings.delegate -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.wrappers.users.globalName -import com.discord.stores.StoreStream -import com.discord.utilities.color.ColorCompat -import com.discord.views.CheckedSetting -import com.lytefast.flexinput.R -import kotlin.math.roundToInt -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -enum class Mode { - RoleDot, - Block, -} - -enum class BlockMode { - ApcaLightWcagDark, - WcagLightApcaDark, - ApcaOnly, - WcagOnly, - ThemeOnly, - InvertedThemeOnly, - WhiteOnly, - BlackOnly, - Unchanged, -} - -class SettingsDelegateEnum>( - private val defaultValue: T, - private val settings: SettingsAPI, - private val deserialiser: (String) -> T, -) : ReadWriteProperty { - override fun getValue(thisRef: Any, property: KProperty<*>): T = - deserialiser(settings.getString(property.name, defaultValue.name)) - - override fun setValue(thisRef: Any, property: KProperty<*>, value: T) = - settings.setString(property.name, value.name) -} - -inline fun > SettingsAPI.delegateEnum( - defaultValue: T -) = SettingsDelegateEnum(defaultValue, this) { enumValueOf(it) } - -private inline fun T.addTo(parent: ViewGroup, block: T.() -> Unit = {}) = - apply { - block() - parent.addView(this) - } - -private typealias Delegate = ReadWriteProperty - -fun basicDelegate(initial: T) = object : Delegate { - private var current = initial - override fun getValue(self: Any?, prop: KProperty<*>): T = current - override fun setValue(self: Any?, prop: KProperty<*>, value: T) { current = value } -} - -private class StateDelegate( - private val inner: Delegate, - private val update: (T) -> Unit, -) : Delegate { - override fun getValue(self: Any?, prop: KProperty<*>): T = inner.getValue(self, prop) - - override fun setValue(self: Any?, prop: KProperty<*>, value: T) { - inner.setValue(self, prop, value) - update(value) - } -} - -object ZinniaSettings { - private val api = SettingsAPI(Zinnia.NAME) - - private var onStateUpdate = {} - - private inline fun reactive(backing: () -> Delegate): StateDelegate { - return StateDelegate(backing()) { onStateUpdate() } - } - - var mode by reactive { api.delegateEnum(Mode.Block) } - - var blockAlsoDefault by reactive { api.delegate(true) } - var blockInverted by reactive { api.delegate(false) } - var blockMode by reactive { api.delegateEnum(BlockMode.ApcaLightWcagDark) } - var blockApcaThresholdLarge by reactive { api.delegate(45.0f) } - var blockApcaThresholdMedium by reactive { api.delegate(45.0f) } - var blockApcaThresholdSmall by reactive { api.delegate(45.0f) } - var blockWcagThreshold by reactive { api.delegate(4.5f) } - - private val _alpha = reactive { api.delegate("alpha", 255) } - var alpha by _alpha - - class Page : SettingsPage() { - private val checks = mutableListOf() - - private val _previewH = reactive { basicDelegate(0) } - private var previewH by _previewH - private val _previewS = reactive { basicDelegate(100) } - private var previewS by _previewS - private val _previewV = reactive { basicDelegate(100) } - private var previewV by _previewV - - private fun addRadio(newMode: BlockMode, text: String, subtext: String? = null): CheckedSetting { - return Utils.createCheckedSetting(requireContext(), CheckedSetting.ViewType.RADIO, text, subtext).addTo(linearLayout) { - isChecked = blockMode == newMode - setOnCheckedListener { - for (check in checks) check.isChecked = false - blockMode = newMode - isChecked = true - } - checks.add(this) - } - } - - private fun createLabel(text: String? = null): TextView { - return TextView(context, null, 0, R.i.UiKit_TextView).apply { - textSize = 16.0f - typeface = ResourcesCompat.getFont(context, Constants.Fonts.whitney_medium) - this.text = text - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - bottomMargin = 4.dp - } - } - } - - private fun addSlider( - min: Int, - max: Int, - initial: Int = min, - onChange: (value: Int, commit: Boolean) -> String - ): LinearLayout { - var pendingValue = initial - return LinearLayout(requireContext(), null, 0, R.i.UiKit_Settings_Item).addTo(linearLayout) { - orientation = LinearLayout.VERTICAL - val display = createLabel(onChange(initial, false)).addTo(this) - SeekBar(context, null, 0, R.i.UiKit_SeekBar).addTo(this) { - this.max = max - min - progress = initial - setPadding(12.dp, 0, 12.dp, 0) - setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged( - seekBar: SeekBar, - progress: Int, - fromUser: Boolean, - ) { - pendingValue = min + progress - display.text = onChange(pendingValue, false) - } - - override fun onStartTrackingTouch(seekBar: SeekBar) {} - override fun onStopTrackingTouch(seekBar: SeekBar) { - onChange(pendingValue, true) - } - }) - } - } - } - - private fun addSlider(binding: Delegate, min: Int, max: Int, immediate: Boolean = false, label: (Int) -> String): LinearLayout { - var value by binding - return addSlider(min, max, value) { newValue, commit -> - @Suppress("AssignedValueIsNeverRead") // kt so dumb - if (immediate || commit) value = newValue - label(newValue) - } - } - - private fun createPreview( - label: String, - styleRes: Int, - ): TextView { - val ctx = requireContext() - val view = TextView(ctx, null, 0, styleRes).apply { - val me = StoreStream.getUsers().me - text = me.globalName ?: me.username - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - marginStart = 16.dp - marginEnd = 16.dp - } - } - LinearLayout(ctx, null, 0, R.i.UiKit_Settings_Item).addTo(linearLayout) { - view.addTo(this) - createLabel(label).addTo(this) { - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - bottomMargin = 0 - } - } - } - return view - } - - override fun onDestroyView() { - onStateUpdate = {} - super.onDestroyView() - } - - override fun onViewBound(view: View) { - super.onViewBound(view) - setActionBarTitle(Zinnia.NAME) - setPadding(0) - - val ctx = requireContext() - linearLayout.run { - val blockSettings = mutableListOf() - val roleDotSettings = mutableListOf() - - addHeader(ctx, "Text colour") - addRadio(BlockMode.ApcaLightWcagDark, "Automatic", "Adjusts text colour based on optimal contrast with role colour") - addRadio(BlockMode.ThemeOnly, "By theme", "Adjusts text colour based on system theme (dark/light)") - addRadio(BlockMode.InvertedThemeOnly, "By theme (inverted)", "Same as above, but inverted") - addRadio(BlockMode.WhiteOnly, "White", "Force text colour to be white") - addRadio(BlockMode.BlackOnly, "Black", "Force text colour to be black") - addRadio(BlockMode.Unchanged, "Unchanged", "Keep text colour; ideal for using with a translucent block") - - addHeader(ctx, "Block Settings") - - val invertSwitch = Utils.createCheckedSetting( - ctx, - CheckedSetting.ViewType.SWITCH, - "Invert block colours", - "By default, the role colour is applied as the block background. Turning this setting on inverts this.\nHas no effect with \"Unchanged\" colour option", - ).addTo(this) { - isChecked = blockInverted - setOnCheckedListener { - blockInverted = !blockInverted - } - blockSettings.add(this) - } - - addSlider(_alpha, 0, 255, true) { "Alpha: ${(it / 2.55f).roundToInt()}%" } - -// createSlider(0, 255, blockApcaThreshold.roundToInt()) { value, commit -> -// blockApcaThreshold = value.toFloat() -// "Apca Threshold: $value" -// } - - addHeader(ctx, "Preview") - val previews = mutableListOf( - Threshold.Large to createPreview("Message header username", R.i.UiKit_TextView_Large_SingleLine), - Threshold.Medium to createPreview("Channels list", R.i.UiKit_TextView).apply { - setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.d.uikit_textsize_medium)) - }, - Threshold.Small to createPreview("Message reply username", R.i.UiKit_TextView).apply { - setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.d.uikit_textsize_small)) - }, - ) - - val hsv = floatArrayOf(0f, 0f, 0f) - Color.colorToHSV(ColorCompat.getThemedColor(this, R.b.color_brand), hsv) - previewH = hsv[0].roundToInt() - previewS = (hsv[1] * 100).roundToInt() - previewV = (hsv[2] * 100).roundToInt() - addSlider(_previewH, 0, 360, true) { "Hue: $it" } - addSlider(_previewS, 0, 100, true) { "Saturation: $it%" } - addSlider(_previewV, 0, 100, true) { "Value: $it%" } - - onStateUpdate = { - previews.forEach { updatePreview(it) } - if (blockMode != BlockMode.Unchanged) { - invertSwitch.l.b().isClickable = true - invertSwitch.alpha = 1f - } else { - invertSwitch.l.b().isClickable = false - invertSwitch.alpha = 0.3f - } - } - onStateUpdate() - } - } - - fun updatePreview(pair: Pair) { - val (threshold, preview) = pair - val colour = Color.HSVToColor(floatArrayOf(previewH.toFloat(), previewS / 100f, previewV / 100f)) - APCAUtil.configureOn(preview, colour, threshold) - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index f6d7bdf..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -@file:Suppress("UnstableApiUsage") - -pluginManagement { - repositories { - google() - gradlePluginPortal() - maven("https://maven.aliucord.com/releases") - maven("https://maven.aliucord.com/snapshots") - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - maven("https://maven.aliucord.com/releases") - maven("https://maven.aliucord.com/snapshots") - } -} - -rootProject.name = "Awoocord" - -val plugins = mapOf( - "ComponentsV2Beta" to "canary/ComponentsV2", - "SlashCommandsFixBeta" to "canary/SlashCommandsFix", - "Bubbles" to "plugins/Crocosmia", - "Clump" to "plugins/Bocchi", - "Scout" to "plugins/Scout", - "RoleBlocks" to "plugins/Zinnia", - "Glance" to "plugins/Myosotis", -) - -include(*plugins.keys.toTypedArray()) - -rootProject.children.forEach { project -> - plugins[project.name]?.let { - project.projectDir = file(it) - } -} diff --git a/updater.json b/updater.json new file mode 100644 index 0000000..3cc5ad4 --- /dev/null +++ b/updater.json @@ -0,0 +1 @@ +{"RoleBlocks":{"version":"1.2.1","build":"https://cdn.jsdelivr.net/gh/cillynder/Awoocord@refs/heads/builds/RoleBlocks.zip","buildCrc32":"F6D9538A","changelog":"# 1.2.1\n* Use correct default block colour in replies\n* Use correct default block colour in \"unchanged\" mode\n\n# 1.2.0\n* Finally fixes the annoying padding issue in replies\n* Adds nice preview blocks in settings with configurable hsv bars for all your previewing needs\n* Tweaked constrast ratio a bit which may improve some colours' legibility\n* Added transparency option, alongside \"unchanged\" colour option which pairs nicely together for a translucent glass effect\n\n# 1.1.1\n* Revert incorrect spacing fix, since it just breaks replies. Proper fix soon\n\n# 1.1.0\n* Fix incorrect spacing in replies\n* Allow setting static text colours\n\n# 1.0.0\n* Initial release >w<","minimumDiscordVersion":126021,"minimumAliucordVersion":"2.6.0","minimumKotlinVersion":"1.5.21","minimumApiLevel":21},"Bubbles":{"version":"1.0.0","build":"https://cdn.jsdelivr.net/gh/cillynder/Awoocord@refs/heads/builds/Bubbles.zip","buildCrc32":"519E0D3A","changelog":"# 1.0.0\n* Initial release >w<","minimumDiscordVersion":126021,"minimumAliucordVersion":"2.6.0","minimumKotlinVersion":"1.5.21","minimumApiLevel":21},"Glance":{"version":"1.0.0","build":"https://cdn.jsdelivr.net/gh/cillynder/Awoocord@refs/heads/builds/Glance.zip","buildCrc32":"7BC2ECDD","changelog":"# 1.0.0\n* Initial release >w<","minimumDiscordVersion":126021,"minimumAliucordVersion":"2.6.0","minimumKotlinVersion":"1.5.21","minimumApiLevel":21},"ComponentsV2Beta":{"version":"8.8.0","build":"https://cdn.jsdelivr.net/gh/cillynder/Awoocord@refs/heads/builds/ComponentsV2Beta.zip","buildCrc32":"9BC12E76","changelog":"TODO {fixed}\n======================\n* File component\n* SelectV2: searching\n* SelectV2: showing selected items in chat list\n\nChangelog {added marginTop}\n======================\n# 8.8.0\n* Fix a possible weird crash\n\n# 8.7.0\n* Prevent ViewRaw crash\n* Add a CV2 tag to distinguish new embeds (will not be in core)\n\n# 7.15.1\n* Fix broken reply preview >w<\n\n# 7.15.0\n* Initial release >w<","minimumDiscordVersion":126021,"minimumAliucordVersion":"2.6.0","minimumKotlinVersion":"1.5.21","minimumApiLevel":21},"SlashCommandsFixBeta":{"version":"8.18.0","build":"https://cdn.jsdelivr.net/gh/cillynder/Awoocord@refs/heads/builds/SlashCommandsFixBeta.zip","buildCrc32":"BCA561A9","changelog":"# 8.18.0\n* Don't use custom props anymore (core has them)\n\n# 7.16.2\n* Use new props\n\n# 7.16.1\n* Prompt restarts\n\n# 7.16.0\n* Initial port >w< thanks @jedenastka","minimumDiscordVersion":126021,"minimumAliucordVersion":"2.6.0","minimumKotlinVersion":"1.5.21","minimumApiLevel":21},"Clump":{"version":"1.0.3","build":"https://cdn.jsdelivr.net/gh/cillynder/Awoocord@refs/heads/builds/Clump.zip","buildCrc32":"7D59ED3F","changelog":"# 1.0.3\n* Clump more than 6 messages together\n\n# 1.0.2\n* Fix (inverted) webhook clumping\n\n# 1.0.1\n* Hide blank space w.r.t attachments and embeds\n\n# 1.0.0\n* Initial release >w<","minimumDiscordVersion":126021,"minimumAliucordVersion":"2.6.0","minimumKotlinVersion":"1.5.21","minimumApiLevel":21},"Scout":{"version":"1.4.0","build":"https://cdn.jsdelivr.net/gh/cillynder/Awoocord@refs/heads/builds/Scout.zip","buildCrc32":"21C04736","changelog":"!!! Minimum Aliucord version requirement {fixed}\n======================\n* Scout now requires Aliucord 2.4.0, please update before reporting issues.\n\nChangelog {added marginTop}\n======================\n# 1.4.0 - Scout is searching for clues about the elusive MvM update\n* Added the authorType filter option to search by user, bot, or webhook\n* Moved sort filter to the top of the new ones\n* Fixes a Discord bug where typing \"mentions\" would also suggest \"has\"\n* Some people said the options were getting bloated, so they're all hidden behind a \"Show all\" button now. They'll still show up in auto suggestions.\n\n# 1.3.0\n* Removes empty discriminator when searching with users\n\n# 1.2.2\n* Fix possible rare crash related to thread searching\n\n# 1.2.1\n* Fixes off-looking thread icon\nOnly Discord will name an icon \"thread_white_24dp\", and it's neither white nor 24dp. Seriously, what were they thinking?\n\n# 1.2.0 - Scout is in:to knitting\n* Adds support for searching threads; simply use in:\n\n# 1.1.3\n* Patch to fix the biggggg top padding in results\n\n# 1.1.2\n* Fix month being one month behind after using the date picker\n\n# 1.1.1\n* Use proper icons for search filter suggestions\n\n# 1.1.0 - Look out, Scout has:updates\n* Add \"has:forward\" and \"has:poll\" filters\n* Add \"exclude:\" filter. It is the opposite of \"has:\" and filters out matching elements\n\n# 1.0.1\n* Fix not being able to search more than one page with sort:old\n\n# 1.0.0\n* Initial release >w<","minimumDiscordVersion":126021,"minimumAliucordVersion":"2.6.0","minimumKotlinVersion":"1.5.21","minimumApiLevel":21}} \ No newline at end of file