package cutthecord.commands; import androidx.annotation.Nullable; import com.discord.models.domain.ModelUser; import com.discord.stores.StoreMessages; import com.discord.stores.StoreStream; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import cutthecord.commands.commands.CmdBold; import cutthecord.commands.commands.CmdClap; import cutthecord.commands.commands.CmdCtc; import cutthecord.commands.commands.CmdFlip; import cutthecord.commands.commands.CmdFullWidth; import cutthecord.commands.commands.CmdGordon; import cutthecord.commands.commands.CmdLenny; import cutthecord.commands.commands.CmdLower; import cutthecord.commands.commands.CmdMe; import cutthecord.commands.commands.CmdMorse; import cutthecord.commands.commands.CmdOwo; import cutthecord.commands.commands.CmdSlap; import cutthecord.commands.commands.CmdSmall; import cutthecord.commands.commands.CmdSmaller; import cutthecord.commands.commands.CmdSpoiler; import cutthecord.commands.commands.CmdSpoilerImg; import cutthecord.commands.commands.CmdStrikethrough; import cutthecord.commands.commands.CmdUpper; import lanchon.dexpatcher.annotation.DexAdd; // Helper class for additional commands (slashcommands) @DexAdd public class CommandHandler { @DexAdd private static final HashMap commands = registerCommands(); @DexAdd public abstract static class Command { @DexAdd private final HashMap subcommands = new HashMap<>(); @DexAdd public void registerSubCommand(String subCmdName, Command command) { subcommands.put(subCmdName, command); } @DexAdd public Set getSubCommandNames() { return subcommands.keySet(); } @DexAdd public String process(String args) { // Check for possible subcommands if (!subcommands.isEmpty()) { final String[] split = args.split(Pattern.quote(" ")); if (split.length > 0) { final String possibleSubCmd = split[0].toLowerCase(); for (Map.Entry subCommand : subcommands.entrySet()) { if (subCommand.getKey().equals(possibleSubCmd)) { // Account for simple commands with no args String newArgs = ""; int i = args.indexOf(" "); if (i == -1) { newArgs = ""; } else { // Has args int newSubPos = i + 1; newArgs = args.substring(newSubPos); } return subCommand.getValue().process(newArgs); } } } } // Handle command return handleCommand(args); } @DexAdd public abstract String handleCommand(String msg); @DexAdd @Nullable public abstract String getPopupInfo(); } // Like a normal command, but the output is only viewable to the sender @DexAdd public abstract static class PrivateCommand extends Command { } @DexAdd public static HashMap registerCommands() { HashMap cmds = new HashMap<>(); cmds.put("upper", new CmdUpper()); cmds.put("lower", new CmdLower()); cmds.put("bold", new CmdBold()); cmds.put("clap", new CmdClap()); cmds.put("flip", new CmdFlip()); cmds.put("slap", new CmdSlap()); cmds.put("fw", new CmdFullWidth()); cmds.put("gordon", new CmdGordon()); cmds.put("me", new CmdMe()); cmds.put("lenny", new CmdLenny()); cmds.put("morse", new CmdMorse()); cmds.put("owo", new CmdOwo()); cmds.put("spoiler", new CmdSpoiler()); cmds.put("spoilerimg", new CmdSpoilerImg()); cmds.put("small", new CmdSmall()); cmds.put("smaller", new CmdSmaller()); cmds.put("st", new CmdStrikethrough()); // TODO can we add an CTC user that responds for these? Command ctcCommand = new CmdCtc(); ctcCommand.registerSubCommand("channelleak", new CmdCtc.CmdCtcChannelLeak()); ctcCommand.registerSubCommand("showtyping", new CmdCtc.CmdCtcShowTyping()); ctcCommand.registerSubCommand("token", new CmdCtc.CmdCtcToken()); ctcCommand.registerSubCommand("account", new CmdCtc.CmdCtcAccount()); ctcCommand.registerSubCommand("addaccount", new CmdCtc.CmdCtcAddAccount()); ctcCommand.registerSubCommand("nodelete", new CmdCtc.CmdCtcNoDelete()); ctcCommand.registerSubCommand("gifautoplay", new CmdCtc.CmdCtcGifAutoPlay()); cmds.put("ctc", ctcCommand); return cmds; } @DexAdd public static HashMap getPopupInfo() { HashMap infoPopups = new HashMap<>(); // Static init order makes things a pain so init in here for (Map.Entry command : commands.entrySet()) { String name = command.getKey(); Command cmd = command.getValue(); infoPopups.put(name, cmd.getPopupInfo()); registerPopupInfoChildren(infoPopups, name, cmd); } return infoPopups; } @DexAdd private static void registerPopupInfoChildren(HashMap infoPopups, String parent, Command parentCmd) { for (Map.Entry subCommand : parentCmd.subcommands.entrySet()) { String name = parent+" "+subCommand.getKey(); infoPopups.put(name, subCommand.getValue().getPopupInfo()); registerPopupInfoChildren(infoPopups, name, subCommand.getValue()); } } @DexAdd public static String slashCommands(StoreMessages storeMessages, long channelId, String str) { String msg = str.trim(); // TODO check for edge cases like /meh parsing to /me // TODO don't allow invalid commaands to end up in chat // Trim off "/" msg = msg.substring(1); for (Map.Entry commandEntry : commands.entrySet()) { if (msg.startsWith(commandEntry.getKey())) { String newArgs = msg; // Account for simple commands with no args int i = msg.indexOf(" "); if (i == -1) { newArgs = ""; } else { // Has args int newSubPos = i + 1; newArgs = msg.substring(newSubPos); } String output = commandEntry.getValue().process(newArgs); if (commandEntry.getValue() instanceof PrivateCommand) { // Show message from ctcbot storeMessages.sendCTCBotMessageToChannel(channelId, output); // This is naughty but discord handles it sanely return null; } return output; } } return str.trim(); } @DexAdd public static String interceptEditMessage(StoreMessages storeMessages, long channelId, String str) { return str.startsWith("/") ? slashCommands(storeMessages, channelId, str) : str; } @DexAdd public static String interceptSendMessage(StoreMessages storeMessages, long channelId, String str) { StoreStream.getUserSettings().setImageSpoiler(false); return str.startsWith("/") ? slashCommands(storeMessages, channelId, str) : str; } }