Initial Import.
authorFelix Dörre <felix@dogcraft.de>
Sat, 21 Nov 2015 11:09:05 +0000 (12:09 +0100)
committerFelix Dörre <felix@dogcraft.de>
Sat, 21 Nov 2015 11:29:31 +0000 (12:29 +0100)
.classpath [new file with mode: 0644]
.gitignore [new file with mode: 0755]
.project [new file with mode: 0755]
README [new file with mode: 0755]
src/org/cacert/votebot/CAcertVoteAuditor.java [new file with mode: 0755]
src/org/cacert/votebot/CAcertVoteBot.java [new file with mode: 0755]
src/org/cacert/votebot/CAcertVoteMechanics.java [new file with mode: 0755]
src/org/cacert/votebot/IRCBot.java [new file with mode: 0755]
src/org/cacert/votebot/IRCClient.java [new file with mode: 0755]

diff --git a/.classpath b/.classpath
new file mode 100644 (file)
index 0000000..91ee9a5
--- /dev/null
@@ -0,0 +1,6 @@
+<?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/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>\r
+       <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
diff --git a/.gitignore b/.gitignore
new file mode 100755 (executable)
index 0000000..bd3461c
--- /dev/null
@@ -0,0 +1,8 @@
+XMPPSvnDaemon.jar
+.first
+config.properties
+developers.properties
+services.properties
+/bin
+/plugins_bin
+infiniteStory
diff --git a/.project b/.project
new file mode 100755 (executable)
index 0000000..904d37d
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>cacert-votebot</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
diff --git a/README b/README
new file mode 100755 (executable)
index 0000000..bdfd87b
--- /dev/null
+++ b/README
@@ -0,0 +1,15 @@
+java -cp VoteBot.jar de.dogcraft.irc.CAcertVoteBot -u -h irc.cacert.org -p 13700 --nick VoteBot
+
+This source code can be used as VoteAuditor:
+java -Dauditor.target.nick=VoteBot -cp VoteBot.jar de.dogcraft.irc.CAcertVoteAuditor -u -h irc.cacert.org -p 13700 --nick VoteBotAuditor
+
+You can connect with ssl by removing "-u"
+Target channels can be changed with
+-Dauditor.target.voteChn=vote
+or:
+-DvoteBot.voteChn=vote
+-DvoteBot.meetingChn=agm
+
+Timeouts can be changed with:
+-DvoteBot.warnSecs=90
+-DvoteBot.timeoutSecs=120
diff --git a/src/org/cacert/votebot/CAcertVoteAuditor.java b/src/org/cacert/votebot/CAcertVoteAuditor.java
new file mode 100755 (executable)
index 0000000..9e1a564
--- /dev/null
@@ -0,0 +1,99 @@
+package org.cacert.votebot;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.cacert.votebot.CAcertVoteMechanics.VoteType;
+
+public class CAcertVoteAuditor extends IRCBot {
+       public CAcertVoteAuditor(IRCClient c, String toAudit) {
+               super(c);
+               this.toAudit = toAudit;
+               c.join(voteAuxChn);
+       }
+
+       String voteAuxChn = System.getProperty("auditor.target.voteChn", "vote");
+       String toAudit;
+
+       long warn = Long.parseLong(System.getProperty("voteBot.warnSecs", "90"));
+       long timeout = Long.parseLong(System.getProperty("voteBot.timeoutSecs",
+                       "120"));
+
+       CAcertVoteMechanics mech = new CAcertVoteMechanics();
+
+       String[] capturedResults = new String[VoteType.values().length];
+       int ctr = -1;
+
+       @Override
+       public synchronized void publicMessage(String from, String channel,
+                       String message) {
+               if (channel.equals(voteAuxChn)) {
+                       if (from.equals(toAudit)) {
+                               if (ctr >= 0) {
+                                       capturedResults[ctr++] = message;
+                                       if (ctr == capturedResults.length) {
+                                               String[] reals = mech.closeVote();
+                                               if (Arrays.equals(reals, capturedResults)) {
+                                                       System.out
+                                                                       .println("Audit for vote was successful.");
+                                               } else {
+                                                       System.out
+                                                                       .println("Audit failed! Vote Bot (or Auditor) is probably broken.");
+                                               }
+                                               ctr = -1;
+                                       }
+                                       return;
+                               }
+                               if (message.startsWith("New Vote: ")) {
+                                       System.out.println("detected vote-start");
+                                       Pattern p = Pattern
+                                                       .compile("New Vote: (.*) has started a vote on \"(.*)\"");
+                                       Matcher m = p.matcher(message);
+                                       if (!m.matches()) {
+                                               System.out.println("error: vote-start malformed");
+                                               return;
+                                       }
+                                       mech.callVote(m.group(1), m.group(2));
+                               } else if (message.startsWith("Results: ")) {
+                                       System.out.println("detected vote-end. Reading results");
+                                       ctr = 0;
+                               }
+                       } else {
+                               if (ctr != -1) {
+                                       System.out.println("Vote after end.");
+                                       return;
+                               }
+                               System.out.println("detected vote");
+                               mech.evaluateVote(from, message);
+                               System.out.println("Current state: " + mech.getCurrentResult());
+                       }
+               }
+       }
+       @Override
+       public synchronized void privateMessage(String from, String message) {
+
+       }
+       @Override
+       public synchronized void join(String cleanReferent, String chn) {
+
+       }
+
+       @Override
+       public synchronized void part(String cleanReferent, String chn) {
+
+       }
+
+       public static void main(String[] args) throws IOException,
+                       InterruptedException {
+               IRCClient ic = IRCClient.parseCommandLine(args, "dogcraft_de-Auditor");
+               String targetNick = System.getProperty("auditor.target.nick");
+               if (targetNick == null) {
+                       System.out
+                                       .println("use -Dauditor.target.nick=TargetNick to set a target nick");
+                       System.exit(0);
+               }
+               ic.setBot(new CAcertVoteAuditor(ic, targetNick));
+       }
+}
diff --git a/src/org/cacert/votebot/CAcertVoteBot.java b/src/org/cacert/votebot/CAcertVoteBot.java
new file mode 100755 (executable)
index 0000000..7a66eae
--- /dev/null
@@ -0,0 +1,86 @@
+package org.cacert.votebot;
+
+import java.io.IOException;
+
+import org.cacert.votebot.CAcertVoteMechanics.State;
+
+public class CAcertVoteBot extends IRCBot implements Runnable {
+       public CAcertVoteBot(IRCClient c) {
+               super(c);
+               c.join(meetingChn);
+               c.join(voteAuxChn);
+               new Thread(this).start();
+       }
+
+       String meetingChn = System.getProperty("voteBot.meetingChn", "agm");
+       String voteAuxChn = System.getProperty("voteBot.voteChn", "vote");
+
+       long warn = Long.parseLong(System.getProperty("voteBot.warnSecs", "90"));
+       long timeout = Long.parseLong(System.getProperty("voteBot.timeoutSecs",
+                       "120"));
+
+       CAcertVoteMechanics mech = new CAcertVoteMechanics();
+
+       @Override
+       public synchronized void publicMessage(String from, String channel,
+                       String message) {
+               if (channel.equals(voteAuxChn)) {
+                       sendPublicMessage(voteAuxChn, mech.evaluateVote(from, message));
+               }
+       }
+       @Override
+       public synchronized void privateMessage(String from, String message) {
+               if (message.startsWith("vote ")) {
+                       String response = mech.callVote(from, message.substring(5));
+                       sendPrivateMessage(from, response);
+                       if (response.startsWith("Sorry,")) {
+                               return;
+                       }
+                       anounce("New Vote: " + from + " has started a vote on \""
+                                       + mech.getTopic() + "\"");
+                       sendPublicMessage(meetingChn, "Please cast your vote in #vote");
+                       sendPublicMessage(voteAuxChn, "Please cast your vote in the next "
+                                       + timeout + " seconds.");
+               }
+       }
+       private synchronized void anounce(String msg) {
+               sendPublicMessage(meetingChn, msg);
+               sendPublicMessage(voteAuxChn, msg);
+       }
+       public void run() {
+               try {
+                       while (true) {
+                               while (mech.getState() == State.IDLE) {
+                                       Thread.sleep(1000);
+                               }
+                               Thread.sleep(warn * 1000);
+                               anounce("Voting on " + mech.getTopic() + " will end in "
+                                               + (timeout - warn) + " seconds.");
+                               Thread.sleep((timeout - warn) * 1000);
+                               anounce("Voting on " + mech.getTopic() + " has closed.");
+                               String[] res = mech.closeVote();
+                               anounce("Results: for " + mech.getTopic() + ":");
+                               for (int i = 0; i < res.length; i++) {
+                                       anounce(res[i]);
+                               }
+                       }
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       @Override
+       public synchronized void join(String cleanReferent, String chn) {
+
+       }
+
+       @Override
+       public synchronized void part(String cleanReferent, String chn) {
+
+       }
+       public static void main(String[] args) throws IOException,
+                       InterruptedException {
+               IRCClient ic = IRCClient.parseCommandLine(args, "dogcraft_de");
+               ic.setBot(new CAcertVoteBot(ic));
+       }
+}
diff --git a/src/org/cacert/votebot/CAcertVoteMechanics.java b/src/org/cacert/votebot/CAcertVoteMechanics.java
new file mode 100755 (executable)
index 0000000..926dd85
--- /dev/null
@@ -0,0 +1,153 @@
+package org.cacert.votebot;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Reprenents the voting-automata for voting in IRC chanenls.
+ *
+ */
+public class CAcertVoteMechanics {
+       public enum VoteType {
+               AYE, NAYE, ABSTAIN
+       }
+       public enum State {
+               RUNNING, IDLE
+       }
+       State state = State.IDLE;
+
+       String voteCaller;
+       String topic;
+
+       private String vote(String voter, String actor, VoteType type) {
+               votes.put(voter, type);
+               if (voter.equals(actor)) {
+                       return "Thanks " + actor + " I count your vote as " + type;
+               } else {
+                       return "Thanks " + actor + " I count your vote for " + voter
+                                       + " as " + type;
+               }
+       }
+       HashMap<String, VoteType> votes = new HashMap<>();
+
+       private String voteError(String actor) {
+               return "Sorry "
+                               + actor
+                               + ", I did not understand your vote, your current vote state remains unchanged!";
+       }
+
+       /**
+        * Adds a vote to the current topic. This interprets proxies.
+        * 
+        * @param actor
+        *            the person that sent this vote
+        * @param txt
+        *            the text that the person sent.
+        * @return A message to
+        * 
+        *         <pre>
+        * actor
+        * </pre>
+        * 
+        *         indicating tha result of his action.
+        */
+       public synchronized String evaluateVote(String actor, String txt) {
+               if (state != State.RUNNING) {
+                       return "Sorry " + actor + ", but currently no vote is running.";
+               }
+
+               String voter = actor;
+               String value = null;
+               if (txt.toLowerCase().matches("^\\s*proxy\\s.*")) {
+                       String[] parts = txt.split("\\s+");
+                       if (parts.length == 3) {
+                               voter = parts[1];
+                               value = parts[2];
+                       }
+               } else {
+                       value = txt.replaceAll("^\\s*|\\s*$", "");
+               }
+               if (value == null) {
+                       return voteError(actor);
+               } else {
+                       value = value.toLowerCase();
+                       switch (value) {
+                               case "aye" :
+                               case "yes" :
+                               case "oui" :
+                               case "ja" : {
+                                       return vote(voter, actor, VoteType.AYE);
+                               }
+                               case "naye" :
+                               case "nay" :
+                               case "no" :
+                               case "non" :
+                               case "nein" : {
+                                       return vote(voter, actor, VoteType.NAYE);
+                               }
+                               case "abstain" :
+                               case "enthaltung" :
+                               case "enthalten" :
+                               case "abs" : {
+                                       return vote(voter, actor, VoteType.ABSTAIN);
+                               }
+                       }
+               }
+               return voteError(actor);
+       }
+       /**
+        * A new vote begins.
+        * 
+        * @param from
+        *            the nick that called the vote
+        * @param topic
+        *            the topic of the vote
+        * @return A response to
+        * 
+        *         <pre>
+        * from
+        * </pre>
+        * 
+        *         indicating success or failure.
+        */
+       public synchronized String callVote(String from, String topic) {
+               if (state != State.IDLE) {
+                       return "Sorry, a vote is already running";
+               }
+               voteCaller = from;
+               this.topic = topic;
+               votes.clear();
+
+               state = State.RUNNING;
+               return "Vote started.";
+       }
+       /**
+        * Ends a vote.
+        * 
+        * @return An array of Strings containing result status messages.
+        */
+       public synchronized String[] closeVote() {
+               int[] res = new int[VoteType.values().length];
+               for (Entry<String, VoteType> i : votes.entrySet()) {
+                       res[i.getValue().ordinal()]++;
+               }
+               String[] results = new String[VoteType.values().length];
+               for (int i = 0; i < res.length; i++) {
+                       results[i] = (VoteType.values()[i] + ": " + res[i]);
+               }
+               votes.clear();
+               voteCaller = null;
+               state = State.IDLE;
+               return results;
+       }
+       public String getTopic() {
+               return topic;
+       }
+       public State getState() {
+               return state;
+       }
+       public String getCurrentResult() {
+               return votes.toString();
+       }
+
+}
diff --git a/src/org/cacert/votebot/IRCBot.java b/src/org/cacert/votebot/IRCBot.java
new file mode 100755 (executable)
index 0000000..d5387c5
--- /dev/null
@@ -0,0 +1,21 @@
+package org.cacert.votebot;
+
+public abstract class IRCBot {
+       private IRCClient c;
+
+       public IRCBot(IRCClient c) {
+               this.c = c;
+       }
+       public abstract void publicMessage(String from, String channel,
+                       String message);
+       public abstract void privateMessage(String from, String message);
+
+       public void sendPublicMessage(String channel, String message) {
+               c.send(message, channel);
+       }
+       public void sendPrivateMessage(String to, String message) {
+               c.sendPrivate(message, to);
+       }
+       public abstract void part(String cleanReferent, String chn);
+       public abstract void join(String cleanReferent, String chn);
+}
diff --git a/src/org/cacert/votebot/IRCClient.java b/src/org/cacert/votebot/IRCClient.java
new file mode 100755 (executable)
index 0000000..3206fc4
--- /dev/null
@@ -0,0 +1,222 @@
+package org.cacert.votebot;
+
+import java.io.BufferedReader;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.Semaphore;
+
+import javax.net.ssl.SSLSocketFactory;
+
+public class IRCClient {
+       private Semaphore loggedin = new Semaphore(0);
+       private PrintWriter out;
+       private Socket s;
+       private String nick;
+       class ServerReader implements Runnable {
+               private BufferedReader br;
+
+               public ServerReader(BufferedReader br) {
+                       this.br = br;
+                       new Thread(this).start();
+               }
+               @Override
+               public void run() {
+                       String l;
+                       try {
+                               while ((l = br.readLine()) != null) {
+                                       String fullline = l;
+                                       //System.out.println(l);
+                                       if (l.startsWith("PING ")) {
+                                               System.out.println("PONG");
+                                               out.println("PONG " + l.substring(5));
+                                       }
+                                       String referent = "";
+                                       if (l.startsWith(":")) {
+                                               String[] parts = l.split(" ", 2);
+                                               referent = parts[0];
+                                               l = parts[1];
+                                       }
+                                       String[] command = l.split(" ", 3);
+                                       if (command[0].equals("001")) {
+                                               loggedin.release();
+                                       }
+                                       if (command[0].equals("PRIVMSG")) {
+                                               String msg = command[2].substring(1);
+                                               String chnl = command[1];
+                                               if (!chnl.startsWith("#")) {
+                                                       handlePrivMsg(referent, msg);
+                                               } else {
+                                                       handleMsg(referent, chnl, msg);
+                                               }
+                                               log(chnl, fullline);
+                                       } else if (command[0].equals("JOIN")) {
+                                               String chn = command[1].substring(1);
+                                               targetBot.join(cleanReferent(referent),
+                                                               chn.substring(1));
+                                               log(chn, fullline);
+                                       } else if (command[0].equals("PART")) {
+                                               String chn = command[1];
+                                               targetBot.part(cleanReferent(referent), chn);
+                                               log(chn, fullline);
+                                       } else {
+                                               System.out.println("unknown line: ");
+                                               System.out.println(l);
+                                       }
+                               }
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                       }
+
+               }
+               private String cleanReferent(String referent) {
+                       String[] parts = referent.split("!");
+                       if (!parts[0].startsWith(":")) {
+                               System.err.println("invalid public message");
+                               return "unknown";
+                       }
+
+                       return parts[0];
+               }
+               HashMap<String, PrintWriter> logs = new HashMap<String, PrintWriter>();
+
+               private void log(String chn, String l) throws IOException {
+                       if (true) {
+                               return;
+                       }
+                       PrintWriter log = logs.get(chn);
+                       if (log == null) {
+                               log = new PrintWriter(new FileOutputStream("irc/log_" + chn),
+                                               true);
+                               logs.put(chn, log);
+                       }
+                       log.println(l);
+               }
+
+               private void handlePrivMsg(String referent, String msg) {
+                       String[] parts = referent.split("!");
+                       if (!parts[0].startsWith(":")) {
+                               System.err.println("invalid private message");
+                               return;
+                       }
+                       if (targetBot == null) {
+                               System.out.println("dropping message");
+                               return;
+                       }
+                       targetBot.privateMessage(parts[0].substring(1), msg);
+               }
+
+               private void handleMsg(String referent, String chnl, String msg) {
+                       String[] parts = referent.split("!");
+                       if (!parts[0].startsWith(":")) {
+                               System.err.println("invalid public message");
+                               return;
+                       }
+                       if (targetBot == null) {
+                               System.out.println("dropping message");
+                               return;
+                       }
+                       if (!chnl.startsWith("#")) {
+                               System.err.println("invalid public message (chnl)");
+                               return;
+
+                       }
+
+                       targetBot.publicMessage(parts[0].substring(1), chnl.substring(1),
+                                       msg);
+               }
+       }
+       public IRCClient(String nick, String server, int port, boolean ssl)
+                       throws IOException, InterruptedException {
+               this.nick = nick;
+               if (!nick.matches("[a-zA-Z0-9_-]+")) {
+                       throw new Error("malformed");
+               }
+
+               if (ssl) {
+                       s = SSLSocketFactory.getDefault().createSocket(server, port);//default-ssl = 7000
+               } else {
+                       s = new Socket(server, port);// default-plain= 6667
+               }
+               out = new PrintWriter(s.getOutputStream(), true);
+               BufferedReader in = new BufferedReader(new InputStreamReader(
+                               s.getInputStream()));
+               new ServerReader(in);
+               out.println("USER " + nick + " 0 * :unknown");
+               out.println("NICK " + nick);
+               loggedin.acquire();
+
+       }
+       HashSet<String> joined = new HashSet<String>();
+       private IRCBot targetBot;
+       public void join(String channel) {
+               if (!channel.matches("[a-zA-Z0-9_-]+")) {
+                       return;
+               }
+               if (joined.add(channel)) {
+                       out.println("JOIN #" + channel);
+               }
+       }
+       public void leave(String channel) {
+               if (!channel.matches("[a-zA-Z0-9_-]+")) {
+                       return;
+               }
+               if (joined.remove(channel)) {
+                       out.println("PART #" + channel);
+               }
+       }
+
+       public void send(String msg, String channel) {
+               if (!channel.matches("[a-zA-Z0-9_-]+")) {
+                       return;
+               }
+               out.println("PRIVMSG #" + channel + " :" + msg);
+       }
+       public void sendPrivate(String msg, String to) {
+               if (!to.matches("[a-zA-Z0-9_-]+")) {
+                       return;
+               }
+               out.println("PRIVMSG " + to + " :" + msg);
+       }
+
+       public void setBot(IRCBot bot) {
+               this.targetBot = bot;
+
+       }
+       public static IRCClient parseCommandLine(String[] commandLine, String nick)
+                       throws IOException, InterruptedException {
+               String host = null;
+               int port = 7000;
+               boolean ssl = true;
+               for (int i = 0; i < commandLine.length; i++) {
+                       String cmd = commandLine[i];
+                       if (cmd.equals("--no-ssl") || cmd.equals("-u")) {
+                               ssl = false;
+                       } else if (cmd.equals("--ssl") || cmd.equals("-s")) {
+                               ssl = true;
+                       } else if (cmd.equals("--host") || cmd.equals("-h")) {
+                               host = commandLine[++i];
+                       } else if (cmd.equals("--port") || cmd.equals("-p")) {
+                               port = Integer.parseInt(commandLine[++i]);
+                       } else if (cmd.equals("--nick") || cmd.equals("-n")) {
+                               nick = commandLine[++i];
+                       } else if (cmd.equals("--help") || cmd.equals("-h")) {
+                               System.out
+                                               .println("Options: [--no-ssl|-u|--ssl|-s|[--host|-h] <host>|[--port|-p] <port>|[--nick|-n] <nick>]*");
+                               System.out
+                                               .println("Requires the -host argument, --ssl is default, last argument of a kind is significant.");
+                               throw new Error("Operation caneled");
+                       } else {
+                               throw new Error("Invalid option (usage with --help): " + cmd);
+                       }
+               }
+               if (host == null) {
+                       throw new Error("--host <host> is missing");
+               }
+               return new IRCClient(nick, host, port, ssl);
+       }
+}