// Big Screen Bot
//
// Douglas Thrift
//
// BigScreenBot.java
/* Copyright 2011 Douglas Thrift
*
* This file is part of Big Screen Bot.
*
* Big Screen Bot is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Big Screen Bot 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Big Screen Bot. If not, see .
*/
package net.douglasthrift.bigscreenbot;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.regex.Pattern;
import javax.net.ssl.SSLSocketFactory;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import com.google.anymote.common.AnymoteFactory;
import com.google.anymote.common.ErrorListener;
import com.google.anymote.device.DeviceAdapter;
import com.google.anymote.device.MessageReceiver;
import org.apache.oro.text.GlobCompiler;
import org.jibble.pircbot.Colors;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.PircBot;
public class BigScreenBot extends PircBot
{
private static final String SETTINGS = "bigscreenbot.properties";
private static final int CHANNEL = 0x1;
private static final int PRIVATE = 0x2;
private static final int BOTH = 0x3;
private static enum Action { RECONNECT, RESTART, QUIT }
private static class Settings extends Properties
{
public boolean getBooleanProperty(String key, boolean defaultValue)
{
String value = getProperty(key);
if (value != null)
return Boolean.parseBoolean(value);
else
return defaultValue;
}
public int getIntegerProperty(String key, int defaultValue)
{
String value = getProperty(key);
if (value != null)
return Integer.parseInt(value);
else
return defaultValue;
}
public List getListProperty(String key)
{
String value = getProperty(key);
if (value != null)
return Arrays.asList(value.split("\\s"));
else
return null;
}
}
private abstract class Command
{
private boolean admin;
private int access;
private String arguments, description;
protected Command(boolean admin, int access, String arguments, String description)
{
this.admin = admin;
this.access = access;
this.arguments = arguments;
this.description = description;
}
public abstract void execute(String channel, String sender, boolean admin, String argument);
public int getAccess()
{
return access;
}
public String getArguments()
{
return arguments;
}
public String getDescription()
{
return description;
}
public boolean isAdmin()
{
return admin;
}
public boolean isChannel()
{
return (access & CHANNEL) != 0;
}
public boolean isPrivate()
{
return (access & PRIVATE) != 0;
}
}
private Settings settings;
private List admins = new ArrayList();
private Map commands = new TreeMap();
private Action action = Action.RECONNECT;
private BigScreenBot(Settings settings)
{
super();
this.settings = settings;
setAutoNickChange(true);
setFinger("Big Screen Bot");
setMessageDelay(0);
setVersion("Big Screen Bot (" + System.getProperty("os.name") + ")");
if (settings.getBooleanProperty("ssl", false))
{
// TODO: figure out how to make this work with Jay's certificate
setSocketFactory(SSLSocketFactory.getDefault());
}
setLogin(System.getProperty("user.name"));
setName(settings.getProperty("nick", "bigscreenbot"));
setVerbose(settings.getBooleanProperty("verbose", false));
for (String admin : settings.getListProperty("admins"))
admins.add(Pattern.compile(GlobCompiler.globToPerl5(admin.toCharArray(), GlobCompiler.DEFAULT_MASK), Pattern.CASE_INSENSITIVE));
commands.put("fling", new Command(false, BOTH, "url [device]", "fling url to a Google TV device")
{
@Override
public void execute(String channel, String sender, boolean admin, String argument)
{
}
});
commands.put("help", new Command(false, PRIVATE, "[command]", "show this help message")
{
@Override
public void execute(String channel, String sender, boolean admin, String arguments)
{
String argument = arguments.split("\\s", 2)[0].toLowerCase();
if (argument.startsWith("!"))
argument = argument.substring(1);
Command command = commands.get(argument);
sendMessage(channel, sender, Colors.BOLD + "command arguments access description" + Colors.NORMAL);
if (command != null)
help(channel, sender, admin, argument, command);
else
for (Map.Entry nameCommand : commands.entrySet())
help(channel, sender, admin, nameCommand.getKey(), nameCommand.getValue());
}
private void help(String channel, String sender, boolean admin, String name, Command command)
{
boolean unavailable = command.isAdmin() && !admin;
String access;
switch (command.getAccess())
{
case CHANNEL:
access = "channel"; break;
case PRIVATE:
access = "private"; break;
case BOTH: default:
access = "channel/private"; break;
}
sendMessage(channel, sender, (unavailable ? Colors.UNDERLINE : "") + String.format("%1$-11s %2$-23s %3$-15s %4$s", name, command.getArguments(), access, command.getDescription()) + (unavailable ? Colors.NORMAL : ""));
}
});
commands.put("pair", new Command(true, PRIVATE, "[device [code]]", "pair with a Google TV device")
{
@Override
public void execute(String channel, String sender, boolean admin, String argument)
{
}
});
commands.put("quit", new Command(true, PRIVATE, "[message]", "quit and do not come back")
{
@Override
public void execute(String channel, String sender, boolean admin, String argument)
{
synchronized (action)
{
action = Action.QUIT;
}
if (!argument.isEmpty())
quitServer(argument);
else
quitServer();
}
});
commands.put("restart", new Command(true, PRIVATE, "", "quit and join running more up to date code")
{
@Override
public void execute(String channel, String sender, boolean admin, String argument)
{
synchronized (action)
{
action = Action.RESTART;
}
quitServer("restarting");
}
});
commands.put("say", new Command(true, PRIVATE, "nick|channel message", "say message to nick or channel")
{
@Override
public void execute(String channel, String sender, boolean admin, String argument)
{
String[] arguments = argument.split("\\s", 2);
if (arguments.length != 2)
{
help(channel, sender, admin, "say");
return;
}
if (arguments[0].equalsIgnoreCase(getNick()))
{
sendMessage(channel, sender, "nice try");
return;
}
sendMessage(arguments[0], arguments[1]);
sendMessage(channel, sender, String.format("successfully sent message (\"%1$s\") to nick/channel (\"%2$s\")", arguments[1], arguments[0]));
}
});
try
{
connect(settings.getProperty("server"), settings.getIntegerProperty("port", 6667));
}
catch (IOException exception)
{
error(exception, 1);
}
catch (IrcException exception)
{
error(exception, 1);
}
}
public void sendMessage(String channel, String sender, String message)
{
sendMessage(channel != null ? channel : sender, message);
}
@Override
protected void onConnect()
{
joinChannel("#bigscreenbot");
}
@Override
protected void onDisconnect()
{
synchronized (action)
{
switch (action)
{
case RESTART:
dispose();
System.exit(2);
case QUIT:
dispose();
break;
default:
while (true)
{
try
{
reconnect();
}
catch (IOException exception)
{
continue;
}
catch (IrcException exception)
{
error(exception, 1);
}
break;
}
}
}
}
@Override
protected void onMessage(String channel, String sender, String login, String hostname, String message)
{
message = Colors.removeFormattingAndColors(message);
doCommandFromMessage(channel, sender, login, hostname, message);
}
@Override
protected void onPrivateMessage(String sender, String login, String hostname, String message)
{
message = Colors.removeFormattingAndColors(message);
doCommandFromMessage(null, sender, login, hostname, message);
}
private void doCommandFromMessage(String channel, String sender, String login, String hostname, String message)
{
boolean admin = matchNickMasks(sender, login, hostname, admins);
// TODO: determine if this person is in our channels before responding
String[] arguments = message.split("\\s", 2);
String argument = arguments[0].toLowerCase();
if (argument.startsWith("!"))
argument = argument.substring(1);
Command command = commands.get(argument);
if (channel != null)
{
if (command == null)
return;
if (!admin && command.isAdmin())
return;
if (!command.isChannel())
return;
}
else
{
if (command == null)
{
sendMessage(channel, sender, "unknown command (\"" + argument + "\"); try \"help\"");
return;
}
if (!admin && command.isAdmin())
{
sendMessage(channel, sender, "unauthorized command (\"" + argument + "\")");
return;
}
if (!command.isPrivate())
{
sendMessage(channel, sender, "inappropriate command (\"" + argument + "\")");
return;
}
}
command.execute(channel, sender, admin, arguments.length == 2 ? arguments[1] : "");
}
private void help(String channel, String sender, boolean admin, String command)
{
commands.get("help").execute(channel, sender, admin, command);
}
public static void main(String[] args)
{
/*JmDNS dns = JmDNS.create(InetAddress.getByName("10.0.0.1"));
for (ServiceInfo info : dns.list("_anymote._tcp.local."))
{
System.out.println(info.getName());
System.out.println(info.getInetAddresses()[0]);
System.out.println(info.getPort());
}
dns.close();*/
Settings settings = new Settings();
FileReader reader = null;
try
{
reader = new FileReader(SETTINGS);
settings.load(reader);
}
catch (FileNotFoundException exception)
{
error(exception, 1);
}
catch (IOException exception)
{
error(exception, 1);
}
finally
{
if (reader != null)
try
{
reader.close();
}
catch (IOException exception)
{
error(exception, 1);
}
}
new BigScreenBot(settings);
}
private static void error(Exception exception)
{
System.err.println("bigscreenbot: " + exception.getMessage());
}
private static void error(Exception exception, int code)
{
error(exception);
System.exit(code);
}
private static boolean matchNickMasks(String sender, String login, String hostname, List masks)
{
String nick = sender + "!" + login + "@" + hostname;
for (Pattern mask : masks)
if (mask.matcher(nick).matches())
return true;
try
{
InetAddress address = InetAddress.getByName(hostname);
String name = address.getCanonicalHostName();
if (!name.equalsIgnoreCase(hostname))
{
nick = sender + "!" + login + "@" + name;
for (Pattern mask : masks)
if (mask.matcher(nick).matches())
return true;
}
}
catch (UnknownHostException exception) {}
return false;
}
}
// vim: expandtab