diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..ed7025e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,39 @@
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+max_line_length = 120
+tab_width = 4
+ij_continuation_indent_size = 4
+ij_any_line_comment_at_first_column = false
+ij_any_line_comment_add_space = true
+ij_smart_tabs = false
+ij_wrap_on_typing = false
+
+[*.{kt,kts}]
+ij_kotlin_allow_trailing_comma = false
+ij_kotlin_allow_trailing_comma_on_call_site = false
+ktlint_code_style = ktlint_official
+ktlint_experimental = enabled
+ktlint_standard_no-wildcard-imports = disabled
+ktlint_standard_string-template-indent = disabled
+ktlint_standard_multiline-expression-wrapping = disabled
+ktlint_standard_function-expression-body = disabled
+ktlint_function_signature_body_expression_wrapping = default
+ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = 6
+ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 3
+ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 6
+
+[*.xml]
+ij_xml_continuation_indent_size = 4
+
+[*.yml]
+indent_size = 2
+
+[*.properties]
+ij_properties_align_group_field_declarations = false
+ij_properties_keep_blank_lines = true
+ij_properties_key_value_delimiter = equals
+ij_properties_spaces_around_key_value_delimiter = false
diff --git a/.github/workflows/build.yml b/.github/workflows/deploy.yml
similarity index 55%
rename from .github/workflows/build.yml
rename to .github/workflows/deploy.yml
index c45bb3f..6aa37c4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/deploy.yml
@@ -1,53 +1,49 @@
-name: "Build"
+name: "Deploy"
-concurrency:
- group: "build"
+concurrency:
+ group: "deploy"
cancel-in-progress: true
on:
push:
branches:
- main
- paths-ignore:
- - '*.md'
jobs:
- build:
+ deploy:
+ name: Build & Deploy Plugins
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
path: "src"
- name: Checkout builds
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
ref: "builds"
path: "builds"
- - name: Checkout Aliucord
- uses: actions/checkout@master
+ - name: Setup JDK 21
+ uses: actions/setup-java@v5
with:
- repository: "Aliucord/Aliucord"
- path: "repo"
-
- - name: Setup JDK 11
- uses: actions/setup-java@v4
- with:
- java-version: 11
+ java-version: 21
distribution: temurin
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- - name: Build Plugins
+ - name: Build
run: |
cd $GITHUB_WORKSPACE/src
- chmod +x gradlew
- ./gradlew make generateUpdaterJson
- cp **/build/*.zip $GITHUB_WORKSPACE/builds
- cp build/updater.json $GITHUB_WORKSPACE/builds
+ chmod +x ./gradlew
+ ./gradlew make :generateUpdaterJson
+
+ rm $GITHUB_WORKSPACE/builds/*.zip
+ cp $GITHUB_WORKSPACE/src/**/build/outputs/*.zip $GITHUB_WORKSPACE/builds/
+ cp $GITHUB_WORKSPACE/src/build/outputs/updater.json $GITHUB_WORKSPACE/builds/
- name: Push builds
run: |
diff --git a/.gitignore b/.gitignore
index 1d3bf9e..bef914f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,16 @@
-*.iml
-.gradle
-/local.properties
-/.idea
-.DS_Store
-/build
-**/build
-/captures
-.externalNativeBuild
-.cxx
-local.properties
+# Build
+/local.properties
+/.kotlin/
+/.gradle/
+**/build/
+**/.cxx/
+**/.externalNativeBuild/
+
+# Editor
+/.vscode/
+/.idea/
+/*.iml
+
+# System
+.DS_Store
+Thumbs.db
diff --git a/ExamplePlugins/java/MyFirstCommand/build.gradle.kts b/ExamplePlugins/java/MyFirstCommand/build.gradle.kts
deleted file mode 100644
index 355488d..0000000
--- a/ExamplePlugins/java/MyFirstCommand/build.gradle.kts
+++ /dev/null
@@ -1,19 +0,0 @@
-version = "1.0.0" // Plugin version. Increment this to trigger the updater
-description = "My first commands!" // Plugin description that will be shown to user
-
-aliucord {
- // Changelog of your plugin
- changelog.set("""
- Some changelog
- """.trimIndent())
- // Image or Gif that will be shown at the top of your changelog page
- // changelogMedia.set("https://cool.png")
-
- // Add additional authors to this plugin
- // author("Name", 0)
- // author("Name", 0)
-
- // Excludes this plugin from the updater, meaning it won't show up for users.
- // Set this if the plugin is unfinished
- excludeFromUpdaterJson.set(true)
-}
diff --git a/ExamplePlugins/java/MyFirstCommand/src/main/AndroidManifest.xml b/ExamplePlugins/java/MyFirstCommand/src/main/AndroidManifest.xml
deleted file mode 100644
index a45f28d..0000000
--- a/ExamplePlugins/java/MyFirstCommand/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/ExamplePlugins/java/MyFirstCommand/src/main/java/com/github/yournamehere/MyFirstCommand.java b/ExamplePlugins/java/MyFirstCommand/src/main/java/com/github/yournamehere/MyFirstCommand.java
deleted file mode 100644
index 0b6c233..0000000
--- a/ExamplePlugins/java/MyFirstCommand/src/main/java/com/github/yournamehere/MyFirstCommand.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.github.yournamehere;
-
-import android.content.Context;
-import com.aliucord.Utils;
-import com.aliucord.annotations.AliucordPlugin;
-import com.aliucord.api.CommandsAPI;
-import com.aliucord.entities.Plugin;
-import com.discord.api.commands.ApplicationCommandType;
-
-import java.util.Arrays;
-
-// Aliucord Plugin annotation. Must be present on the main class of your plugin
-@AliucordPlugin(requiresRestart = false /* Whether your plugin requires a restart after being installed/updated */)
-// Plugin class. Must extend Plugin and override start and stop
-// Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/1_introduction.md#basic-plugin-structure
-public class MyFirstCommand extends Plugin {
- @Override
- public void start(Context context) {
- // Register a command with the name hello and description "My first command!" and no arguments.
- // Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/2_commands.md
- commands.registerCommand("hello", "My first command!", ctx -> {
- // Just return a command result with hello world as the content
- return new CommandsAPI.CommandResult(
- "Hello World!",
- null, // List of embeds
- false // Whether to send visible for everyone
- );
- });
-
- // A bit more advanced command with arguments
- commands.registerCommand(
- "hellowitharguments",
- "Hello World but with arguments!",
- Arrays.asList(
- Utils.createCommandOption(ApplicationCommandType.STRING, "name", "Person to say hello to"),
- Utils.createCommandOption(ApplicationCommandType.USER, "user", "User to say hello to")
- ),
- ctx -> {
- // Check if a user argument was passed
- if (ctx.containsArg("user")) {
- var user = ctx.getRequiredUser("user");
- return new CommandsAPI.CommandResult("Hello " + user.getUsername() + "!");
- } else {
- // Returns either the argument value if present, or the defaultValue ("World" in this case)
- var name = ctx.getStringOrDefault("name", "World");
- return new CommandsAPI.CommandResult("Hello " + name + "!");
- }
- }
- );
- }
-
- @Override
- public void stop(Context context) {
- // Unregister all commands
- commands.unregisterAll();
- }
-}
diff --git a/ExamplePlugins/java/MyFirstPatch/build.gradle.kts b/ExamplePlugins/java/MyFirstPatch/build.gradle.kts
deleted file mode 100644
index 1ebb20c..0000000
--- a/ExamplePlugins/java/MyFirstPatch/build.gradle.kts
+++ /dev/null
@@ -1,19 +0,0 @@
-version = "1.0.0" // Plugin version. Increment this to trigger the updater
-description = "My first patch!" // Plugin description that will be shown to user
-
-aliucord {
- // Changelog of your plugin
- changelog.set("""
- Some changelog
- """.trimIndent())
- // Image or Gif that will be shown at the top of your changelog page
- // changelogMedia.set("https://cool.png")
-
- // Add additional authors to this plugin
- // author("Name", 0)
- // author("Name", 0)
-
- // Excludes this plugin from the updater, meaning it won't show up for users.
- // Set this if the plugin is unfinished
- excludeFromUpdaterJson.set(true)
-}
diff --git a/ExamplePlugins/java/MyFirstPatch/src/main/AndroidManifest.xml b/ExamplePlugins/java/MyFirstPatch/src/main/AndroidManifest.xml
deleted file mode 100644
index a45f28d..0000000
--- a/ExamplePlugins/java/MyFirstPatch/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/ExamplePlugins/java/MyFirstPatch/src/main/java/com/github/yournamehere/MyFirstPatch.java b/ExamplePlugins/java/MyFirstPatch/src/main/java/com/github/yournamehere/MyFirstPatch.java
deleted file mode 100644
index c7c614f..0000000
--- a/ExamplePlugins/java/MyFirstPatch/src/main/java/com/github/yournamehere/MyFirstPatch.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.github.yournamehere;
-
-import android.content.Context;
-import com.aliucord.CollectionUtils;
-import com.aliucord.annotations.AliucordPlugin;
-import com.aliucord.entities.MessageEmbedBuilder;
-import com.aliucord.entities.Plugin;
-import com.aliucord.patcher.*;
-import com.aliucord.wrappers.embeds.MessageEmbedWrapper;
-import com.discord.models.user.CoreUser;
-import com.discord.stores.StoreUserTyping;
-import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage;
-import com.discord.widgets.chat.list.entries.ChatListEntry;
-import com.discord.widgets.chat.list.entries.MessageEntry;
-
-// Aliucord Plugin annotation. Must be present on the main class of your plugin
-@AliucordPlugin(requiresRestart = false /* Whether your plugin requires a restart after being installed/updated */)
-// Plugin class. Must extend Plugin and override start and stop
-// Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/1_introduction.md#basic-plugin-structure
-public class MyFirstPatch extends Plugin {
- @Override
- public void start(Context context) throws Throwable {
- // Patch that adds an embed with message statistics to each message
- // Patched method is WidgetChatListAdapterItemMessage.onConfigure(int type, ChatListEntry entry)
- patcher.patch(
- // see https://docs.oracle.com/javase/tutorial/reflect/class/classNew.html
- WidgetChatListAdapterItemMessage.class.getDeclaredMethod("onConfigure", int.class, ChatListEntry.class),
- new Hook(param -> { // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
- // Obtain the second argument passed to the method, so the ChatListEntry
- // Because this is a Message item, it will always be a MessageEntry, so cast it to that
- var entry = (MessageEntry) param.args[1];
-
- // You need to be careful when messing with messages, because they may be loading
- // (user sent a message, and it is currently sending)
- if (entry.getMessage().isLoading()) return;
-
- // Now add an embed with the statistics
-
- // This method may be called multiple times per message, e.g. if it is edited,
- // so first remove existing embeds
- CollectionUtils.removeIf(entry.getMessage().getEmbeds(), e -> {
- // MessageEmbed is an obfuscated class. However, Aliucord provides wrappers for commonly used
- // obfuscated classes, the MessageEmbedWrapper in this case.
- return "Message Statistics".equals(MessageEmbedWrapper.getTitle(e));
- });
-
- // Creating embeds is a pain, so Aliucord provides a convenient builder
- var embed = new MessageEmbedBuilder().
- setTitle("Message Statistics")
- .addField("Length", entry.getMessage().getContent() != null ? Integer.toString(entry.getMessage().getContent().length()) : "0", false)
- .addField("ID", Long.toString(entry.getMessage().getId()), false).build();
-
- entry.getMessage().getEmbeds().add(embed);
- })
- );
-
- // Patch that renames Juby to JoobJoob
- patcher.patch(
- CoreUser.class.getDeclaredMethod("getUsername"),
- new PreHook(param -> { // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
- if (((CoreUser) param.thisObject).getId() == 925141667688878090L) {
- // setResult() in before patches skips original method invocation
- param.setResult("JoobJoob");
- }
- })
- );
-
- // Patch that hides your typing status by replacing the method and simply doing nothing
- patcher.patch(
- StoreUserTyping.class.getDeclaredMethod("setUserTyping", long.class),
- InsteadHook.DO_NOTHING
- );
- }
-
- @Override
- public void stop(Context context) {
- // Remove all patches
- patcher.unpatchAll();
- }
-}
diff --git a/ExamplePlugins/kotlin/MyFirstCommand/build.gradle.kts b/ExamplePlugins/kotlin/MyFirstCommand/build.gradle.kts
deleted file mode 100644
index 355488d..0000000
--- a/ExamplePlugins/kotlin/MyFirstCommand/build.gradle.kts
+++ /dev/null
@@ -1,19 +0,0 @@
-version = "1.0.0" // Plugin version. Increment this to trigger the updater
-description = "My first commands!" // Plugin description that will be shown to user
-
-aliucord {
- // Changelog of your plugin
- changelog.set("""
- Some changelog
- """.trimIndent())
- // Image or Gif that will be shown at the top of your changelog page
- // changelogMedia.set("https://cool.png")
-
- // Add additional authors to this plugin
- // author("Name", 0)
- // author("Name", 0)
-
- // Excludes this plugin from the updater, meaning it won't show up for users.
- // Set this if the plugin is unfinished
- excludeFromUpdaterJson.set(true)
-}
diff --git a/ExamplePlugins/kotlin/MyFirstCommand/src/main/AndroidManifest.xml b/ExamplePlugins/kotlin/MyFirstCommand/src/main/AndroidManifest.xml
deleted file mode 100644
index a45f28d..0000000
--- a/ExamplePlugins/kotlin/MyFirstCommand/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/ExamplePlugins/kotlin/MyFirstCommand/src/main/kotlin/com/github/yournamehere/MyFirstCommand.kt b/ExamplePlugins/kotlin/MyFirstCommand/src/main/kotlin/com/github/yournamehere/MyFirstCommand.kt
deleted file mode 100644
index 59577a4..0000000
--- a/ExamplePlugins/kotlin/MyFirstCommand/src/main/kotlin/com/github/yournamehere/MyFirstCommand.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.github.yournamehere
-
-import android.content.Context
-import com.aliucord.Utils
-import com.aliucord.annotations.AliucordPlugin
-import com.aliucord.api.CommandsAPI
-import com.aliucord.entities.Plugin
-import com.discord.api.commands.ApplicationCommandType
-
-// Aliucord Plugin annotation. Must be present on the main class of your plugin
-@AliucordPlugin(requiresRestart = false /* Whether your plugin requires a restart after being installed/updated */)
-// Plugin class. Must extend Plugin and override start and stop
-// Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/1_introduction.md#basic-plugin-structure
-class MyFirstCommand : Plugin() {
- override fun start(context: Context) {
- // Register a command with the name hello and description "My first command!" and no arguments.
- // Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/2_commands.md
- commands.registerCommand("hello", "My first command!") {
- // Just return a command result with hello world as the content
- CommandsAPI.CommandResult(
- "Hello World!",
- null, // List of embeds
- false // Whether to send visible for everyone
- )
- }
-
- // A bit more advanced command with arguments
- commands.registerCommand("hellowitharguments", "Hello World but with arguments!", listOf(
- Utils.createCommandOption(ApplicationCommandType.STRING, "name", "Person to say hello to"),
- Utils.createCommandOption(ApplicationCommandType.USER, "user", "User to say hello to")
- )) { ctx ->
- // Check if a user argument was passed
- if (ctx.containsArg("user")) {
- val user = ctx.getRequiredUser("user")
- CommandsAPI.CommandResult("Hello ${user.username}!")
- } else {
- // Returns either the argument value if present, or the defaultValue ("World" in this case)
- val name = ctx.getStringOrDefault("name", "World")
- CommandsAPI.CommandResult("Hello $name!")
- }
- }
- }
-
- override fun stop(context: Context) {
- // Unregister our commands
- commands.unregisterAll()
- }
-}
diff --git a/ExamplePlugins/kotlin/MyFirstPatch/build.gradle.kts b/ExamplePlugins/kotlin/MyFirstPatch/build.gradle.kts
deleted file mode 100644
index 1ebb20c..0000000
--- a/ExamplePlugins/kotlin/MyFirstPatch/build.gradle.kts
+++ /dev/null
@@ -1,19 +0,0 @@
-version = "1.0.0" // Plugin version. Increment this to trigger the updater
-description = "My first patch!" // Plugin description that will be shown to user
-
-aliucord {
- // Changelog of your plugin
- changelog.set("""
- Some changelog
- """.trimIndent())
- // Image or Gif that will be shown at the top of your changelog page
- // changelogMedia.set("https://cool.png")
-
- // Add additional authors to this plugin
- // author("Name", 0)
- // author("Name", 0)
-
- // Excludes this plugin from the updater, meaning it won't show up for users.
- // Set this if the plugin is unfinished
- excludeFromUpdaterJson.set(true)
-}
diff --git a/ExamplePlugins/kotlin/MyFirstPatch/src/main/AndroidManifest.xml b/ExamplePlugins/kotlin/MyFirstPatch/src/main/AndroidManifest.xml
deleted file mode 100644
index a45f28d..0000000
--- a/ExamplePlugins/kotlin/MyFirstPatch/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/README.md b/README.md
index e00b299..9cb223a 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,41 @@
-# `Aliucord Plugin Repo Template`
+# Aliucord Plugin Repo Template
+
+---
Template for an [Aliucord](https://github.com/Aliucord) plugin repo
-⚠️ Make sure you check "Include all branches" when using this template
+⚠️ Make sure you check "Include all branches" when using this template \
+⚠️ Consider getting familiar with Java and/or Kotlin and Gradle before starting
+
+## Pre-requisites
+
+- Java JDK 17 or newer (JDK 21 is recommended). Example distributions:
+ - [Adoptium](https://adoptium.net)
+ - [Temurin](https://adoptium.net/temurin)
+ - [Azul](https://www.azul.com/downloads/?package=jdk#zulu)
+- [Android Studio](https://developer.android.com/studio)
-
## Getting started with writing your first plugin
-This template includes 2 example plugins demonstrating commands and patches which you can find in the ExamplePlugins folder.
+This template includes an example plugin written in Kotlin and Java, demonstrating how to implement
+a command and patches.
+
+To set up your development environment:
+
+1. Clone this repository to your local machine.
+2. Open the cloned repository in Android Studio.
+3. Open the gradle build script at [plugin/build.gradle.kts](plugins/build.gradle.kts), read the
+ comments and replace all the placeholders marked with `// TODO`
+4. Familiarize yourself with the project structure. Most files are commented
+
+To build and deploy your plugin:
-1. Open the root build.gradle.kts, read the comments and replace all the placeholders
-2. Familiarize yourself with the project structure. Most files are commented
-3. Build or deploy your first plugin using:
- - Windows: `.\gradlew.bat MyFirstCommand:make` or `.\gradlew.bat MyFirstCommand:deployWithAdb`
- - Linux & Mac: `./gradlew MyFirstCommand:make` or `./gradlew MyFirstCommand:deployWithAdb`
+- On Linux & Mac, run `./gradlew MyFirstKotlinPlugin:make` to build the plugin.
+ Use `./gradlew MyFirstKotlinPlugin:deployWithAdb` to deploy directly to a connected device.
+- On Windows, use `.\gradlew.bat MyFirstKotlinPlugin:make`
+ and `.\gradlew.bat MyFirstKotlinPlugin:deployWithAdb` for building and deploying, respectively.
## License
-Everything in this repo is released into the public domain. You may use it however you want with no conditions whatsoever
+Everything in this repo is released into the public domain. You may use it however you want with no
+conditions whatsoever
diff --git a/build.gradle.kts b/build.gradle.kts
index 0a34669..2d1be22 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,89 +1,6 @@
-import com.aliucord.gradle.AliucordExtension
-import com.android.build.gradle.BaseExtension
-
-buildscript {
- repositories {
- google()
- mavenCentral()
- // Aliucords Maven repo which contains our tools and dependencies
- maven("https://maven.aliucord.com/snapshots")
- // Shitpack which still contains some Aliucord dependencies for now. TODO: Remove
- maven("https://jitpack.io")
- }
-
- dependencies {
- classpath("com.android.tools.build:gradle:7.0.4")
- // Aliucord gradle plugin which makes everything work and builds plugins
- classpath("com.aliucord:gradle:main-SNAPSHOT")
- // Kotlin support. Remove if you want to use Java
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21")
- }
-}
-
-allprojects {
- repositories {
- google()
- mavenCentral()
- maven("https://maven.aliucord.com/snapshots")
- }
-}
-
-fun Project.aliucord(configuration: AliucordExtension.() -> Unit) = extensions.getByName("aliucord").configuration()
-
-fun Project.android(configuration: BaseExtension.() -> Unit) = extensions.getByName("android").configuration()
-
-subprojects {
- apply(plugin = "com.android.library")
- apply(plugin = "com.aliucord.gradle")
- // Remove if using Java
- apply(plugin = "kotlin-android")
-
- // Fill out with your info
- aliucord {
- author("DISCORD USERNAME", 123456789L)
- updateUrl.set("https://raw.githubusercontent.com/USERNAME/REPONAME/builds/updater.json")
- buildUrl.set("https://raw.githubusercontent.com/USERNAME/REPONAME/builds/%s.zip")
- }
-
- android {
- compileSdkVersion(31)
-
- defaultConfig {
- minSdk = 24
- targetSdk = 31
- }
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
-
- tasks.withType {
- kotlinOptions {
- jvmTarget = "11" // Required
- // Disables some unnecessary features
- freeCompilerArgs = freeCompilerArgs +
- "-Xno-call-assertions" +
- "-Xno-param-assertions" +
- "-Xno-receiver-assertions"
- }
- }
- }
-
- dependencies {
- val discord by configurations
- val implementation by configurations
-
- // Stubs for all Discord classes
- discord("com.discord:discord:aliucord-SNAPSHOT")
- implementation("com.aliucord:Aliucord:main-SNAPSHOT")
-
- implementation("androidx.appcompat:appcompat:1.4.0")
- implementation("com.google.android.material:material:1.4.0")
- implementation("androidx.constraintlayout:constraintlayout:2.1.2")
- }
-}
-
-task("clean") {
- delete(rootProject.buildDir)
+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
}
diff --git a/gradle.properties b/gradle.properties
index 01b80d7..c1337ce 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,19 +1,11 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
+# Gradle
+org.gradle.configuration-cache=true
+org.gradle.configureondemand=true
+org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app"s APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
+
+# Kotlin
+kotlin.code.style=official
+
+# Android
android.useAndroidX=true
-# Automatically convert third-party libraries to use AndroidX
-android.enableJetifier=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..009698d
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,21 @@
+[versions]
+aliucord = "2.6.0"
+aliucord-gradle = "2.2.0"
+android = "8.13.1"
+discord = "126021"
+kotlin = "2.2.21"
+#noinspection GradleDependency
+kotlin-stdlib = "1.5.21"
+ktlint = "1.8.0"
+ktlint-plugin = "14.0.1"
+
+[libraries]
+aliucord = { module = "com.aliucord:Aliucord", version.ref = "aliucord" }
+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" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index f6b961f..8bdaf60 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index da0e964..2a84e18 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Wed Jun 09 16:18:24 EST 2021
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
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
diff --git a/gradlew b/gradlew
index 005bcde..ef07e01 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
#!/bin/sh
#
-# Copyright © 2015-2021 the original authors.
+# Copyright © 2015 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.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -55,7 +57,7 @@
# 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
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
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"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -133,22 +133,29 @@ 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.
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then
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.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index 6a68175..5eed7ee 100755
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,89 +1,94 @@
-@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
+@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
+@rem SPDX-License-Identifier: Apache-2.0
+@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=.
+@rem This is normally unused
+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="-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% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/plugins/MyFirstJavaPlugin/build.gradle.kts b/plugins/MyFirstJavaPlugin/build.gradle.kts
new file mode 100644
index 0000000..fb2d426
--- /dev/null
+++ b/plugins/MyFirstJavaPlugin/build.gradle.kts
@@ -0,0 +1,26 @@
+version = "1.0.0" // Plugin version. Increment this to trigger an update
+description = "My first Java plugin!" // Plugin description that will be shown to user
+
+aliucord {
+ // Changelog of your plugin
+ changelog.set(
+ """
+ # 1.0.0
+ * Initial plugin release!
+ """.trimIndent()
+ )
+ // Image or Gif that will be shown at the top of your changelog page
+ // changelogMedia.set("https://cool.png")
+
+ // Add additional authors to this plugin
+ // author("Name", 0L, hyperlink = true)
+ // author("Name", 0L, hyperlink = true)
+
+ // Excludes this plugin from publishing and global plugin repositories.
+ // Set this to false if the plugin is unfinished
+ deploy.set(false)
+
+ // Builds and deploys this plugin but excludes it from global plugin repositories.
+ // Set this if the plugin has reached EOL but a last update should still occur.
+ // deployHidden.set(true)
+}
diff --git a/plugins/MyFirstJavaPlugin/src/main/java/com/github/yournamehere/MyFirstJavaPlugin.java b/plugins/MyFirstJavaPlugin/src/main/java/com/github/yournamehere/MyFirstJavaPlugin.java
new file mode 100644
index 0000000..ba161fe
--- /dev/null
+++ b/plugins/MyFirstJavaPlugin/src/main/java/com/github/yournamehere/MyFirstJavaPlugin.java
@@ -0,0 +1,125 @@
+package com.github.yournamehere;
+
+import android.content.Context;
+
+import com.aliucord.Utils;
+import com.aliucord.annotations.AliucordPlugin;
+import com.aliucord.api.CommandsAPI;
+import com.aliucord.entities.MessageEmbedBuilder;
+import com.aliucord.entities.Plugin;
+import com.aliucord.patcher.Hook;
+import com.aliucord.patcher.InsteadHook;
+import com.aliucord.patcher.PreHook;
+import com.aliucord.wrappers.embeds.MessageEmbedWrapper;
+import com.discord.api.commands.ApplicationCommandType;
+import com.discord.api.message.embed.MessageEmbed;
+import com.discord.models.user.CoreUser;
+import com.discord.stores.StoreUserTyping;
+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 java.util.Arrays;
+import java.util.Iterator;
+
+// Aliucord Plugin annotation. Must be present on the main class of your plugin
+// Plugin class. Must extend Plugin and override start and stop
+// Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/1_introduction.md#basic-plugin-structure
+@AliucordPlugin(
+ requiresRestart = false // Whether your plugin requires a restart after being installed/updated
+)
+@SuppressWarnings("unused")
+public class MyFirstJavaPlugin extends Plugin {
+ @Override
+ public void start(Context context) throws Throwable {
+ // Register a command with the name hello and description "My first command!" and no arguments.
+ // Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/2_commands.md
+ commands.registerCommand("hello", "My first command!", ctx -> {
+ // Just return a command result with hello world as the content
+ return new CommandsAPI.CommandResult(
+ "Hello World!",
+ null, // List of embeds
+ false // Whether to send visible for everyone
+ );
+ });
+
+ // A bit more advanced command with arguments
+ commands.registerCommand(
+ "hellowitharguments",
+ "Hello World but with arguments!",
+ Arrays.asList(
+ Utils.createCommandOption(ApplicationCommandType.STRING, "name", "Person to say hello to"),
+ Utils.createCommandOption(ApplicationCommandType.USER, "user", "User to say hello to")
+ ),
+ ctx -> {
+ String username;
+
+ // Check if a user argument was passed
+ if (ctx.containsArg("user")) {
+ username = ctx.getRequiredUser("user").getUsername();
+ } else {
+ // Returns either the argument value if present, or the defaultValue ("World" in this case)
+ username = ctx.getStringOrDefault("name", "World");
+ }
+
+ // Return the final result that will be displayed in chat as a response to the command
+ return new CommandsAPI.CommandResult("Hello " + username + "!");
+ }
+ );
+
+ // Patch that adds an embed with message statistics to each message
+ // Patched method is WidgetChatListAdapterItemMessage.onConfigure(int type, ChatListEntry entry)
+ patcher.patch(WidgetChatListAdapterItemMessage.class.getDeclaredMethod("onConfigure", int.class, ChatListEntry.class), new Hook(param -> {
+ // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
+ // Obtain the second argument passed to the method, so the ChatListEntry
+ // Because this is a Message item, it will always be a MessageEntry, so cast it to that
+ var entry = (MessageEntry) param.args[1];
+ var message = entry.getMessage();
+
+ // You need to be careful when messing with messages, because they may be loading
+ // (user sent a message, and it is currently sending)
+ if (message.isLoading()) return;
+
+ // Now add an embed with the statistics
+
+ // This method may be called multiple times per message, e.g. if it is edited,
+ // so first remove existing embeds.
+ Iterator embedIterator = message.getEmbeds().iterator();
+ while (embedIterator.hasNext()) {
+ MessageEmbedWrapper embed = new MessageEmbedWrapper(embedIterator.next());
+
+ if ("Message Statistics".equals(embed.getTitle()))
+ embedIterator.remove();
+ }
+
+ // Creating embeds is a pain, so Aliucord provides a convenient builder
+ var embed = new MessageEmbedBuilder()
+ .setTitle("Message Statistics")
+ .addField("Length", message.getContent() != null ? Integer.toString(message.getContent().length()) : "0", false)
+ .addField("ID", Long.toString(message.getId()), false).build();
+
+ message.getEmbeds().add(embed);
+ }));
+
+ // Patch that renames Juby to JoobJoob
+ patcher.patch(
+ CoreUser.class.getDeclaredMethod("getUsername"),
+ new PreHook(param -> { // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
+ if (((CoreUser) param.thisObject).getId() == 925141667688878090L) {
+ // setResult() in before patches skips original method invocation
+ param.setResult("JoobJoob");
+ }
+ })
+ );
+
+ // Patch that hides your typing status by replacing the method and simply doing nothing
+ // This patches the method StoreUserTyping.setUserTyping(long channelId)
+ patcher.patch(StoreUserTyping.class.getDeclaredMethod("setUserTyping", long.class), InsteadHook.DO_NOTHING);
+ }
+
+ @Override
+ public void stop(Context context) {
+ // Remove all patches
+ patcher.unpatchAll();
+ }
+}
diff --git a/plugins/MyFirstKotlinPlugin/build.gradle.kts b/plugins/MyFirstKotlinPlugin/build.gradle.kts
new file mode 100644
index 0000000..e127b97
--- /dev/null
+++ b/plugins/MyFirstKotlinPlugin/build.gradle.kts
@@ -0,0 +1,26 @@
+version = "1.0.0" // Plugin version. Increment this to trigger an update
+description = "My first Kotlin plugin!" // Plugin description that will be shown to user
+
+aliucord {
+ // Changelog of your plugin
+ changelog.set(
+ """
+ # 1.0.0
+ * Initial plugin release!
+ """.trimIndent()
+ )
+ // Image or Gif that will be shown at the top of your changelog page
+ // changelogMedia.set("https://cool.png")
+
+ // Add additional authors to this plugin
+ // author("Name", 0L, hyperlink = true)
+ // author("Name", 0L, hyperlink = true)
+
+ // Excludes this plugin from publishing and global plugin repositories.
+ // Set this to false if the plugin is unfinished
+ deploy.set(false)
+
+ // Builds and deploys this plugin but excludes it from global plugin repositories.
+ // Set this if the plugin has reached EOL but a last update should still occur.
+ // deployHidden.set(true)
+}
diff --git a/ExamplePlugins/kotlin/MyFirstPatch/src/main/kotlin/com/github/yournamehere/MyFirstPatch.kt b/plugins/MyFirstKotlinPlugin/src/main/kotlin/com/github/yournamehere/MyFirstKotlinPlugin.kt
similarity index 53%
rename from ExamplePlugins/kotlin/MyFirstPatch/src/main/kotlin/com/github/yournamehere/MyFirstPatch.kt
rename to plugins/MyFirstKotlinPlugin/src/main/kotlin/com/github/yournamehere/MyFirstKotlinPlugin.kt
index bab47b9..c54c3c7 100644
--- a/ExamplePlugins/kotlin/MyFirstPatch/src/main/kotlin/com/github/yournamehere/MyFirstPatch.kt
+++ b/plugins/MyFirstKotlinPlugin/src/main/kotlin/com/github/yournamehere/MyFirstKotlinPlugin.kt
@@ -1,11 +1,14 @@
package com.github.yournamehere
import android.content.Context
+import com.aliucord.Utils
import com.aliucord.annotations.AliucordPlugin
+import com.aliucord.api.CommandsAPI
import com.aliucord.entities.MessageEmbedBuilder
import com.aliucord.entities.Plugin
import com.aliucord.patcher.*
import com.aliucord.wrappers.embeds.MessageEmbedWrapper.Companion.title
+import com.discord.api.commands.ApplicationCommandType
import com.discord.models.user.CoreUser
import com.discord.stores.StoreUserTyping
import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage
@@ -13,33 +16,78 @@ import com.discord.widgets.chat.list.entries.ChatListEntry
import com.discord.widgets.chat.list.entries.MessageEntry
// Aliucord Plugin annotation. Must be present on the main class of your plugin
-@AliucordPlugin(requiresRestart = false /* Whether your plugin requires a restart after being installed/updated */)
// Plugin class. Must extend Plugin and override start and stop
// Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/1_introduction.md#basic-plugin-structure
-class MyFirstPatch : Plugin() {
+@AliucordPlugin(
+ requiresRestart = false // Whether your plugin requires a restart after being installed/updated
+)
+@Suppress("unused")
+class MyFirstKotlinPlugin : Plugin() {
override fun start(context: Context) {
+ // Register a command with the name hello and description "My first command!" and no arguments.
+ // Learn more: https://github.com/Aliucord/documentation/blob/main/plugin-dev/2_commands.md
+ commands.registerCommand("hello", "My first command!") {
+ // Just return a command result with hello world as the content
+ CommandsAPI.CommandResult(
+ "Hello World!",
+ null, // List of embeds
+ false // Whether to send visible for everyone
+ )
+ }
+
+ // A bit more advanced command with arguments
+ commands.registerCommand(
+ "hellowitharguments",
+ "Hello World but with arguments!",
+ listOf(
+ Utils.createCommandOption(
+ ApplicationCommandType.STRING,
+ "name",
+ "Person to say hello to"
+ ),
+ Utils.createCommandOption(
+ ApplicationCommandType.USER,
+ "user",
+ "User to say hello to"
+ )
+ )
+ ) { ctx ->
+ // Check if a user argument was passed
+ val username = if (ctx.containsArg("user")) {
+ ctx.getRequiredUser("user").username
+ } else {
+ // Returns either the argument value if present, or the defaultValue ("World" in this case)
+ ctx.getStringOrDefault("name", "World")
+ }
+
+ // Return the final result that will be displayed in chat as a response to the command
+ CommandsAPI.CommandResult("Hello $username!")
+ }
+
// Patch that adds an embed with message statistics to each message
// Patched method is WidgetChatListAdapterItemMessage.onConfigure(int type, ChatListEntry entry)
- patcher.after /* Class whose method to patch */(
+ patcher.after(
"onConfigure", // Method name
// Refer to https://kotlinlang.org/docs/reflection.html#class-references
// and https://docs.oracle.com/javase/tutorial/reflect/class/classNew.html
Int::class.java, // int type
ChatListEntry::class.java // ChatListEntry entry
- ) { param -> // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
+ ) { param ->
+ // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
// Obtain the second argument passed to the method, so the ChatListEntry
// Because this is a Message item, it will always be a MessageEntry, so cast it to that
val entry = param.args[1] as MessageEntry
+ val message = entry.message
// You need to be careful when messing with messages, because they may be loading
// (user sent a message, and it is currently sending)
- if (entry.message.isLoading) return@after
+ if (message.isLoading) return@after
// Now add an embed with the statistics
// This method may be called multiple times per message, e.g. if it is edited,
// so first remove existing embeds
- entry.message.embeds.removeIf {
+ message.embeds.removeAll {
// MessageEmbed.getTitle() is actually obfuscated, but Aliucord provides extensions for commonly used
// obfuscated Discord classes, so just import the MessageEmbed.title extension and boom goodbye obfuscation!
it.title == "Message Statistics"
@@ -48,15 +96,16 @@ class MyFirstPatch : Plugin() {
// Creating embeds is a pain, so Aliucord provides a convenient builder
MessageEmbedBuilder().run {
setTitle("Message Statistics")
- addField("Length", (entry.message.content?.length ?: 0).toString(), false)
- addField("ID", entry.message.id.toString(), false)
+ addField("Length", "${message.content?.length ?: 0}", false)
+ addField("ID", message.id.toString(), false)
- entry.message.embeds.add(build())
+ message.embeds.add(build())
}
}
// Patch that renames Juby to JoobJoob
- patcher.before("getUsername") { param -> // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
+ patcher.before("getUsername") { param ->
+ // see https://api.xposed.info/reference/de/robv/android/xposed/XC_MethodHook.MethodHookParam.html
// in before, after and instead patches, `this` refers to the instance of the class
// the patched method is on, so the CoreUser instance here
if (id == 925141667688878090) {
@@ -67,7 +116,8 @@ class MyFirstPatch : Plugin() {
// Patch that hides your typing status by replacing the method and simply doing nothing
patcher.instead(
- "setUserTyping", Long::class.java // long channelId
+ "setUserTyping",
+ Long::class.java // java.lang.Long channelId
) { null }
}
diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts
new file mode 100644
index 0000000..5fb3f00
--- /dev/null
+++ b/plugins/build.gradle.kts
@@ -0,0 +1,73 @@
+@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
+
+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 {
+ // TODO: Change to your package name
+ namespace = "com.github.yournamehere"
+ compileSdk = 36
+
+ defaultConfig {
+ minSdk = 21
+ }
+
+ buildFeatures {
+ aidl = false
+ buildConfig = true
+ renderScript = false
+ shaders = false
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
+ }
+ }
+
+ configure {
+ // TODO: Change to your name and user ID
+ author("yournamehere", 0L, hyperlink = true)
+
+ // TODO: Change to your repository
+ github("https://github.com/Aliucord/plugins-template")
+ }
+
+ 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.kotlin.stdlib)
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 54b32ab..362035f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,22 +1,32 @@
-rootProject.name = "AliucordPlugins"
+@file:Suppress("UnstableApiUsage")
-// This file sets what projects are included. Every time you add a new project, you must add it
-// to the includes below.
-
-// Plugins are included like this
-include(
- "MyFirstCommand",
- "MyFirstPatch"
-)
+pluginManagement {
+ repositories {
+ google()
+ gradlePluginPortal()
+ maven {
+ name = "aliucord"
+ url = uri("https://maven.aliucord.com/releases")
+ }
+ }
+}
-// This is required because plugins are in the ExamplePlugins/kotlin subdirectory.
-//
-// Assuming you put all your plugins into the project root, so on the same
-// level as this file, simply remove everything below.
-//
-// Otherwise, if you want a different structure, for example all plugins in a folder called "plugins",
-// then simply change the path
-rootProject.children.forEach {
- // Change kotlin to java if you'd rather use java
- it.projectDir = file("ExamplePlugins/kotlin/${it.name}")
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven {
+ name = "aliucord"
+ url = uri("https://maven.aliucord.com/releases")
+ }
+ }
}
+
+rootProject.name = "aliucord-plugins"
+include(":plugins")
+
+// Add each directory under ./plugins as a separate project
+rootDir.resolve("plugins")
+ .listFiles { file -> file.isDirectory && file.resolve("build.gradle.kts").exists() }!!
+ .forEach { include(":plugins:${it.name}") }