BigScreenBot.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. // Big Screen Bot
  2. //
  3. // Douglas Thrift
  4. //
  5. // BigScreenBot.java
  6. /* Copyright 2011 Douglas Thrift
  7. *
  8. * This file is part of Big Screen Bot.
  9. *
  10. * Big Screen Bot is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * Big Screen Bot is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with Big Screen Bot. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. package net.douglasthrift.bigscreenbot;
  24. import java.io.FileNotFoundException;
  25. import java.io.FileReader;
  26. import java.io.IOException;
  27. import java.net.InetAddress;
  28. import java.net.UnknownHostException;
  29. import java.util.ArrayList;
  30. import java.util.Arrays;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.Properties;
  34. import java.util.TreeMap;
  35. import java.util.regex.Pattern;
  36. import javax.net.ssl.SSLSocketFactory;
  37. import javax.jmdns.JmDNS;
  38. import javax.jmdns.ServiceEvent;
  39. import javax.jmdns.ServiceInfo;
  40. import javax.jmdns.ServiceListener;
  41. import com.google.anymote.common.AnymoteFactory;
  42. import com.google.anymote.common.ErrorListener;
  43. import com.google.anymote.device.DeviceAdapter;
  44. import com.google.anymote.device.MessageReceiver;
  45. import org.apache.oro.text.GlobCompiler;
  46. import org.jibble.pircbot.Colors;
  47. import org.jibble.pircbot.IrcException;
  48. import org.jibble.pircbot.PircBot;
  49. public class BigScreenBot extends PircBot
  50. {
  51. private static final String SETTINGS = "bigscreenbot.properties";
  52. private static final int CHANNEL = 0x1;
  53. private static final int PRIVATE = 0x2;
  54. private static final int BOTH = 0x3;
  55. private static enum Action { RECONNECT, RESTART, QUIT }
  56. private static class Settings extends Properties
  57. {
  58. public boolean getBooleanProperty(String key, boolean defaultValue)
  59. {
  60. String value = getProperty(key);
  61. if (value != null)
  62. return Boolean.parseBoolean(value);
  63. else
  64. return defaultValue;
  65. }
  66. public int getIntegerProperty(String key, int defaultValue)
  67. {
  68. String value = getProperty(key);
  69. if (value != null)
  70. return Integer.parseInt(value);
  71. else
  72. return defaultValue;
  73. }
  74. public List<String> getListProperty(String key)
  75. {
  76. String value = getProperty(key);
  77. if (value != null)
  78. return Arrays.asList(value.split("\\s"));
  79. else
  80. return null;
  81. }
  82. }
  83. private abstract class Command
  84. {
  85. private boolean admin;
  86. private int access;
  87. private String arguments, description;
  88. protected Command(boolean admin, int access, String arguments, String description)
  89. {
  90. this.admin = admin;
  91. this.access = access;
  92. this.arguments = arguments;
  93. this.description = description;
  94. }
  95. public abstract void execute(String channel, String sender, boolean admin, String argument);
  96. public int getAccess()
  97. {
  98. return access;
  99. }
  100. public String getArguments()
  101. {
  102. return arguments;
  103. }
  104. public String getDescription()
  105. {
  106. return description;
  107. }
  108. public boolean isAdmin()
  109. {
  110. return admin;
  111. }
  112. public boolean isChannel()
  113. {
  114. return (access & CHANNEL) != 0;
  115. }
  116. public boolean isPrivate()
  117. {
  118. return (access & PRIVATE) != 0;
  119. }
  120. }
  121. private Settings settings;
  122. private List<Pattern> admins = new ArrayList<Pattern>();
  123. private Map<String, Command> commands = new TreeMap<String, Command>();
  124. private Action action = Action.RECONNECT;
  125. private BigScreenBot(Settings settings)
  126. {
  127. super();
  128. this.settings = settings;
  129. setAutoNickChange(true);
  130. setFinger("Big Screen Bot");
  131. setMessageDelay(0);
  132. setVersion("Big Screen Bot (" + System.getProperty("os.name") + ")");
  133. if (settings.getBooleanProperty("ssl", false))
  134. {
  135. // TODO: figure out how to make this work with Jay's certificate
  136. setSocketFactory(SSLSocketFactory.getDefault());
  137. }
  138. setLogin(System.getProperty("user.name"));
  139. setName(settings.getProperty("nick", "bigscreenbot"));
  140. setVerbose(settings.getBooleanProperty("verbose", false));
  141. for (String admin : settings.getListProperty("admins"))
  142. admins.add(Pattern.compile(GlobCompiler.globToPerl5(admin.toCharArray(), GlobCompiler.DEFAULT_MASK), Pattern.CASE_INSENSITIVE));
  143. commands.put("fling", new Command(false, BOTH, "url [device]", "fling url to a Google TV device")
  144. {
  145. @Override
  146. public void execute(String channel, String sender, boolean admin, String argument)
  147. {
  148. }
  149. });
  150. commands.put("help", new Command(false, PRIVATE, "[command]", "show this help message")
  151. {
  152. @Override
  153. public void execute(String channel, String sender, boolean admin, String arguments)
  154. {
  155. String argument = arguments.split("\\s", 2)[0].toLowerCase();
  156. if (argument.startsWith("!"))
  157. argument = argument.substring(1);
  158. Command command = commands.get(argument);
  159. sendMessage(channel, sender, Colors.BOLD + "command arguments access description" + Colors.NORMAL);
  160. if (command != null)
  161. help(channel, sender, admin, argument, command);
  162. else
  163. for (Map.Entry<String, Command> nameCommand : commands.entrySet())
  164. help(channel, sender, admin, nameCommand.getKey(), nameCommand.getValue());
  165. }
  166. private void help(String channel, String sender, boolean admin, String name, Command command)
  167. {
  168. boolean unavailable = command.isAdmin() && !admin;
  169. String access;
  170. switch (command.getAccess())
  171. {
  172. case CHANNEL:
  173. access = "channel"; break;
  174. case PRIVATE:
  175. access = "private"; break;
  176. case BOTH: default:
  177. access = "channel/private"; break;
  178. }
  179. 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 : ""));
  180. }
  181. });
  182. commands.put("pair", new Command(true, PRIVATE, "[device [code]]", "pair with a Google TV device")
  183. {
  184. @Override
  185. public void execute(String channel, String sender, boolean admin, String argument)
  186. {
  187. }
  188. });
  189. commands.put("quit", new Command(true, PRIVATE, "[message]", "quit and do not come back")
  190. {
  191. @Override
  192. public void execute(String channel, String sender, boolean admin, String argument)
  193. {
  194. synchronized (action)
  195. {
  196. action = Action.QUIT;
  197. }
  198. if (!argument.isEmpty())
  199. quitServer(argument);
  200. else
  201. quitServer();
  202. }
  203. });
  204. commands.put("restart", new Command(true, PRIVATE, "", "quit and join running more up to date code")
  205. {
  206. @Override
  207. public void execute(String channel, String sender, boolean admin, String argument)
  208. {
  209. synchronized (action)
  210. {
  211. action = Action.RESTART;
  212. }
  213. quitServer("restarting");
  214. }
  215. });
  216. commands.put("say", new Command(true, PRIVATE, "nick|channel message", "say message to nick or channel")
  217. {
  218. @Override
  219. public void execute(String channel, String sender, boolean admin, String argument)
  220. {
  221. String[] arguments = argument.split("\\s", 2);
  222. if (arguments.length != 2)
  223. {
  224. help(channel, sender, admin, "say");
  225. return;
  226. }
  227. if (arguments[0].equalsIgnoreCase(getNick()))
  228. {
  229. sendMessage(channel, sender, "nice try");
  230. return;
  231. }
  232. sendMessage(arguments[0], arguments[1]);
  233. sendMessage(channel, sender, String.format("successfully sent message (\"%1$s\") to nick/channel (\"%2$s\")", arguments[1], arguments[0]));
  234. }
  235. });
  236. try
  237. {
  238. connect(settings.getProperty("server"), settings.getIntegerProperty("port", 6667));
  239. }
  240. catch (IOException exception)
  241. {
  242. error(exception, 1);
  243. }
  244. catch (IrcException exception)
  245. {
  246. error(exception, 1);
  247. }
  248. }
  249. public void sendMessage(String channel, String sender, String message)
  250. {
  251. sendMessage(channel != null ? channel : sender, message);
  252. }
  253. @Override
  254. protected void onConnect()
  255. {
  256. joinChannel("#bigscreenbot");
  257. }
  258. @Override
  259. protected void onDisconnect()
  260. {
  261. synchronized (action)
  262. {
  263. switch (action)
  264. {
  265. case RESTART:
  266. dispose();
  267. System.exit(2);
  268. case QUIT:
  269. dispose();
  270. break;
  271. default:
  272. while (true)
  273. {
  274. try
  275. {
  276. reconnect();
  277. }
  278. catch (IOException exception)
  279. {
  280. continue;
  281. }
  282. catch (IrcException exception)
  283. {
  284. error(exception, 1);
  285. }
  286. break;
  287. }
  288. }
  289. }
  290. }
  291. @Override
  292. protected void onMessage(String channel, String sender, String login, String hostname, String message)
  293. {
  294. message = Colors.removeFormattingAndColors(message);
  295. doCommandFromMessage(channel, sender, login, hostname, message);
  296. }
  297. @Override
  298. protected void onPrivateMessage(String sender, String login, String hostname, String message)
  299. {
  300. message = Colors.removeFormattingAndColors(message);
  301. doCommandFromMessage(null, sender, login, hostname, message);
  302. }
  303. private void doCommandFromMessage(String channel, String sender, String login, String hostname, String message)
  304. {
  305. boolean admin = matchNickMasks(sender, login, hostname, admins);
  306. // TODO: determine if this person is in our channels before responding
  307. String[] arguments = message.split("\\s", 2);
  308. String argument = arguments[0].toLowerCase();
  309. if (argument.startsWith("!"))
  310. argument = argument.substring(1);
  311. Command command = commands.get(argument);
  312. if (channel != null)
  313. {
  314. if (command == null)
  315. return;
  316. if (!admin && command.isAdmin())
  317. return;
  318. if (!command.isChannel())
  319. return;
  320. }
  321. else
  322. {
  323. if (command == null)
  324. {
  325. sendMessage(channel, sender, "unknown command (\"" + argument + "\"); try \"help\"");
  326. return;
  327. }
  328. if (!admin && command.isAdmin())
  329. {
  330. sendMessage(channel, sender, "unauthorized command (\"" + argument + "\")");
  331. return;
  332. }
  333. if (!command.isPrivate())
  334. {
  335. sendMessage(channel, sender, "inappropriate command (\"" + argument + "\")");
  336. return;
  337. }
  338. }
  339. command.execute(channel, sender, admin, arguments.length == 2 ? arguments[1] : "");
  340. }
  341. private void help(String channel, String sender, boolean admin, String command)
  342. {
  343. commands.get("help").execute(channel, sender, admin, command);
  344. }
  345. public static void main(String[] args)
  346. {
  347. /*JmDNS dns = JmDNS.create(InetAddress.getByName("10.0.0.1"));
  348. for (ServiceInfo info : dns.list("_anymote._tcp.local."))
  349. {
  350. System.out.println(info.getName());
  351. System.out.println(info.getInetAddresses()[0]);
  352. System.out.println(info.getPort());
  353. }
  354. dns.close();*/
  355. Settings settings = new Settings();
  356. FileReader reader = null;
  357. try
  358. {
  359. reader = new FileReader(SETTINGS);
  360. settings.load(reader);
  361. }
  362. catch (FileNotFoundException exception)
  363. {
  364. error(exception, 1);
  365. }
  366. catch (IOException exception)
  367. {
  368. error(exception, 1);
  369. }
  370. finally
  371. {
  372. if (reader != null)
  373. try
  374. {
  375. reader.close();
  376. }
  377. catch (IOException exception)
  378. {
  379. error(exception, 1);
  380. }
  381. }
  382. new BigScreenBot(settings);
  383. }
  384. private static void error(Exception exception)
  385. {
  386. System.err.println("bigscreenbot: " + exception.getMessage());
  387. }
  388. private static void error(Exception exception, int code)
  389. {
  390. error(exception);
  391. System.exit(code);
  392. }
  393. private static boolean matchNickMasks(String sender, String login, String hostname, List<Pattern> masks)
  394. {
  395. String nick = sender + "!" + login + "@" + hostname;
  396. for (Pattern mask : masks)
  397. if (mask.matcher(nick).matches())
  398. return true;
  399. try
  400. {
  401. InetAddress address = InetAddress.getByName(hostname);
  402. String name = address.getCanonicalHostName();
  403. if (!name.equalsIgnoreCase(hostname))
  404. {
  405. nick = sender + "!" + login + "@" + name;
  406. for (Pattern mask : masks)
  407. if (mask.matcher(nick).matches())
  408. return true;
  409. }
  410. }
  411. catch (UnknownHostException exception) {}
  412. return false;
  413. }
  414. }
  415. // vim: expandtab