/*
* Eduardo, an IRC bot framework
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) Eduardo team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.eduardo.service.command;
import com.google.inject.Inject;
import com.sk89q.eduardo.event.CommandEvent;
import com.sk89q.eduardo.event.CommandQueryEvent;
import com.sk89q.eduardo.event.message.MessageEvent;
import com.sk89q.eduardo.model.context.Context;
import com.sk89q.eduardo.model.response.Response;
import com.sk89q.eduardo.service.auth.AuthService;
import com.sk89q.eduardo.service.auth.Subject;
import com.sk89q.eduardo.service.event.EventBus;
import com.sk89q.eduardo.service.event.EventHandler.Priority;
import com.sk89q.eduardo.service.event.Subscribe;
import com.sk89q.eduardo.service.plugin.Plugin;
import com.sk89q.eduardo.service.throttle.RateLimiter;
import com.sk89q.eduardo.util.config.Config;
import com.sk89q.intake.CommandException;
import com.sk89q.intake.CommandMapping;
import com.sk89q.intake.InvocationCommandException;
import com.sk89q.intake.context.CommandContext;
import com.sk89q.intake.context.CommandLocals;
import com.sk89q.intake.dispatcher.Dispatcher;
import com.sk89q.intake.dispatcher.SimpleDispatcher;
import com.sk89q.intake.parametric.ParametricBuilder;
import com.sk89q.intake.util.auth.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Supplier;
@Plugin(id = "command-manager")
public class SimpleCommandManager implements CommandManager {
private static final Logger log = LoggerFactory.getLogger(CommandManager.class);
private static final int MAX_DEPTH = 10;
private final EventBus eventBus;
private final Supplier<String> prefix;
@Inject private AuthService authService;
private final Dispatcher dispatcher;
private final ParametricBuilder builder;
@Inject
public SimpleCommandManager(Config config, EventBus eventBus, AuthService authService, RateLimiter limiter) {
this.prefix = config.stringAt("command.prefix", ".");
this.eventBus = eventBus;
eventBus.register(this);
dispatcher = new SimpleDispatcher();
builder = new ParametricBuilder();
builder.setAuthorizer(new ServiceAuthorizer(authService));
builder.addBinding(new DefaultBinding());
builder.addExceptionConverter(new DefaultExceptionConverter());
builder.addInvokeListener(new RateLimitListener(limiter));
}
@Override
public Dispatcher getDispatcher() {
return dispatcher;
}
@Override
public void register(Object object) {
builder.registerMethodsAsCommands(dispatcher, object);
}
@Override
public String removePrefix(String arguments) {
String prefix = this.prefix.get();
if (arguments.startsWith(prefix)) {
return arguments.substring(prefix.length());
} else {
return arguments;
}
}
@Subscribe(priority = Priority.VERY_EARLY, ignoreCancelled = true)
public void handleCommandRecursion(CommandEvent event) {
if (event.getDepth() >= MAX_DEPTH) {
event.getResponse().respond("error: Commands have reached the maximum recursion limit");
event.setCancelled(true);
}
}
@Subscribe(ignoreCancelled = true)
public void onCommand(CommandEvent event) {
String[] split = CommandContext.split(event.getArguments());
if (split.length > 0 && dispatcher.contains(split[0])) {
CommandLocals locals = new CommandLocals();
Context context = event.getContext();
locals.put(CommandEvent.class, event);
locals.put(Response.class, event.getResponse());
locals.put(Context.class, context);
locals.put(Subject.class, authService.login(event.getContext()));
event.setCancelled(true);
try {
dispatcher.call(event.getArguments(), locals, new String[0]);
} catch (InvocationCommandException e) {
log.warn("Failed to execute a command", e);
event.getResponse().respond("An unexpected error occurred while executing the command");
} catch (CommandException e) {
event.getResponse().respond("error: " + e.getMessage());
} catch (AuthorizationException ignored) {
log.info("User was not permitted to run !" + event.getArguments());
}
}
}
@Subscribe(ignoreCancelled = true)
public void onCommandQueryEvent(CommandQueryEvent event) {
if (event.getDescription() == null) {
CommandLocals locals = new CommandLocals();
Context context = event.getContext();
locals.put(CommandEvent.class, event);
locals.put(Context.class, context);
locals.put(Subject.class, authService.login(event.getContext()));
CommandMapping mapping = dispatcher.get(event.getCommand());
if (mapping != null && mapping.getCallable().testPermission(locals)) {
event.setDescription(mapping.getDescription().getShortDescription());
}
}
}
@Subscribe
public void onMessage(MessageEvent event) {
String message = event.getMessage();
String prefix = this.prefix.get();
if (message.length() > prefix.length() && message.startsWith(prefix)) {
String arguments = message.substring(prefix.length());
String[] split = CommandContext.split(arguments);
String command = split[0];
if (command.length() >= 2 && command.endsWith("?")) {
if (authService.login(event.getContext()).testPermission("help")) {
String actualCommand = command.substring(0, command.length() - 1);
CommandQueryEvent queryEvent = new CommandQueryEvent(event.getContext(), actualCommand);
eventBus.post(queryEvent);
if (!queryEvent.isCancelled()) {
String desc = queryEvent.getDescription();
if (desc != null) {
event.getResponse().respond(actualCommand + ": " + desc);
} else {
event.getResponse().respond("No information is available for " + actualCommand);
}
}
}
} else {
eventBus.post(new CommandEvent(event.getContext(), arguments, event.getResponse()));
}
}
}
}