// 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