BigScreenBot.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  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. import org.jibble.pircbot.User;
  50. public class BigScreenBot extends PircBot
  51. {
  52. private static final String SETTINGS = "bigscreenbot.properties";
  53. private static final int CHANNEL = 0x1;
  54. private static final int PRIVATE = 0x2;
  55. private static final int BOTH = 0x3;
  56. private static enum Action { RECONNECT, RESTART, QUIT }
  57. private static class Settings extends Properties
  58. {
  59. public boolean getBooleanProperty(String key, boolean defaultValue)
  60. {
  61. String value = getProperty(key);
  62. if (value != null)
  63. return Boolean.parseBoolean(value);
  64. else
  65. return defaultValue;
  66. }
  67. public int getIntegerProperty(String key, int defaultValue)
  68. {
  69. String value = getProperty(key);
  70. if (value != null)
  71. return Integer.parseInt(value);
  72. else
  73. return defaultValue;
  74. }
  75. public List<String> getListProperty(String key)
  76. {
  77. String value = getProperty(key);
  78. if (value != null)
  79. return Arrays.asList(value.split("\\s"));
  80. else
  81. return null;
  82. }
  83. }
  84. private abstract class Command
  85. {
  86. private boolean admin;
  87. private int access;
  88. private String arguments, description;
  89. protected Command(boolean admin, int access, String arguments, String description)
  90. {
  91. this.admin = admin;
  92. this.access = access;
  93. this.arguments = arguments;
  94. this.description = description;
  95. }
  96. public abstract void execute(String channel, String sender, boolean admin, String argument);
  97. public int getAccess()
  98. {
  99. return access;
  100. }
  101. public String getArguments()
  102. {
  103. return arguments;
  104. }
  105. public String getDescription()
  106. {
  107. return description;
  108. }
  109. public boolean isAdmin()
  110. {
  111. return admin;
  112. }
  113. public boolean isChannel()
  114. {
  115. return (access & CHANNEL) != 0;
  116. }
  117. public boolean isPrivate()
  118. {
  119. return (access & PRIVATE) != 0;
  120. }
  121. }
  122. private Settings settings;
  123. private List<Pattern> admins = new ArrayList<Pattern>();
  124. private Map<String, Command> commands = new TreeMap<String, Command>();
  125. private Action action = Action.RECONNECT;
  126. private JmDNS mdns;
  127. private BigScreenBot(Settings settings)
  128. {
  129. super();
  130. this.settings = settings;
  131. setAutoNickChange(true);
  132. setFinger("Big Screen Bot");
  133. setMessageDelay(0);
  134. setVersion("Big Screen Bot (" + System.getProperty("os.name") + ")");
  135. if (settings.getBooleanProperty("ssl", false))
  136. {
  137. // TODO: figure out how to make this work with Jay's certificate
  138. setSocketFactory(SSLSocketFactory.getDefault());
  139. }
  140. setLogin(System.getProperty("user.name"));
  141. setName(settings.getProperty("nick", "bigscreenbot"));
  142. setVerbose(settings.getBooleanProperty("verbose", false));
  143. for (String admin : settings.getListProperty("admins"))
  144. admins.add(Pattern.compile(GlobCompiler.globToPerl5(admin.toCharArray(), GlobCompiler.DEFAULT_MASK), Pattern.CASE_INSENSITIVE));
  145. commands.put("fling", new Command(false, BOTH, "url [device]", "fling url to a Google TV device")
  146. {
  147. @Override
  148. public void execute(String channel, String sender, boolean admin, String argument)
  149. {
  150. }
  151. });
  152. commands.put("help", new Command(false, PRIVATE, "[command]", "show this help message")
  153. {
  154. @Override
  155. public void execute(String channel, String sender, boolean admin, String arguments)
  156. {
  157. String argument = arguments.split("\\s", 2)[0].toLowerCase();
  158. if (argument.startsWith("!"))
  159. argument = argument.substring(1);
  160. Command command = commands.get(argument);
  161. sendMessage(channel, sender, Colors.BOLD + "command arguments access description" + Colors.NORMAL);
  162. if (command != null)
  163. help(channel, sender, admin, argument, command);
  164. else
  165. for (Map.Entry<String, Command> nameCommand : commands.entrySet())
  166. help(channel, sender, admin, nameCommand.getKey(), nameCommand.getValue());
  167. }
  168. private void help(String channel, String sender, boolean admin, String name, Command command)
  169. {
  170. boolean unavailable = command.isAdmin() && !admin;
  171. String access;
  172. switch (command.getAccess())
  173. {
  174. case CHANNEL:
  175. access = "channel"; break;
  176. case PRIVATE:
  177. access = "private"; break;
  178. case BOTH: default:
  179. access = "channel/private"; break;
  180. }
  181. 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 : ""));
  182. }
  183. });
  184. commands.put("pair", new Command(true, PRIVATE, "[device [code]]", "pair with a Google TV device")
  185. {
  186. @Override
  187. public void execute(String channel, String sender, boolean admin, String argument)
  188. {
  189. /*JmDNS dns = JmDNS.create(InetAddress.getByName(settings.getProperty("interface")));
  190. for (ServiceInfo info : dns.list("_anymote._tcp.local."))
  191. {
  192. System.out.println(info.getName());
  193. System.out.println(info.getInetAddresses()[0]);
  194. System.out.println(info.getPort());
  195. }
  196. dns.close();*/
  197. }
  198. });
  199. commands.put("quit", new Command(true, PRIVATE, "[message]", "quit and do not come back")
  200. {
  201. @Override
  202. public void execute(String channel, String sender, boolean admin, String argument)
  203. {
  204. synchronized (action)
  205. {
  206. action = Action.QUIT;
  207. }
  208. if (!argument.isEmpty())
  209. quitServer(argument);
  210. else
  211. quitServer();
  212. }
  213. });
  214. commands.put("restart", new Command(true, PRIVATE, "", "quit and join running more up to date code")
  215. {
  216. @Override
  217. public void execute(String channel, String sender, boolean admin, String argument)
  218. {
  219. synchronized (action)
  220. {
  221. action = Action.RESTART;
  222. }
  223. quitServer("restarting");
  224. }
  225. });
  226. commands.put("say", new Command(true, PRIVATE, "nick|channel message", "say message to nick or channel")
  227. {
  228. @Override
  229. public void execute(String channel, String sender, boolean admin, String argument)
  230. {
  231. String[] arguments = argument.split("\\s", 2);
  232. if (arguments.length != 2)
  233. {
  234. help(channel, sender, admin, "say");
  235. return;
  236. }
  237. if (arguments[0].equalsIgnoreCase(getNick()))
  238. {
  239. sendMessage(channel, sender, "nice try");
  240. return;
  241. }
  242. sendMessage(arguments[0], arguments[1]);
  243. sendMessage(channel, sender, String.format("successfully sent message (\"%1$s\") to nick/channel (\"%2$s\")", arguments[1], arguments[0]));
  244. }
  245. });
  246. try
  247. {
  248. connect(settings.getProperty("server"), settings.getIntegerProperty("port", 6667));
  249. }
  250. catch (IOException exception)
  251. {
  252. error(exception, 1);
  253. }
  254. catch (IrcException exception)
  255. {
  256. error(exception, 1);
  257. }
  258. }
  259. public void sendMessage(String channel, String sender, String message)
  260. {
  261. sendMessage(channel != null ? channel : sender, message);
  262. }
  263. @Override
  264. protected void onConnect()
  265. {
  266. joinChannel("#bigscreenbot");
  267. }
  268. @Override
  269. protected void onDisconnect()
  270. {
  271. synchronized (action)
  272. {
  273. switch (action)
  274. {
  275. case RESTART:
  276. dispose();
  277. System.exit(2);
  278. case QUIT:
  279. dispose();
  280. break;
  281. default:
  282. while (true)
  283. {
  284. try
  285. {
  286. reconnect();
  287. }
  288. catch (IOException exception)
  289. {
  290. continue;
  291. }
  292. catch (IrcException exception)
  293. {
  294. error(exception, 1);
  295. }
  296. break;
  297. }
  298. }
  299. }
  300. }
  301. @Override
  302. protected void onMessage(String channel, String sender, String login, String hostname, String message)
  303. {
  304. message = Colors.removeFormattingAndColors(message);
  305. doCommandFromMessage(channel, sender, login, hostname, message);
  306. }
  307. @Override
  308. protected void onPrivateMessage(String sender, String login, String hostname, String message)
  309. {
  310. message = Colors.removeFormattingAndColors(message);
  311. doCommandFromMessage(null, sender, login, hostname, message);
  312. }
  313. private void doCommandFromMessage(String channel, String sender, String login, String hostname, String message)
  314. {
  315. boolean admin = matchNickMasks(sender, login, hostname, admins);
  316. if (!admin && !isNickInChannels(sender))
  317. return;
  318. String[] arguments = message.split("\\s", 2);
  319. String argument = arguments[0].toLowerCase();
  320. if (argument.startsWith("!"))
  321. argument = argument.substring(1);
  322. Command command = commands.get(argument);
  323. if (channel != null)
  324. {
  325. if (command == null)
  326. return;
  327. if (!admin && command.isAdmin())
  328. return;
  329. if (!command.isChannel())
  330. return;
  331. }
  332. else
  333. {
  334. if (command == null)
  335. {
  336. sendMessage(channel, sender, "unknown command (\"" + argument + "\"); try \"help\"");
  337. return;
  338. }
  339. if (!admin && command.isAdmin())
  340. {
  341. sendMessage(channel, sender, "unauthorized command (\"" + argument + "\")");
  342. return;
  343. }
  344. if (!command.isPrivate())
  345. {
  346. sendMessage(channel, sender, "inappropriate command (\"" + argument + "\")");
  347. return;
  348. }
  349. }
  350. command.execute(channel, sender, admin, arguments.length == 2 ? arguments[1] : "");
  351. }
  352. private void help(String channel, String sender, boolean admin, String command)
  353. {
  354. commands.get("help").execute(channel, sender, admin, command);
  355. }
  356. private boolean isNickInChannels(String nick)
  357. {
  358. for (String channel : getChannels())
  359. for (User user : getUsers(channel))
  360. if (user.equals(nick))
  361. return true;
  362. return false;
  363. }
  364. public static void main(String[] args)
  365. {
  366. Settings settings = new Settings();
  367. FileReader reader = null;
  368. try
  369. {
  370. reader = new FileReader(SETTINGS);
  371. settings.load(reader);
  372. }
  373. catch (FileNotFoundException exception)
  374. {
  375. error(exception, 1);
  376. }
  377. catch (IOException exception)
  378. {
  379. error(exception, 1);
  380. }
  381. finally
  382. {
  383. if (reader != null)
  384. try
  385. {
  386. reader.close();
  387. }
  388. catch (IOException exception)
  389. {
  390. error(exception, 1);
  391. }
  392. }
  393. new BigScreenBot(settings);
  394. }
  395. private static void error(Exception exception)
  396. {
  397. System.err.println("bigscreenbot: " + exception.getMessage());
  398. }
  399. private static void error(Exception exception, int code)
  400. {
  401. error(exception);
  402. System.exit(code);
  403. }
  404. private static boolean matchNickMasks(String sender, String login, String hostname, List<Pattern> masks)
  405. {
  406. String nick = sender + "!" + login + "@" + hostname;
  407. for (Pattern mask : masks)
  408. if (mask.matcher(nick).matches())
  409. return true;
  410. try
  411. {
  412. InetAddress address = InetAddress.getByName(hostname);
  413. String name = address.getCanonicalHostName();
  414. if (!name.equalsIgnoreCase(hostname))
  415. {
  416. nick = sender + "!" + login + "@" + name;
  417. for (Pattern mask : masks)
  418. if (mask.matcher(nick).matches())
  419. return true;
  420. }
  421. }
  422. catch (UnknownHostException exception) {}
  423. return false;
  424. }
  425. }
  426. // vim: expandtab