001package co.aikar.commands;
002
003import co.aikar.commands.apachecommonslang.ApacheCommonsExceptionUtil;
004import net.dv8tion.jda.api.AccountType;
005import net.dv8tion.jda.api.JDA;
006import net.dv8tion.jda.api.entities.ChannelType;
007import net.dv8tion.jda.api.entities.Message;
008import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
009import org.jetbrains.annotations.NotNull;
010
011import java.util.Arrays;
012import java.util.Collection;
013import java.util.Collections;
014import java.util.HashMap;
015import java.util.List;
016import java.util.Locale;
017import java.util.Map;
018import java.util.logging.Level;
019import java.util.logging.Logger;
020
021public class JDACommandManager extends CommandManager<
022        MessageReceivedEvent,
023        JDACommandEvent,
024        String,
025        MessageFormatter<String>,
026        JDACommandExecutionContext,
027        JDAConditionContext
028        > {
029
030    private final JDA jda;
031    protected JDACommandCompletions completions;
032    protected JDACommandContexts contexts;
033    protected JDALocales locales;
034    protected Map<String, JDARootCommand> commands = new HashMap<>();
035    private Logger logger;
036    private CommandConfig defaultConfig;
037    private CommandConfigProvider configProvider;
038    private CommandPermissionResolver permissionResolver;
039    private long botOwner = 0L;
040
041    public JDACommandManager(JDA jda) {
042        this(jda, null);
043    }
044
045    public JDACommandManager(JDA jda, JDAOptions options) {
046        if (options == null) {
047            options = new JDAOptions();
048        }
049        this.jda = jda;
050        this.permissionResolver = options.permissionResolver;
051        jda.addEventListener(new JDAListener(this));
052        this.defaultConfig = options.defaultConfig == null ? new JDACommandConfig() : options.defaultConfig;
053        this.configProvider = options.configProvider;
054        this.defaultFormatter = new JDAMessageFormatter();
055        this.completions = new JDACommandCompletions(this);
056        this.logger = Logger.getLogger(this.getClass().getSimpleName());
057
058        getCommandConditions().addCondition("owneronly", context -> {
059            if (context.getIssuer().getEvent().getAuthor().getIdLong() != getBotOwnerId()) {
060                throw new ConditionFailedException("Only the bot owner can use this command."); // TODO: MessageKey
061            }
062        });
063
064        getCommandConditions().addCondition("guildonly", context -> {
065            if (context.getIssuer().getEvent().getChannelType() != ChannelType.TEXT) {
066                throw new ConditionFailedException("This command must be used in guild chat."); // TODO: MessageKey
067            }
068        });
069
070        getCommandConditions().addCondition("privateonly", context -> {
071            if (context.getIssuer().getEvent().getChannelType() != ChannelType.PRIVATE) {
072                throw new ConditionFailedException("This command must be used in private chat."); // TODO: MessageKey
073            }
074        });
075
076        getCommandConditions().addCondition("grouponly", context -> {
077            if (context.getIssuer().getEvent().getChannelType() != ChannelType.GROUP) {
078                throw new ConditionFailedException("This command must be used in group chat."); // TODO: MessageKey
079            }
080        });
081    }
082
083    public static JDAOptions options() {
084        return new JDAOptions();
085    }
086
087    void initializeBotOwner() {
088        if (botOwner == 0L) {
089            if (jda.getAccountType() == AccountType.BOT) {
090                botOwner = jda.retrieveApplicationInfo().complete().getOwner().getIdLong();
091            } else {
092                botOwner = jda.getSelfUser().getIdLong();
093            }
094        }
095    }
096
097    public long getBotOwnerId() {
098        // Just in case initialization on ReadyEvent fails.
099        initializeBotOwner();
100        return botOwner;
101    }
102
103    public JDA getJDA() {
104        return jda;
105    }
106
107    public Logger getLogger() {
108        return logger;
109    }
110
111    public void setLogger(Logger logger) {
112        this.logger = logger;
113    }
114
115    public CommandConfig getDefaultConfig() {
116        return defaultConfig;
117    }
118
119    public void setDefaultConfig(@NotNull CommandConfig defaultConfig) {
120        this.defaultConfig = defaultConfig;
121    }
122
123    public CommandConfigProvider getConfigProvider() {
124        return configProvider;
125    }
126
127    public void setConfigProvider(CommandConfigProvider configProvider) {
128        this.configProvider = configProvider;
129    }
130
131    public CommandPermissionResolver getPermissionResolver() {
132        return permissionResolver;
133    }
134
135    public void setPermissionResolver(CommandPermissionResolver permissionResolver) {
136        this.permissionResolver = permissionResolver;
137    }
138
139    @Override
140    public CommandContexts<?> getCommandContexts() {
141        if (this.contexts == null) {
142            this.contexts = new JDACommandContexts(this);
143        }
144        return this.contexts;
145    }
146
147    @Override
148    public CommandCompletions<?> getCommandCompletions() {
149        return this.completions;
150    }
151
152    @Override
153    public void registerCommand(BaseCommand command) {
154        command.onRegister(this);
155        for (Map.Entry<String, RootCommand> entry : command.registeredCommands.entrySet()) {
156            String commandName = entry.getKey().toLowerCase(Locale.ENGLISH);
157            JDARootCommand cmd = (JDARootCommand) entry.getValue();
158            if (!cmd.isRegistered) {
159                cmd.isRegistered = true;
160                commands.put(commandName, cmd);
161            }
162        }
163    }
164
165    public void unregisterCommand(BaseCommand command) {
166        for (Map.Entry<String, RootCommand> entry : command.registeredCommands.entrySet()) {
167            String jdaCommandName = entry.getKey().toLowerCase(Locale.ENGLISH);
168            JDARootCommand jdaCommand = (JDARootCommand) entry.getValue();
169            jdaCommand.getSubCommands().values().removeAll(command.subCommands.values());
170            if (jdaCommand.isRegistered && jdaCommand.getSubCommands().isEmpty()) {
171                jdaCommand.isRegistered = false;
172                commands.remove(jdaCommandName);
173            }
174        }
175    }
176
177    @Override
178    public boolean hasRegisteredCommands() {
179        return !this.commands.isEmpty();
180    }
181
182    @Override
183    public boolean isCommandIssuer(Class<?> type) {
184        return JDACommandEvent.class.isAssignableFrom(type);
185    }
186
187    @Override
188    public JDACommandEvent getCommandIssuer(Object issuer) {
189        if (!(issuer instanceof MessageReceivedEvent)) {
190            throw new IllegalArgumentException(issuer.getClass().getName() + " is not a Message Received Event.");
191        }
192        return new JDACommandEvent(this, (MessageReceivedEvent) issuer);
193    }
194
195    @Override
196    public RootCommand createRootCommand(String cmd) {
197        return new JDARootCommand(this, cmd);
198    }
199
200    @Override
201    public Collection<RootCommand> getRegisteredRootCommands() {
202        return Collections.unmodifiableCollection(commands.values());
203    }
204
205    @Override
206    public Locales getLocales() {
207        if (this.locales == null) {
208            this.locales = new JDALocales(this);
209            this.locales.loadLanguages();
210        }
211        return this.locales;
212    }
213
214    @Override
215    public CommandExecutionContext createCommandContext(RegisteredCommand command, CommandParameter parameter, CommandIssuer sender, List<String> args, int i, Map<String, Object> passedArgs) {
216        return new JDACommandExecutionContext(command, parameter, (JDACommandEvent) sender, args, i, passedArgs);
217    }
218
219    @Override
220    public CommandCompletionContext createCompletionContext(RegisteredCommand command, CommandIssuer sender, String input, String config, String[] args) {
221        // Not really going to be used;
222        //noinspection unchecked
223        return new CommandCompletionContext(command, sender, input, config, args);
224    }
225
226    @Override
227    public void log(LogLevel level, String message, Throwable throwable) {
228        Level logLevel = level == LogLevel.INFO ? Level.INFO : Level.SEVERE;
229        logger.log(logLevel, LogLevel.LOG_PREFIX + message);
230        if (throwable != null) {
231            for (String line : ACFPatterns.NEWLINE.split(ApacheCommonsExceptionUtil.getFullStackTrace(throwable))) {
232                logger.log(logLevel, LogLevel.LOG_PREFIX + line);
233            }
234        }
235    }
236
237    void dispatchEvent(MessageReceivedEvent event) {
238        Message message = event.getMessage();
239        String msg = message.getContentRaw();
240
241        CommandConfig config = getCommandConfig(event);
242
243        String prefixFound = null;
244        for (String prefix : config.getCommandPrefixes()) {
245            if (msg.startsWith(prefix)) {
246                prefixFound = prefix;
247                break;
248            }
249        }
250        if (prefixFound == null) {
251            return;
252        }
253
254        String[] args = ACFPatterns.SPACE.split(msg.substring(prefixFound.length()), -1);
255        if (args.length == 0) {
256            return;
257        }
258        String cmd = args[0].toLowerCase(Locale.ENGLISH);
259        JDARootCommand rootCommand = this.commands.get(cmd);
260        if (rootCommand == null) {
261            return;
262        }
263        if (args.length > 1) {
264            args = Arrays.copyOfRange(args, 1, args.length);
265        } else {
266            args = new String[0];
267        }
268        rootCommand.execute(this.getCommandIssuer(event), cmd, args);
269    }
270
271    private CommandConfig getCommandConfig(MessageReceivedEvent event) {
272        CommandConfig config = this.defaultConfig;
273        if (this.configProvider != null) {
274            CommandConfig provided = this.configProvider.provide(event);
275            if (provided != null) {
276                config = provided;
277            }
278        }
279        return config;
280    }
281
282
283    @Override
284    public String getCommandPrefix(CommandIssuer issuer) {
285        MessageReceivedEvent event = ((JDACommandEvent) issuer).getEvent();
286        CommandConfig commandConfig = getCommandConfig(event);
287        List<String> prefixes = commandConfig.getCommandPrefixes();
288        return prefixes.isEmpty() ? "" : prefixes.get(0);
289    }
290}