--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry kind="src" path="src"/>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>\r
+ <classpathentry kind="lib" path="mysql-connector-java-5.0.8-bin.jar">\r
+ <attributes>\r
+ <attribute name="javadoc_location" value="jar:platform:/resource/RizonHelpBot/mysql-connector-java-5.0.8-doc.jar!/"/>\r
+ </attributes>\r
+ </classpathentry>\r
+ <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+ <name>BncBot</name>\r
+ <comment></comment>\r
+ <projects>\r
+ </projects>\r
+ <buildSpec>\r
+ <buildCommand>\r
+ <name>org.eclipse.jdt.core.javabuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ </buildSpec>\r
+ <natures>\r
+ <nature>org.eclipse.jdt.core.javanature</nature>\r
+ </natures>\r
+</projectDescription>\r
--- /dev/null
+package net.rizon;\r
+\r
+import java.sql.Connection;\r
+import java.sql.DriverManager;\r
+import java.sql.SQLException;\r
+import java.util.Properties;\r
+\r
+public class DatabaseConnection {\r
+ private static Connection con = null;\r
+ private static Properties props = null;\r
+\r
+ public static Connection getConnection() {\r
+ if (props == null) throw new RuntimeException("DatabaseConnection not initialized");\r
+ \r
+ try {\r
+ if((con != null) && con.isValid(2)) {\r
+ return con;\r
+ } else { // Attempt reconnect.\r
+ if (con != null) con.close();\r
+ \r
+ DatabaseConnection.connect();\r
+ \r
+ if(con == null) {\r
+ throw new RuntimeException("MySQL server has gone away. Attempted reconnection failed.");\r
+ }\r
+ \r
+ return con;\r
+ }\r
+ }\r
+ catch (SQLException e) {\r
+ e.printStackTrace();\r
+ }\r
+ return con;\r
+ }\r
+ \r
+ public static boolean isInitialized() {\r
+ return props != null;\r
+ }\r
+ \r
+ public static void setProps(Properties aProps) {\r
+ props = aProps;\r
+ }\r
+ \r
+ private static void connect() {\r
+ String driver = props.getProperty("sqlDriver");\r
+ String url = props.getProperty("sqlUrl");\r
+ String user = props.getProperty("sqlUser");\r
+ String password = props.getProperty("sqlPass");\r
+ \r
+ try {\r
+ Class.forName(driver);\r
+ } catch (ClassNotFoundException e) {\r
+ e.printStackTrace();\r
+ }\r
+ try {\r
+ con = DriverManager.getConnection(url, user, password);\r
+ } catch (SQLException e) {\r
+ e.printStackTrace();\r
+ throw new RuntimeException("Error connecting to MySQL: " + e.getMessage());\r
+ }\r
+ }\r
+}\r
--- /dev/null
+package net.rizon;\r
+\r
+import java.io.IOException;\r
+import java.net.InetAddress;\r
+import java.net.Socket;\r
+import java.net.UnknownHostException;\r
+import java.security.KeyManagementException;\r
+import java.security.NoSuchAlgorithmException;\r
+import java.security.Security;\r
+import java.security.cert.CertificateException;\r
+import java.security.cert.X509Certificate;\r
+\r
+import javax.net.ssl.SSLContext;\r
+import javax.net.ssl.SSLException;\r
+import javax.net.ssl.SSLSocket;\r
+import javax.net.ssl.SSLSocketFactory;\r
+import javax.net.ssl.TrustManager;\r
+import javax.net.ssl.X509TrustManager;\r
+\r
+/**\r
+ * An SSLSocketFactory implementation that treats all certificates as valid. Use with care.\r
+ */\r
+public class TrustingSSLSocketFactory extends SSLSocketFactory {\r
+\r
+ private SSLSocketFactory factory;\r
+ private String[] ciphers;\r
+\r
+ /**\r
+ * Create a new SSLSocketFactory factory that will create Sockets regardless of what certificate\r
+ * is used.\r
+ * @throws SSLException if it cannot initialize correctly.\r
+ */\r
+ public TrustingSSLSocketFactory() throws SSLException {\r
+ Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());\r
+ System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");\r
+ try {\r
+ SSLContext sslContext;\r
+ sslContext = SSLContext.getInstance("SSLv3");\r
+ sslContext.init(null, new TrustManager[] { new TrustingX509TrustManager() }, null);\r
+ factory = sslContext.getSocketFactory();\r
+ } catch (NoSuchAlgorithmException nsae) {\r
+ throw new SSLException("Unable to initialize the SSL context: ", nsae);\r
+ } catch (KeyManagementException kme) {\r
+ throw new SSLException("Unable to register a trust manager: ", kme);\r
+ }\r
+ ciphers = factory.getDefaultCipherSuites();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.ssl.SSLSocketFactory#getDefaultCipherSuites()\r
+ */\r
+ @Override\r
+ public String[] getDefaultCipherSuites() {\r
+ return ciphers;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.ssl.SSLSocketFactory#getSupportedCipherSuites()\r
+ */\r
+ @Override\r
+ public String[] getSupportedCipherSuites() {\r
+ return ciphers;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.SocketFactory#createSocket(java.lang.String, int)\r
+ */\r
+ @Override\r
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {\r
+ return prepare((SSLSocket)factory.createSocket(host, port));\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.SocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int)\r
+ */\r
+ @Override\r
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort)\r
+ throws IOException, UnknownHostException {\r
+ return prepare((SSLSocket)factory.createSocket(host, port, localHost, localPort));\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int)\r
+ */\r
+ @Override\r
+ public Socket createSocket(InetAddress address, int port) throws IOException {\r
+ return prepare((SSLSocket)factory.createSocket(address, port));\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int, java.net.InetAddress,\r
+ * int)\r
+ */\r
+ @Override\r
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress,\r
+ int localPort) throws IOException {\r
+ return prepare((SSLSocket)factory.createSocket(address, port, localAddress, localPort));\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.ssl.SSLSocketFactory#createSocket(java.net.Socket, java.lang.String, int,\r
+ * boolean)\r
+ */\r
+ @Override\r
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)\r
+ throws IOException {\r
+ return prepare((SSLSocket)factory.createSocket(s, host, port, autoClose));\r
+ }\r
+\r
+ /**\r
+ * Setup the socket with ciphers. Add code here to do things to all created Sockets.\r
+ * @param baseSocket\r
+ * @return <code>baseSocket</code> all set up.\r
+ */\r
+ private SSLSocket prepare(SSLSocket baseSocket) {\r
+ baseSocket.setEnabledCipherSuites(ciphers);\r
+ return baseSocket;\r
+ }\r
+\r
+ /**\r
+ * Trusts everyone. Very insecure.\r
+ */\r
+ private class TrustingX509TrustManager implements X509TrustManager {\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[],\r
+ * java.lang.String)\r
+ */\r
+ public void checkClientTrusted(X509Certificate[] arg0, String arg1)\r
+ throws CertificateException {\r
+ // no Exception implies acceptance\r
+ return;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[],\r
+ * java.lang.String)\r
+ */\r
+ public void checkServerTrusted(X509Certificate[] arg0, String arg1)\r
+ throws CertificateException {\r
+ // no Exception implies acceptance\r
+ return;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()\r
+ */\r
+ public X509Certificate[] getAcceptedIssuers() {\r
+ return new X509Certificate[0];\r
+ }\r
+\r
+ }\r
+}\r
--- /dev/null
+package net.rizon.bncbot;\r
+\r
+import java.io.FileInputStream;\r
+import java.io.FileNotFoundException;\r
+import java.io.IOException;\r
+import java.io.PrintWriter;\r
+import java.net.Socket;\r
+import java.text.DateFormat;\r
+import java.text.SimpleDateFormat;\r
+import java.util.Date;\r
+import java.util.Hashtable;\r
+import java.util.Properties;\r
+import java.util.Scanner;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import net.rizon.TrustingSSLSocketFactory;\r
+import net.rizon.DatabaseConnection;\r
+\r
+public class BncBot {\r
+ public static PrintWriter netOut;\r
+ public static String channel;\r
+ public static String adminChannel;\r
+ public static String whyChannel;\r
+\r
+ public static UserDB database;\r
+ public static Hashtable<String, String> whyLookup = new Hashtable<String, String>();\r
+ public static Hashtable<String, Date> regTimeLookup = new Hashtable<String, Date>();\r
+\r
+ public static final boolean debug = true;\r
+ public static boolean running = true;\r
+\r
+ public final static Properties config = new Properties();\r
+\r
+ // Entry point.\r
+ public static void main(String[] args) {\r
+ try {\r
+ FileInputStream fis = new FileInputStream("bncbot.properties");\r
+ config.load(fis);\r
+ fis.close();\r
+\r
+ DatabaseConnection.setProps(config);\r
+ botLoop();\r
+ } catch (FileNotFoundException e) {\r
+ System.out.println("Error loading config: " + e.getMessage());\r
+ e.printStackTrace();\r
+ } catch (IOException e) {\r
+ System.out.println("Error loading config: " + e.getMessage());\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ // Main bot loop.\r
+ public static void botLoop() {\r
+ try {\r
+ BncBot.database = new UserDB();\r
+\r
+ Socket socket = new TrustingSSLSocketFactory().createSocket(config.getProperty("server"), Integer.parseInt(config.getProperty("port")));\r
+ netOut = new PrintWriter(socket.getOutputStream(), true);\r
+\r
+ // Send stuff at beginning.\r
+ if (!config.getProperty("pass").equals(""))\r
+ BncBot.send("PASS :" + config.getProperty("pass"));\r
+ BncBot.send("NICK " + config.getProperty("nick"));\r
+ BncBot.send("USER " + config.getProperty("ident") + " 1 * :" + config.getProperty("gecos"));\r
+ if (!config.getProperty("nickservPass").equals(""))\r
+ BncBot.privmsg("NickServ", "IDENTIFY " + config.getProperty("nickservPass"));\r
+\r
+ // Set up database save loop.\r
+ Thread dbSaveDaemon = new DatabaseDaemon();\r
+ dbSaveDaemon.start();\r
+\r
+ // Set up stdin.\r
+ new StdinLoop().start();\r
+\r
+ // Set up main input loop.\r
+ Scanner scan = new Scanner(socket.getInputStream());\r
+ String line, words[], userlookup = null;\r
+ Pattern ccStripper = Pattern.compile("[\u0002\u001F\u000F]");\r
+ Pattern colorStripper = Pattern.compile(" \u0003[0-9]{1,2}(,[0-9]{1,2})?");\r
+ Pattern userIs = Pattern.compile("([A-Z0-9`_^|\\[\\]]*) is .*", Pattern.CASE_INSENSITIVE);\r
+ Pattern regPattern = Pattern.compile("Time registered: +(.*)$", Pattern.CASE_INSENSITIVE);\r
+ DateFormat dateParser = (DateFormat) new SimpleDateFormat("MMM dd hh:mm:ss yyyy z");\r
+\r
+ while (running && scan.hasNextLine()) {\r
+ line = scan.nextLine();\r
+ line = ccStripper.matcher(line).replaceAll("");\r
+ line = colorStripper.matcher(line).replaceAll("");\r
+\r
+ if (BncBot.debug)\r
+ System.out.println("<- " + line);\r
+\r
+ words = line.split("\\s+");\r
+\r
+ // Handle ping messages.\r
+ if (line.startsWith("PING ") && words.length > 1)\r
+ BncBot.send("PONG " + words[1]);\r
+\r
+ // Look for end of MOTD.\r
+ if (words[1].equals("376")) {\r
+ Thread.sleep(500); // Sleep to allow NickServ to process our IDENT.\r
+ BncBot.send("JOIN " + config.getProperty("adminChannel"));\r
+ BncBot.send("JOIN " + config.getProperty("whyChannel"));\r
+ }\r
+\r
+ // Look for INFO responses (requires special treatment.)\r
+ if (words[0].split("!")[0].equals(":NickServ")) {\r
+ String message = "";\r
+ for (int i = 3; i < words.length; i++)\r
+ message += ((i == 3) ? words[i].substring(1) : words[i]) + " ";\r
+ message = message.trim();\r
+\r
+ Matcher ui = userIs.matcher(message), rp = regPattern.matcher(message);\r
+ if (ui.matches() && ui.groupCount() == 1) {\r
+ userlookup = ui.group(1);\r
+ } else if (rp.matches() && rp.groupCount() == 1 && userlookup != null) {\r
+ synchronized (BncBot.regTimeLookup) {\r
+ BncBot.regTimeLookup.put(userlookup, dateParser.parse(rp.group(1)));\r
+ }\r
+ userlookup = null;\r
+ }\r
+ }\r
+\r
+ if (words[1].equals("PRIVMSG") || words[1].equals("NOTICE")) {\r
+ new MessageProc(words).start();\r
+ }\r
+ }\r
+\r
+ // Save database.\r
+ dbSaveDaemon.interrupt();\r
+ database.saveData(true);\r
+\r
+ // Send quit.\r
+ BncBot.send("QUIT :Ping timeout (9001 seconds)");\r
+ netOut.close();\r
+ socket.close();\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ // Split message into parts of 400 bytes.\r
+ public static void privmsg(String target, String msg) {\r
+ if (msg.length() > 400) {\r
+ for (int i = 400; i > 0; i--) {\r
+ if (msg.substring(i, i + 1).matches("\\s")) {\r
+ doPrivmsg(target, msg.substring(0, i));\r
+ privmsg(target, msg.substring(i));\r
+ return;\r
+ }\r
+ }\r
+ } else {\r
+ doPrivmsg(target, msg);\r
+ }\r
+ }\r
+\r
+ private static void doPrivmsg(String target, String line) {\r
+ BncBot.send("PRIVMSG " + target + " :" + line);\r
+ }\r
+\r
+ public static void notice(String target, String msg) {\r
+ if (msg.length() > 400) {\r
+ for (int i = 400; i > 0; i--) {\r
+ if (msg.substring(i, i + 1).matches("\\s")) {\r
+ doNotice(target, msg.substring(0, i));\r
+ privmsg(target, msg.substring(i));\r
+ return;\r
+ }\r
+ }\r
+ } else {\r
+ doNotice(target, msg);\r
+ }\r
+ }\r
+\r
+ private static void doNotice(String target, String line) {\r
+ BncBot.send("NOTICE " + target + " :" + line);\r
+ }\r
+\r
+ private static void send(String line) {\r
+ if (BncBot.debug)\r
+ System.out.println("-> " + line);\r
+ netOut.println(line);\r
+ }\r
+\r
+ private static class DatabaseDaemon extends Thread {\r
+ public void run() {\r
+ try {\r
+ Thread.sleep(300000); // Sleep for 5 minutes.\r
+ BncBot.database.saveData();\r
+ } catch (InterruptedException e) {\r
+ return; // Whatever.\r
+ }\r
+ }\r
+ }\r
+\r
+ private static class StdinLoop extends Thread {\r
+ public void run() {\r
+ Scanner scan = new Scanner(System.in);\r
+ String str;\r
+ while (((str = scan.nextLine()) != "") && running) {\r
+ send(str);\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+class MessageProc extends Thread {\r
+ private String[] tokens;\r
+ private String message;\r
+ private static final Pattern whyPattern = Pattern.compile("([A-Z0-9`_^|\\[\\]]*) has [A-Z]* access to #[A-Z]*. Reason: .*\\. Main nick: (.*)", Pattern.CASE_INSENSITIVE);\r
+\r
+ public MessageProc(String[] words) {\r
+ this.tokens = words.clone();\r
+\r
+ this.message = "";\r
+ for (int i = 3; i < tokens.length; i++)\r
+ this.message += ((i == 3) ? tokens[i].substring(1) : tokens[i]) + " ";\r
+ this.message = this.message.trim();\r
+ }\r
+\r
+ public void run() {\r
+ if (tokens[2].equalsIgnoreCase(BncBot.config.getProperty("adminChannel")) && tokens.length >= 5) { // Process messages from the admin channel.\r
+ if (tokens[3].equalsIgnoreCase(":.bncaccept")) {\r
+\r
+ } else if (tokens[3].equalsIgnoreCase(":.bncreject")) {\r
+\r
+ }\r
+ } else if (tokens[2].equalsIgnoreCase(BncBot.config.getProperty("adminChannel")) && tokens.length == 4) { // Process commands w/o args.\r
+ if (tokens[3].equalsIgnoreCase(":.shutdownbot")) {\r
+ BncBot.running = false;\r
+ }\r
+ } else if (tokens[0].substring(1).split("!")[0].equalsIgnoreCase("ChanServ")) { // Process WHY responses.\r
+ Matcher m = whyPattern.matcher(message);\r
+ if (m.matches() && m.groupCount() == 2) {\r
+ synchronized (BncBot.whyLookup) {\r
+ BncBot.whyLookup.put(m.group(1).trim(), m.group(2).trim());\r
+ }\r
+ }\r
+ } else if (tokens[1].equals("PRIVMSG") && tokens[3].equalsIgnoreCase(":request")) { // Process requests.\r
+ String user = tokens[0].substring(1);\r
+ String nick = user.split("!")[0];\r
+ String hostmask = user.split("!")[1];\r
+\r
+ String mainNick = lookupNick(nick);\r
+ Date regTime = lookupRegTime(mainNick);\r
+ long timeDifference = System.currentTimeMillis() - regTime.getTime();\r
+ int daysDifference = (int) (timeDifference / 86400000); // Divide by one day in millis.\r
+\r
+ BncBot.privmsg(nick, "Your main nick is: " + mainNick);\r
+ BncBot.privmsg(nick, "You registered on: " + regTime.toString());\r
+ BncBot.privmsg(nick, "You registered " + daysDifference + " ago.");\r
+ \r
+ if(daysDifference > 7) {\r
+ UserDB.UserEntry ue = BncBot.database.findUser(mainNick);\r
+ if(ue != null) {\r
+ if(ue.getApprovalState() == UserDB.UserEntry.ApprovalState.Approved) { \r
+ BncBot.privmsg(nick, String.format("You already have an approved BNC. It was approved by %s (%s) at %s.", ue.getApprovedByNick(),\r
+ ue.getApprovedByHostmask(), ue.getTimeApproved()));\r
+ } else if (ue.getApprovalState() == UserDB.UserEntry.ApprovalState.Rejected){\r
+ BncBot.privmsg(nick, String.format("Your previous BNC request has been rejected. The reason given was: %s", ue.getRejectionReason()));\r
+ BncBot.privmsg(nick, BncBot.config.getProperty("rejectionAppeal"));\r
+ } else {\r
+ BncBot.privmsg(nick, "You already have a pending BNC request. Be patient as a staff member reviews your request. You will receive a memo when your request has been processed.");\r
+ }\r
+ } else {\r
+ int id = BncBot.database.addUser(mainNick, hostmask);\r
+ if(id < 0) {\r
+ BncBot.privmsg(nick, "An error occurred while processing your request.");\r
+ } else {\r
+ BncBot.privmsg(nick, "Your request has been entered into the system and is now pending review by a staff member.");\r
+ BncBot.privmsg(nick, "Note that this process has no defined length, and may take anywhere from a couple hours to a couple days. You will receive a memo once your request has been processed.");\r
+ \r
+ BncBot.privmsg(BncBot.config.getProperty("adminChannel"), String.format("Nick '%s' (Main: %s; Hostmask: %s) has submitted a BNC request. The ID of this request is %s.", nick,\r
+ mainNick, hostmask, id));\r
+ BncBot.privmsg(BncBot.config.getProperty("adminChannel"), "To accept the request, type '.bncaccept' followed by either the main nick or request ID. To reject, type '.bncreject' followed by the main nick or ID, then a reason.");\r
+ \r
+ }\r
+ }\r
+ } else {\r
+ BncBot.privmsg(nick, "The main nick of your group must be registered for at least 7 days before requesting a BNC.");\r
+ }\r
+ }\r
+ }\r
+\r
+ private synchronized static String lookupNick(String nick) {\r
+ try {\r
+ BncBot.privmsg("ChanServ", "ACCESS " + BncBot.config.getProperty("whyChannel") + " ADD " + nick + " 3");\r
+ BncBot.privmsg("ChanServ", "WHY " + BncBot.config.getProperty("whyChannel") + " " + nick);\r
+ BncBot.privmsg("ChanServ", "ACCESS " + BncBot.config.getProperty("whyChannel") + " DEL " + nick);\r
+\r
+ long time = System.currentTimeMillis();\r
+ while (!BncBot.whyLookup.containsKey(nick) && ((time + 5000) > System.currentTimeMillis())) { // While we don't have a result. Time out after 5 seconds.\r
+ Thread.sleep(100);\r
+ }\r
+\r
+ synchronized (BncBot.whyLookup) {\r
+ return BncBot.whyLookup.remove(nick);\r
+ }\r
+ } catch (Exception e) {\r
+ return null;\r
+ }\r
+ }\r
+\r
+ private synchronized static Date lookupRegTime(String nick) {\r
+ try {\r
+ BncBot.privmsg("NickServ", "INFO " + nick);\r
+\r
+ long time = System.currentTimeMillis();\r
+ while (!BncBot.regTimeLookup.containsKey(nick) && ((time + 5000) > System.currentTimeMillis())) { // While we don't have a result. Time out after 5 seconds.\r
+ Thread.sleep(100);\r
+ }\r
+\r
+ synchronized (BncBot.regTimeLookup) {\r
+ return BncBot.regTimeLookup.remove(nick);\r
+ }\r
+ } catch (Exception e) {\r
+ return null;\r
+ }\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+package net.rizon.bncbot;\r
+\r
+import java.sql.Connection;\r
+import java.sql.PreparedStatement;\r
+import java.sql.ResultSet;\r
+import java.sql.SQLException;\r
+import java.sql.Statement;\r
+import java.sql.Timestamp;\r
+import java.util.Date;\r
+import java.util.Enumeration;\r
+import java.util.Hashtable;\r
+\r
+import net.rizon.DatabaseConnection;\r
+\r
+public class UserDB {\r
+ private Hashtable<Integer, UserEntry> entries = new Hashtable<Integer, UserEntry>();\r
+\r
+ public UserDB() {\r
+ Connection conn = DatabaseConnection.getConnection();\r
+\r
+ // Try to load from the DB.\r
+ try {\r
+ PreparedStatement ps = conn.prepareStatement("SELECT * FROM `bncbot_users`;");\r
+ ResultSet rs = ps.executeQuery();\r
+\r
+ while (rs.next()) {\r
+ entries.put(rs.getInt("bnc_user_id"), UserEntry.fromResultSet(rs));\r
+ }\r
+\r
+ rs.close();\r
+ ps.close();\r
+ } catch (SQLException e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ public int addUser(String nick, String hostmask) {\r
+ Connection conn = DatabaseConnection.getConnection();\r
+\r
+ try {\r
+ PreparedStatement ps = conn.prepareStatement("INSERT INTO `bncbot_users` (`bnc_user_nick`,`bnc_user_hostmask`) VALUES (?,?);", Statement.RETURN_GENERATED_KEYS);\r
+\r
+ ps.setString(1, nick);\r
+ ps.setString(2, hostmask);\r
+ ps.execute();\r
+\r
+ ResultSet rs = ps.getGeneratedKeys();\r
+ if (!rs.next()) {\r
+ System.out.println("Error adding user: MySQL did not return a AUTO_INCREMENT key.");\r
+ return -1;\r
+ }\r
+\r
+ synchronized (this.entries) {\r
+ this.entries.put(rs.getInt(1), new UserEntry(rs.getInt(1), nick, hostmask));\r
+ }\r
+\r
+ return rs.getInt(1);\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ return -1;\r
+ }\r
+ }\r
+\r
+ public UserEntry findUser(String nick) {\r
+ synchronized (this.entries) {\r
+ for (Enumeration<Integer> iter = this.entries.keys(); iter.hasMoreElements();) {\r
+ Integer key = iter.nextElement();\r
+ UserEntry ue = this.entries.get(key);\r
+\r
+ if (ue.getNick().equalsIgnoreCase(nick)) {\r
+ return ue;\r
+ }\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ public UserEntry findUser(int userID) {\r
+ synchronized (this.entries) {\r
+ for (Enumeration<Integer> iter = this.entries.keys(); iter.hasMoreElements();) {\r
+ Integer key = iter.nextElement();\r
+\r
+ if (key == userID) {\r
+ return this.entries.get(key);\r
+ }\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ public boolean approveUser(String nick, String approvalUser) {\r
+ return approveUser(findUser(nick), approvalUser);\r
+ }\r
+\r
+ public boolean approveUser(int userID, String approvalUser) {\r
+ return approveUser(findUser(userID), approvalUser);\r
+ }\r
+\r
+ public boolean approveUser(UserEntry ue, String aUser) {\r
+ if (ue == null)\r
+ return false;\r
+\r
+ ue.approve(aUser);\r
+ return true;\r
+ }\r
+\r
+ public boolean rejectUser(String nick, String rejectionUser, String rejectionReason) {\r
+ return rejectUser(findUser(nick), rejectionUser, rejectionReason);\r
+ }\r
+\r
+ public boolean rejectUser(int userID, String rejectionUser, String rejectionReason) {\r
+ return rejectUser(findUser(userID), rejectionUser, rejectionReason);\r
+ }\r
+\r
+ public boolean rejectUser(UserEntry ue, String rUser, String rReason) {\r
+ if (ue == null)\r
+ return false;\r
+\r
+ ue.reject(rUser, rReason);\r
+ return true;\r
+ }\r
+\r
+ public void saveData(boolean now) {\r
+ String query = "REPLACE " + (now ? "" : "DELAYED ") + "INTO `bncbot_users` VALUES (?,?,?,?,?,?,?,?,?);";\r
+\r
+ Connection conn = DatabaseConnection.getConnection();\r
+ try {\r
+ PreparedStatement ps = conn.prepareStatement(query);\r
+\r
+ synchronized (this.entries) {\r
+ for (Enumeration<Integer> iter = this.entries.keys(); iter.hasMoreElements();) {\r
+ Integer key = iter.nextElement();\r
+ UserEntry ue = this.entries.get(key);\r
+\r
+ if (ue.isModified()) {\r
+ ps.setInt(1, key);\r
+ ps.setString(2, ue.getNick());\r
+ ps.setString(3, ue.getHostmask());\r
+ ps.setTimestamp(4, new Timestamp(ue.getTimeRequested().getTime()));\r
+ ps.setInt(5, ue.getApprovalState().getAsInt());\r
+ ps.setString(6, ue.getApprovedByNick());\r
+ ps.setString(7, ue.getApprovedByHostmask());\r
+ ps.setTimestamp(8, (ue.getApprovalState() == UserEntry.ApprovalState.Approved) ? new Timestamp(ue.getTimeApproved().getTime()) : null);\r
+ ps.setString(9, ue.getRejectionReason());\r
+\r
+ ps.execute();\r
+ ue.setNotModified();\r
+ }\r
+ }\r
+ }\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+\r
+ public void saveData() {\r
+ saveData(false);\r
+ }\r
+\r
+ public static class UserEntry {\r
+ private int id;\r
+ private String nick;\r
+ private String hostmask;\r
+ private Date timeRequested;\r
+\r
+ private ApprovalState approved;\r
+ private String approvedBy;\r
+ private String approvedByHostmask;\r
+ private Date approvedTime;\r
+\r
+ private String rejectedReason;\r
+\r
+ // Flag to tell if the entry is modified\r
+ // and must be flushed to DB.\r
+ private boolean modified;\r
+\r
+ public UserEntry(int id, String nick, String hostmask, Date timeRequested) {\r
+ this.id = id;\r
+ this.nick = nick;\r
+ this.hostmask = hostmask;\r
+ this.timeRequested = timeRequested;\r
+\r
+ this.approved = ApprovalState.NotProcessed;\r
+ this.approvedBy = null;\r
+ this.approvedByHostmask = null;\r
+ this.approvedTime = null;\r
+\r
+ this.rejectedReason = null;\r
+\r
+ this.modified = true;\r
+ }\r
+\r
+ public UserEntry(int id, String nick, String hostmask) {\r
+ this(id, nick, hostmask, new Date());\r
+ }\r
+\r
+ public int getId() {\r
+ return this.id;\r
+ }\r
+\r
+ public String getNick() {\r
+ return this.nick;\r
+ }\r
+\r
+ public String getHostmask() {\r
+ return this.hostmask;\r
+ }\r
+\r
+ public Date getTimeRequested() {\r
+ return this.timeRequested;\r
+ }\r
+\r
+ public ApprovalState getApprovalState() {\r
+ return this.approved;\r
+ }\r
+\r
+ public String getApprovedByNick() {\r
+ return (this.approved == ApprovalState.Approved) ? this.approvedBy : null;\r
+ }\r
+\r
+ public String getApprovedByHostmask() {\r
+ return (this.approved == ApprovalState.Approved) ? this.approvedByHostmask : null;\r
+ }\r
+\r
+ public Date getTimeApproved() {\r
+ return (this.approved == ApprovalState.Approved) ? this.approvedTime : null;\r
+ }\r
+\r
+ public String getRejectionReason() {\r
+ return (this.approved == ApprovalState.Rejected) ? this.rejectedReason : null;\r
+ }\r
+\r
+ public void approve(String user) {\r
+ this.approved = ApprovalState.Approved;\r
+ this.approvedBy = user.split("!")[0];\r
+ this.approvedByHostmask = user;\r
+ this.approvedTime = new Date();\r
+\r
+ this.modified = true;\r
+ }\r
+\r
+ public void reject(String user, String reason) {\r
+ this.approved = ApprovalState.Rejected;\r
+ this.approvedBy = user.split("!")[0];\r
+ this.approvedByHostmask = user;\r
+ this.approvedTime = new Date();\r
+ this.rejectedReason = reason;\r
+\r
+ this.modified = true;\r
+ }\r
+\r
+ public boolean isModified() {\r
+ return this.modified;\r
+ }\r
+\r
+ public void setNotModified() {\r
+ this.modified = false;\r
+ }\r
+\r
+ public static UserEntry fromResultSet(ResultSet rs) {\r
+ try {\r
+ UserEntry ue = new UserEntry(rs.getInt("bnc_user_id"), rs.getString("bnc_user_nick"), rs.getString("bnc_user_hostmask"), new Date(rs.getTimestamp("bnc_time_requested").getTime()));\r
+\r
+ ue.approved = ApprovalState.valueOf(rs.getInt("bnc_approved"));\r
+ ue.approvedBy = rs.getString("bnc_approved_by");\r
+ ue.approvedByHostmask = rs.getString("bnc_approved_by_hostmask");\r
+ ue.approvedTime = new Date(rs.getTimestamp("bnc_approved_time").getTime());\r
+\r
+ ue.rejectedReason = rs.getString("bnc_rejected_reason");\r
+\r
+ ue.modified = false;\r
+\r
+ return ue;\r
+ } catch (Exception e) {\r
+ return null;\r
+ }\r
+ }\r
+\r
+ public enum ApprovalState {\r
+ NotProcessed {\r
+ public int getAsInt() {\r
+ return 0;\r
+ }\r
+ },\r
+ Approved {\r
+ public int getAsInt() {\r
+ return 1;\r
+ }\r
+ },\r
+ Rejected {\r
+ public int getAsInt() {\r
+ return 2;\r
+ }\r
+ };\r
+\r
+ public abstract int getAsInt();\r
+\r
+ public static ApprovalState valueOf(int value) {\r
+ for (ApprovalState as : ApprovalState.values()) {\r
+ if (as.getAsInt() == value)\r
+ return as;\r
+ }\r
+ throw new AssertionError("Invalid approval state.");\r
+ }\r
+ }\r
+ }\r
+}\r