/*
* Created on Apr 22, 2007
*/
package hudson.plugins.im.bot;
import hudson.plugins.im.AuthenticationHolder;
import hudson.plugins.im.IMChat;
import hudson.plugins.im.IMException;
import hudson.plugins.im.IMMessage;
import hudson.plugins.im.IMMessageListener;
import hudson.plugins.im.Sender;
import hudson.plugins.im.bot.SetAliasCommand.AliasCommand;
import hudson.plugins.im.tools.ExceptionHelper;
import hudson.plugins.im.tools.MessageHelper;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.logging.Logger;
import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContextHolder;
/**
* Instant messaging bot.
*
* @author Pascal Bleser
* @author kutzi
*/
public class Bot implements IMMessageListener {
private static final Logger LOGGER = Logger.getLogger(Bot.class.getName());
private class HelpCommand implements BotCommand {
public void executeCommand(IMChat groupChat, IMMessage message,
Sender sender, String[] args) throws IMException {
if (helpCache == null) {
final StringBuilder msg = new StringBuilder("Available commands:");
for (final Entry<String, BotCommand> item : cmdsAndAliases.entrySet()) {
// skip myself
if ((item.getValue() != this)
&& (item.getValue().getHelp() != null)) {
msg.append("\n");
msg.append(item.getKey());
msg.append(item.getValue().getHelp());
}
}
helpCache = msg.toString();
}
groupChat.sendMessage(helpCache);
}
public String getHelp() {
return null;
}
};
private static final Map<String, BotCommand> STATIC_COMMANDS_MAP;
static {
STATIC_COMMANDS_MAP = new HashMap<String, BotCommand>();
STATIC_COMMANDS_MAP.put("status", new StatusCommand());
STATIC_COMMANDS_MAP.put("s", new StatusCommand());
STATIC_COMMANDS_MAP.put("health", new HealthCommand());
STATIC_COMMANDS_MAP.put("h", new HealthCommand());
STATIC_COMMANDS_MAP.put("jobs", new StatusCommand());
STATIC_COMMANDS_MAP.put("queue", new QueueCommand());
STATIC_COMMANDS_MAP.put("q", new QueueCommand());
STATIC_COMMANDS_MAP.put("testresult", new TestResultCommand());
STATIC_COMMANDS_MAP.put("abort", new AbortCommand());
STATIC_COMMANDS_MAP.put("comment", new CommentCommand());
STATIC_COMMANDS_MAP.put("botsnack", new SnackCommand());
STATIC_COMMANDS_MAP.put("userstat", new UserStatCommand());
}
private final SortedMap<String, BotCommand> cmdsAndAliases = new TreeMap<String, BotCommand>();
private final IMChat chat;
private final String nick;
private final String imServer;
private final String commandPrefix;
private String helpCache = null;
private final AuthenticationHolder authentication;
public Bot(IMChat chat, String nick, String imServer,
String commandPrefix, AuthenticationHolder authentication
) {
this.chat = chat;
this.nick = nick;
this.imServer = imServer;
this.commandPrefix = commandPrefix;
this.authentication = authentication;
this.cmdsAndAliases.putAll(STATIC_COMMANDS_MAP);
BuildCommand buildCommand = new BuildCommand(this.nick + "@" + this.imServer);
this.cmdsAndAliases.put("build", buildCommand);
this.cmdsAndAliases.put("schedule", buildCommand);
this.cmdsAndAliases.put("help", new HelpCommand());
this.cmdsAndAliases.put("alias", new SetAliasCommand(this));
chat.addMessageListener(this);
}
public void onMessage(IMMessage msg) {
// is it a command for me ? (returns null if not, the payload if so)
String payload = retrieveMessagePayLoad(msg.getBody());
if (payload != null) {
String sender = msg.getFrom();
if (!msg.isAuthorized()) {
try {
this.chat.sendMessage(sender + " you're not a buddy of me. I won't take any commands from you.");
} catch (IMException e) {
LOGGER.warning(ExceptionHelper.dump(e));
}
return;
}
// split words
String[] args = MessageHelper.extractCommandLine(payload);
if (args.length > 0) {
// first word is the command name
String cmd = args[0];
String id = this.chat.getIMId(sender);
final Sender s;
if (id != null) {
s = new Sender(this.chat.getNickName(sender), id);
} else {
s = new Sender(this.chat.getNickName(sender));
}
try {
BotCommand command = this.cmdsAndAliases.get(cmd);
if (command != null) {
Authentication oldAuthentication = SecurityContextHolder.getContext().getAuthentication();
try {
if (this.authentication != null) {
SecurityContextHolder.getContext().setAuthentication(this.authentication.getAuthentication());
}
command.executeCommand(this.chat, msg, s, args);
} finally {
if (this.authentication != null) {
SecurityContextHolder.getContext().setAuthentication(oldAuthentication);
}
}
} else {
this.chat.sendMessage(sender + " did you mean me? Unknown command '" + cmd
+ "'\nUse " + this.commandPrefix + "help to get help!");
}
} catch (IMException e) {
LOGGER.warning(ExceptionHelper.dump(e));
}
}
}
}
private static boolean isNickSeparator(final String candidate) {
return ":".equals(candidate) || ",".equals(candidate);
}
private String retrieveMessagePayLoad(final String body) {
if (body == null) {
return null;
}
if (body.startsWith(this.commandPrefix)) {
return body.substring(this.commandPrefix.length()).trim();
}
if (body.startsWith(this.nick)
&& isNickSeparator(body.substring(this.nick.length(), this.nick
.length() + 1))) {
return body.substring(this.nick.length() + 1).trim();
}
return null;
}
/**
* Returns the command or alias associated with the given name
* or <code>null</code>.
*/
BotCommand getCommand(String name) {
return this.cmdsAndAliases.get(name);
}
/**
* Registers a new alias.
*
* @return the alias previously registered under this name or <code>null</code>
* if no alias was registered by that name previously
* @throws IllegalArgumentException when trying to override a built-in command
*/
BotCommand addAlias(String name, BotCommand alias) {
BotCommand old = this.cmdsAndAliases.get(name);
if (old != null && ! (old instanceof AliasCommand)) {
throw new IllegalArgumentException("Won't override built-in command: '" + name + "'!");
}
this.cmdsAndAliases.put(name, alias);
this.helpCache = null;
return old;
}
/**
* Removes an existing alias.
*
* @param name The name of the alias
* @return the removed alias or <code>null</code> if no alias by that name is registered
*/
AliasCommand removeAlias(String name) {
BotCommand alias = this.cmdsAndAliases.get(name);
if (alias instanceof AliasCommand) {
this.cmdsAndAliases.remove(name);
return (AliasCommand) alias;
} else if (alias != null) {
throw new IllegalArgumentException("Won't remove built-in command: '" + name + "'!");
}
return null;
}
/**
* Returns a map of all currently defined aliases.
* The map is sorted by the alias name.
*/
SortedMap<String, AliasCommand> getAliases() {
SortedMap<String, AliasCommand> result = new TreeMap<String, AliasCommand>();
for (Map.Entry<String, BotCommand> entry : this.cmdsAndAliases.entrySet()) {
if (entry.getValue() instanceof AliasCommand) {
result.put(entry.getKey(), (AliasCommand) entry.getValue());
}
}
return result;
}
/**
* Called on Hudson shutdown.
*/
public void shutdown() {
this.chat.removeMessageListener(this);
if (this.chat.isMultiUserChat()) {
try {
chat.sendMessage("Oops, seems like Hudson is going down now. See ya!");
} catch (IMException e) {
// ignore
}
}
}
}