feat(canary/SlashCommandsFix): init @ jedenastka/Aliucord@47a1c8e43f
This commit is contained in:
parent
50ba1acc23
commit
a087010fbd
21 changed files with 955 additions and 1 deletions
42
canary/SlashCommandsFix/build.gradle.kts
Normal file
42
canary/SlashCommandsFix/build.gradle.kts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
version = "7.16.0"
|
||||
description = "Beta backport of SlashCommandsFix"
|
||||
|
||||
aliucord {
|
||||
changelog.set("""
|
||||
# 7.16.0
|
||||
* Initial port >w< thanks @jedenastka
|
||||
""".trimIndent())
|
||||
|
||||
excludeFromUpdaterJson.set(false)
|
||||
}
|
||||
|
||||
//apply(plugin = "com.gradleup.shadow")
|
||||
apply(plugin = "com.github.johnrengelman.shadow") // remove when gradle 8
|
||||
|
||||
val shadowDir = File(buildDir, "intermediates/shadowed")
|
||||
|
||||
tasks.register<ShadowJar>("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<Sync>("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)
|
||||
}
|
||||
}
|
||||
2
canary/SlashCommandsFix/src/main/AndroidManifest.xml
Normal file
2
canary/SlashCommandsFix/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="moe.lava.corenary.slashcommandsfix" />
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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<User> botUser = Optional.ofNullable(this.botId).map(userId -> usersStore.getUsers().get(userId));
|
||||
return new Application(this.id, this.name, this.icon, permissions, botUser);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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<com.discord.api.commands.ApplicationCommandOption> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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<ApiApplication> applications;
|
||||
public List<ApiApplicationCommand> applicationCommands;
|
||||
|
||||
public ApiApplicationIndex() {
|
||||
this.applications = null;
|
||||
this.applicationCommands = null;
|
||||
}
|
||||
|
||||
public ApplicationIndex toModel() {
|
||||
var applications = new HashMap<Long, Application>();
|
||||
for (var application: this.applications) {
|
||||
applications.put(application.id, application.toModel());
|
||||
}
|
||||
var applicationCommands = new HashMap<Long, RemoteApplicationCommand>();
|
||||
for (var applicationCommand: this.applicationCommands) {
|
||||
applicationCommands.put(applicationCommand.id, applicationCommand.toModel());
|
||||
}
|
||||
|
||||
return new ApplicationIndex(applications, applicationCommands);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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<Long, Boolean> roles;
|
||||
public Map<Long, Boolean> channels;
|
||||
|
||||
public ApiPermissions() {
|
||||
this.user = null;
|
||||
this.roles = null;
|
||||
this.channels = null;
|
||||
}
|
||||
|
||||
public Permissions toModel(Optional<Long> defaultMemberPermissions) {
|
||||
return new Permissions(Optional.ofNullable(user), roles, channels, defaultMemberPermissions);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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<User> botUser) {
|
||||
super(id, name, icon, null, -1, botUser.map(user -> UserUtils.INSTANCE.synthesizeApiUser(user)).orElse(null), false);
|
||||
this.permissions_ = permissions;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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<Long, Application> applications;
|
||||
public Map<Long, RemoteApplicationCommand> applicationCommands;
|
||||
|
||||
public ApplicationIndex(Map<Long, Application> applications, Map<Long, RemoteApplicationCommand> applicationCommands) {
|
||||
this.applications = applications;
|
||||
this.applicationCommands = applicationCommands;
|
||||
}
|
||||
|
||||
public ApplicationIndex(List<ApplicationIndex> 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<Long, Integer>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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<Long, ApplicationIndex> guild;
|
||||
public Map<Long, ApplicationIndex> dm;
|
||||
public Optional<ApplicationIndex> user;
|
||||
|
||||
public ApplicationIndexCache() {
|
||||
this.guild = new HashMap<>();
|
||||
this.dm = new HashMap<>();
|
||||
this.user = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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<ApplicationIndex> getFromCache(ApplicationIndexCache cache);
|
||||
void insertIntoCache(ApplicationIndexCache cache, ApplicationIndex index);
|
||||
void removeFromCache(ApplicationIndexCache cache);
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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<ApplicationIndex> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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<ApplicationIndex> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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<ApplicationIndex> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
* 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.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.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<Long>) 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<String, Error.SkemaError>) skemaErrorSubErrorsField.get(error));
|
||||
var dataErrors = (List<Error.SkemaErrorItem>) 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> 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<Application>(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<ApplicationIndexSource> applicationIndexSourceFromContext(long guildId, StoreChannelsSelected storeChannelsSelected) {
|
||||
Optional<ApplicationIndexSource> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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<Boolean> user;
|
||||
public Map<Long, Boolean> roles;
|
||||
public Map<Long, Boolean> channels;
|
||||
public Optional<Long> defaultMemberPermissions;
|
||||
|
||||
public Permissions(Optional<Boolean> user, Map<Long, Boolean> roles, Map<Long, Boolean> channels, Optional<Long> 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<Long> 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<Long> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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<ApplicationCommandOption> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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
|
||||
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) {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue