feat(canary/SlashCommandsFix): init @ jedenastka/Aliucord@47a1c8e43f

This commit is contained in:
LavaDesu 2025-07-16 02:06:10 +10:00
parent 50ba1acc23
commit a087010fbd
Signed by: cilly
GPG key ID: 6500251E087653C9
21 changed files with 955 additions and 1 deletions

View 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)
}
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="moe.lava.corenary.slashcommandsfix" />

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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) {}
}

View file

@ -1,6 +1,6 @@
rootProject.name = "Awoocord"
val canaryPlugins = arrayOf("ComponentsV2")
val canaryPlugins = arrayOf("ComponentsV2", "SlashCommandsFix")
include(
"AlignThreads",