diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..fc206c2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+raven.mono: /usr/bin/mcs /usr/bin/mono clean ./raven.csharp ./connection.csharp ./ravencommand.csharp /bin/bash /usr/bin/mail /usr/bin/wget
+ mkdir -p /usr/local/etc/TheRaven
+ mcs -out:raven.mono reportmessage.csharp *exception.csharp irc*message.csharp connection.csharp raven*.csharp
+ id raven || useradd -M -G bzr,ircd,api raven
+ id raven || usermod -d /usr/local/etc/TheRaven raven
+ chown raven:raven /usr/local/etc/TheRaven
+
+clean:
+ if [ "$$(ls ./*~ 2>/dev/null | wc -l)" -gt 0 ]; then rm -Rf *~; fi
+ if [ "$$(ls ./*.mono 2>/dev/null | wc -l)" -gt 0 ]; then rm -Rf *.mono; fi
+ if [ "$$(ls ./\#* 2>/dev/null | wc -l)" -gt 0 ]; then rm -Rf \#*; fi
+ if [ -f raven.mono ]; then rm raven.mono; fi
+
+edit:
+ emacs -nw raven.csharp
+
+test: raven.mono
+ su raven -c 'script -c "mono ./raven.mono -c /usr/local/etc/TheRaven-Test -v" /tmp/raven-test.log'
+
+check-for-verbosity:
+ grep Console.WriteLine *.csharp | egrep -v 'verbosity|raven.csharp'; echo
+
+install: raven.mono
+ cp raven.mono /opt/raven.mono
+ [ ! -d /usr/local/etc/TheRaven ] || mkdir -p /usr/local/etc/TheRaven
+ chown -R raven:raven /opt/raven.mono /usr/local/etc/TheRaven*
+ chmod 0600 /opt/raven.mono /usr/local/etc/TheRaven*/*
+ chmod 0700 /usr/local/etc/TheRaven*
+ cp ./raven.service /usr/lib/systemd/system/raven.service
+ /usr/bin/bash make-conf-dir.bash /usr/local/etc/TheRaven
+ systemctl daemon-reload
+ systemctl enable raven
+
+commit: /usr/bin/bzr
+ bzr commit
+ chown -R raven:bzr .bzr
diff --git a/alreadyidentifiedexception.csharp b/alreadyidentifiedexception.csharp
new file mode 100644
index 0000000..346764a
--- /dev/null
+++ b/alreadyidentifiedexception.csharp
@@ -0,0 +1,13 @@
+using System;
+
+namespace AniNIX.TheRaven {
+ public class AlreadyIdentifiedException : System.Exception {
+
+ ///
+ /// Create a new AlreadyIdentifiedException to identify this event
+ ///
+ public AlreadyIdentifiedException(String message) : base(message) { }
+ public AlreadyIdentifiedException() : base(null) { }
+
+ }
+}
diff --git a/connection.csharp b/connection.csharp
new file mode 100644
index 0000000..a80e6ab
--- /dev/null
+++ b/connection.csharp
@@ -0,0 +1,177 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+namespace AniNIX.TheRaven {
+
+ public class Connection : IDisposable {
+
+ private const int _ircReadTimeout = 200000; // We set this to the IRC mimimum of two minutes in microseconds
+
+ //These privates will be the socket we use.
+ private NetworkStream _networkStream = null; // This is the stream to use.
+ private TcpClient _tcpClient = null; // This is the TCP socket for the stream.
+ private StreamWriter _streamWriter = null; // This is the stream to write to
+ private StreamReader _streamReader = null; // This is the stream to read from
+ private String _host = null; // This is DNS name or IP of the host to talk to
+ private int _port = 0; // this is the port number to connect to
+
+ ///
+ /// Connect to the host, populating the socket from the configuration options
+ ///
+ public Connection(String host, int port) {
+ ReportMessage.Log(Verbosity.Verbose,String.Format("Connecting to host {0}...",host));
+ this._host = host;
+ this._port = port;
+ this._tcpClient = new TcpClient(this._host,this._port);
+ this._tcpClient.ReceiveTimeout = Connection._ircReadTimeout;
+ this._networkStream = this._tcpClient.GetStream();
+ this._streamWriter = new StreamWriter(this._networkStream);
+ this._streamReader = new StreamReader(this._networkStream);
+ ReportMessage.Log(Verbosity.VeryVerbose,"... Connected.");
+ }
+
+ ///
+ /// Reads a line from the socket
+ ///
+ /// A string read from the socket
+ public IRCServerMessage Read() {
+ String response = null;
+ while (response == null) {
+ try {
+ response = this._streamReader.ReadLine();
+ } catch (IOException e) { // If the socket times out, make sure the host is still alive.
+ try {
+ IRCPongMessage pingHost = new IRCPongMessage(String.Format("PING :{0}",this._host));
+ Write(pingHost);
+ response = this._streamReader.ReadLine();
+ } catch (IOException f) { // If we get this, then the socket is dead and we need to signal
+ throw new RavenTimedOutException(String.Format("{0}\n{1}\n",e.Message,f.Message));
+ }
+ }
+ if (response != null && response.Length > 3 && response.Substring(0,4).Equals("PING")) { // if the response is a PING message, PONG and read again.
+ IRCPongMessage pong = new IRCPongMessage(response);
+ Write(pong);
+ response = null;
+ }
+ }
+ IRCServerMessage readMessage = new IRCServerMessage(response);
+ ReportMessage.Log(Verbosity.VeryVerbose,readMessage.ToString());
+ return readMessage;
+
+ }
+
+ ///
+ /// Writes a line to the socket
+ ///
+ ///
+ /// The string to write
+ ///
+ public void Write(IRCMessage toWrite) {
+ ReportMessage.Log(Verbosity.VeryVerbose,toWrite.ToString());
+ this._streamWriter.WriteLine(String.Format("{0}\r\n",toWrite.GetOutgoingIRCString()));
+ this._streamWriter.Flush();
+ }
+
+ ///
+ /// Is the user logged in?
+ ///
+ ///
+ /// The username to check
+ ///
+ ///
+ /// A boolean value representing whether the user is logged in or not
+ ///
+ public bool IsLoggedIn(String userName) {
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("Asking for user {0} login status.",userName));
+ String outgoing = String.Format("WHOIS {0}\r\n",userName);
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("<<< {0}",outgoing.Trim()));
+ this._streamWriter.WriteLine(outgoing);
+ this._streamWriter.Flush();
+ String[] bySpace;
+ do {
+ String response = this._streamReader.ReadLine();
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format(">>> {0}",response));
+ bySpace = response.Split(' ');
+ if (bySpace.Length > 1 && bySpace[1].Equals("330")) {
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("User {0} is authenticated.",userName));
+ return true;
+ }
+ } while (bySpace.Length < 2 || !bySpace[1].Equals("318"));
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("User {0} is not authenticated.",userName));
+ return false;
+ }
+
+ ///
+ /// Get the modes for a user
+ ///
+ ///
+ /// the username to check
+ ///
+ ///
+ /// A string with the modes.
+ ///
+ public String GetModes(String userName) {
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("Asking for user {0} mode.",userName));
+ String outgoing = String.Format("MODE {0}\r\n",userName);
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("<<< {0}",outgoing.Trim()));
+ this._streamWriter.WriteLine(outgoing);
+ this._streamWriter.Flush();
+ String[] bySpace;
+ do {
+ String response = this._streamReader.ReadLine();
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format(">>> {0}",response));
+ bySpace = response.Split(' ');
+ if (bySpace.Length > 9 && bySpace[1].Equals("330")) {
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("User {0} has modes {1}.",userName,bySpace[4]));
+ return bySpace[4];
+ }
+ } while (bySpace.Length < 2 || !bySpace[1].Equals("502"));
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("Cannot get user modes -- not a netadmin.",userName));
+ return "";
+ }
+
+
+
+ /* CONNECTION NEEDS TO BE DISPOSABLE BECAUSE IT HOLDS A SOCKET */
+
+ ///
+ /// Clean up this Connection, implementing IDisposable
+ ///
+ public void Dispose() {
+ Dispose(true); // Dispose of this instance
+ GC.SuppressFinalize(this); //The Garbage Collector doesn't need to finalize it.
+ }
+
+ ///
+ /// Force the GarbageCollector to Dispose if programmer does not
+ ///
+ ~Connection() {
+ Dispose(false);
+ ReportMessage.Log(Verbosity.Error,"Programmer forgot to dispose of Connection. Marking for Garbage Collector");
+ }
+
+ // This bool indicates whether we are disposed of yet or not
+ bool _isDisposed = false;
+
+ ///
+ /// Dispose of this Connection's resources responsibly.
+ ///
+ protected virtual void Dispose(bool disposing) {
+ if (!this._isDisposed) { //if we haven't already disposed of this, we should.
+ if (disposing) {
+ //No managed resources for this class.
+ }
+ // Cleaning unmanaged resources
+ this._streamReader.Dispose();
+ this._streamWriter.Dispose();
+ this._tcpClient.Close();
+ this._networkStream.Dispose();
+ }
+ this._isDisposed = true;
+ }
+ }
+}
diff --git a/exitedexception.csharp b/exitedexception.csharp
new file mode 100644
index 0000000..c177f3c
--- /dev/null
+++ b/exitedexception.csharp
@@ -0,0 +1,17 @@
+using System;
+
+namespace AniNIX.TheRaven {
+ public class RavenExitedException : System.Exception {
+
+ ///
+ /// Create a new RavenTimedOutException to identify this event
+ ///
+ public RavenExitedException(String message) : base(message) { }
+
+ ///
+ /// Create a new RavenTimedOutException to identify this event
+ ///
+ public RavenExitedException() : base(null) { }
+
+ }
+}
diff --git a/ircclientmessage.csharp b/ircclientmessage.csharp
new file mode 100644
index 0000000..8f14870
--- /dev/null
+++ b/ircclientmessage.csharp
@@ -0,0 +1,37 @@
+using System;
+using System.Text;
+namespace AniNIX.TheRaven {
+
+ //IRC messages are a primitive data type for us to use.
+ public class IRCClientMessage : IRCMessage {
+
+ //No incoming string
+ public new string GetIncomingIRCString() {
+ return null;
+ }
+
+ public void CreateCustomMessage(String ircString) {
+ outgoingIRCString = ircString;
+ }
+
+ public void NickServIdent(String nickServPass) {
+ outgoingIRCString = String.Format("PRIVMSG NickServ IDENTIFY {0}",nickServPass);
+ }
+
+ public void CreateJoinMessage(String channel) {
+ outgoingIRCString = String.Format("JOIN {0}",channel);
+ }
+
+ public void CreatePartMessage(String channel) {
+ outgoingIRCString = String.Format("PART {0}",channel);
+ }
+
+ public void PrivMsg(String message, string destination) {
+ outgoingIRCString = String.Format("PRIVMSG {0} :{1}",destination,message);
+ }
+
+ public void ActionMsg(String message, string destination) {
+ outgoingIRCString = String.Format("PRIVMSG {0} :\u0001ACTION {1}",destination,message);
+ }
+ }
+}
diff --git a/ircmessage.csharp b/ircmessage.csharp
new file mode 100644
index 0000000..976c0ed
--- /dev/null
+++ b/ircmessage.csharp
@@ -0,0 +1,28 @@
+using System;
+
+namespace AniNIX.TheRaven {
+
+ public abstract class IRCMessage {
+
+ protected string incomingIRCString;
+ protected string outgoingIRCString;
+
+ public string GetOutgoingIRCString() {
+ return outgoingIRCString;
+ }
+
+ public string GetIncomingIRCString() {
+ return incomingIRCString;
+ }
+
+ public override string ToString() {
+ if (outgoingIRCString == null || outgoingIRCString.Length < 1) {
+ return String.Format(">>> {0}",incomingIRCString);
+ } else if (incomingIRCString == null || incomingIRCString.Length < 1) {
+ return String.Format("<<< {0}",outgoingIRCString);
+ } else {
+ return string.Format(">>> {1}\n<<< {0}",outgoingIRCString,incomingIRCString);
+ }
+ }
+ }
+}
diff --git a/ircpongmessage.csharp b/ircpongmessage.csharp
new file mode 100644
index 0000000..b3cb81e
--- /dev/null
+++ b/ircpongmessage.csharp
@@ -0,0 +1,17 @@
+using System;
+using System.Text;
+
+namespace AniNIX.TheRaven {
+
+ public class IRCPongMessage : IRCMessage {
+
+ public IRCPongMessage(String serverString) {
+ incomingIRCString = String.Copy(serverString.Trim());
+ outgoingIRCString = String.Copy(incomingIRCString);
+ char[] arr = outgoingIRCString.ToCharArray();
+ arr[1] = 'O';
+ outgoingIRCString = new String(arr);
+ }
+
+ }
+}
diff --git a/ircservermessage.csharp b/ircservermessage.csharp
new file mode 100644
index 0000000..2b2be85
--- /dev/null
+++ b/ircservermessage.csharp
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+
+namespace AniNIX.TheRaven {
+
+ //IRC messages are a primitive data type for us to use.
+ public class IRCServerMessage : IRCMessage {
+
+ public string user { get; protected set; }
+ public string server { get; protected set; }
+ public string msgCode { get; protected set; }
+ public string target { get; protected set; }
+ public string message { get; protected set; }
+
+ public IRCServerMessage(String serverString) {
+ incomingIRCString = serverString.Trim();
+ try {
+ String[] byColon = incomingIRCString.Split(':');
+ user = byColon[0];
+ String[] bySpace = byColon[1].Split(' ');
+ List messageL = new List(byColon);
+ messageL.RemoveAt(0);
+ messageL.RemoveAt(0);
+ message = String.Join(":",messageL.ToArray());
+ if (bySpace[0].Contains("!")) {
+ String[] byExclamation = bySpace[0].Split('!');
+ user = byExclamation[0];
+ server = byExclamation[1];
+ } else {
+ user = null;
+ server = bySpace[0];
+ }
+
+ msgCode = bySpace[1];
+ target = bySpace[2];
+ } catch (IndexOutOfRangeException e) {
+ ReportMessage.Log(Verbosity.Error,String.Format("!!! Can't translate string:\n{0}",serverString,e.ToString()));
+ user = null;
+ server = null;
+ msgCode = null;
+ target = null;
+ message = null;
+ }
+ }
+
+ // There is no outgoing string.
+ public new string GetOutgoingIRCString() {
+ return null;
+ }
+
+ public override string ToString() {
+ if (Raven.verbosity == Verbosity.Explicit) {
+ return String.Format(">>> {0}\nUser: {1}\nServer: {2}\nmsgCode: {3}\nTarget: {4}\nMessage: {5}\n",incomingIRCString,user,server,msgCode,target,message);
+ } else {
+ return String.Format(">>> {0}",incomingIRCString);
+ }
+ }
+ }
+}
diff --git a/make-conf-dir.bash b/make-conf-dir.bash
new file mode 100644
index 0000000..9f8ff9f
--- /dev/null
+++ b/make-conf-dir.bash
@@ -0,0 +1,16 @@
+#!/usr/bin/bash
+
+mkdir -p $1
+touch $1/blacklist.txt
+touch $1/crowfacts.txt
+touch $1/crowfacts.txt.bak
+touch $1/hangman.txt
+touch $1/keepalive-loginDefaults.txt
+touch $1/magic8.txt
+touch $1/todo.txt
+touch $1/whitelist.txt
+touch $1/loginDefaults.txt
+touch $1/rooms.txt
+touch $1/searches.txt
+touch $1/helptext.txt
+
diff --git a/raven-local.service b/raven-local.service
new file mode 100644
index 0000000..c781a9f
--- /dev/null
+++ b/raven-local.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=AniNIX::Raven IRC Bot for Local
+After=network.target
+
+[Service]
+type=simple
+ExecStart=/usr/bin/mono /opt/raven.mono -c /usr/local/etc/TheRaven-Local
+ExecReload=/bin/kill -HUP $MAINPID
+KillMode=process
+Restart=always
+User=ircd
+Group=ircd
+
+[Install]
+WantedBy=multi-user.target
diff --git a/raven.csharp b/raven.csharp
new file mode 100644
index 0000000..53193e5
--- /dev/null
+++ b/raven.csharp
@@ -0,0 +1,387 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace AniNIX.TheRaven {
+
+ public sealed class Raven : IDisposable {
+
+ /* These are the private globals for this instance
+ * They should never be accessible outside this Raven
+ */
+ //These are the basic configuration information to be overwritten
+ public String Host { get; private set; } //This is the Host we are connecting to.
+ public int Port { get; private set; } // This is Port to connect on
+ private string _nick; // This is the _nickname for this Raven to use.
+ private string _nickServPass; // This is the password we will send to NickServ to identify
+ private string _autoSend; // This is the command we will automatically send to the Host
+ private string configDir; // This is the configuration directory.
+ private Connection _connection; //This is the socket to the Host
+
+ public List channels = new List(); //This is the list of channels to join
+ public List whitelist = new List(); //This is the list of admin users.
+ public List blacklist = new List(); // This is the list of blocked people.
+ public String helpText = null; // This is the text to send when people ask for help -- this is configurable to allow for skinning
+ public List searches = new List(); //These are the searches
+ public String[] magic8 = null; //These are the strings to return like a Magic 8-ball to questions.
+ public String[] crowFacts = null; //These are the possible CrowFacts
+ public List crowFactsSubscribers = new List(); //These are the subscribers to CrowFacts
+ public Dictionary notifications = new Dictionary(); // This is the notifications list for TheRaven.
+
+ public Random randomSeed = new Random(); //This is our random seed
+
+ public Dictionary MailerCount = new Dictionary(); // Messages may only be sent up to a maximum to the admins.
+
+ public static Verbosity verbosity { get; private set; } // This is the level to which this and all Raven instances will log.
+
+ ///
+ /// Show the settings used by this Raven.
+ ///
+ /// A string representing this Raven
+ public override string ToString() {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("### AniNIX::TheRaven -- Running Values ###\n");
+ sb.Append(String.Format("Host: {0}\n",Host));
+ sb.Append(String.Format("Port: {0}\n",Port));
+ sb.Append(String.Format("Nick: {0}\n",_nick));
+ sb.Append("NickServPass: ****\n");
+ sb.Append(String.Format("Auto: {0}\n",_autoSend));
+ sb.Append(String.Format("Conf: {0}\n",configDir));
+ sb.Append(String.Format("Verbosity: {0}\n",Raven.verbosity));
+ return sb.ToString();
+ }
+
+ ///
+ /// Read from the files in the directory to configure this Raven
+ ///
+ // TODO: This and ParseArgs may get punted into their own static class to improve readability.
+ private void ConfigureSelfFromFiles(String configDir) {
+
+ if (configDir==null || configDir == "" || !Directory.Exists(configDir)) {
+ ReportMessage.Log(Verbosity.Error,"Configuration directory does not exist!");
+ return;
+ }
+
+
+ ReportMessage.Log(Verbosity.Always,String.Format("Reading from files in {0}...",configDir));
+
+ //These are locals that will be used throughout
+ ReportMessage.Log(Verbosity.Verbose,"Reading login defaults");
+ String[] loginDefaults = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"loginDefaults.txt")).ToArray();
+ //We have to populate these properties fom the list explicitly
+ if (loginDefaults.Length < 4) {
+ ReportMessage.Log(Verbosity.Error,"Login defaults are incomplete. No changes made.");
+ } else {
+ Host = (Host==null) ? loginDefaults[0] : Host;
+ try {
+ Port = (Port == 0) ? Int32.Parse(loginDefaults[1]) : Port;
+ } catch (Exception e) {
+ ReportMessage.Log(Verbosity.Verbose,"Cannot parse Port.");
+ e.ToString();
+ Port = 6667;
+ }
+ _nick = (_nick == null) ? loginDefaults[2] : _nick;
+ _nickServPass = (_nickServPass == null) ? loginDefaults[3] : _nickServPass;
+ }
+
+ //Read all the channels to join.
+ List tempChannels = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"rooms.txt"));
+ // Because the convention is to use # for comments, channels in the rooms.txt file do not start with a #
+ foreach (String channel in tempChannels) {
+ channels.Add(String.Format("#{0}",channel));
+ }
+
+ //Read the whitelist of folks allowed to execute administrative commands
+ whitelist = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"whitelist.txt"));
+
+ //Read the blacklist of folks not allowed to do anything.
+ blacklist = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"blacklist.txt"));
+
+ //Read the helptext
+ helpText = RavenConfigure.ReadFirstLineFromFile(Path.Combine(configDir,"helptext.txt"));
+
+ //Read the searches to use
+ searches = RavenConfigure.ReadLineDelimitedFile(Path.Combine(configDir,"searches.txt"));
+
+ //Read the Magic8 options
+ magic8 = RavenConfigure.ReadLineDelimitedFileToArr(Path.Combine(configDir,"magic8.txt"));
+
+ //Read the CrowFacts
+ crowFacts = RavenConfigure.ReadLineDelimitedFileToArr(Path.Combine(configDir,"crowfacts.txt"));
+
+ //Read the notifications
+ foreach (String combo in RavenConfigure.ReadLineDelimitedFileToArr(Path.Combine(configDir,"notifications.txt"))) {
+ String[] byPipe = combo.Split('|');
+ notifications.Add(String.Format("#{0}",byPipe[0]),byPipe[1]);
+ }
+ }
+
+ ///
+ /// Parse arguments from the command line.
+ ///
+ //TODO: Move this to RavenConfigure and add struct for these configurations
+ public void ParseArguments(String[] args) {
+ if (args != null) {
+ for (int i = 0; i < args.Length; i++) {
+ ReportMessage.Log(Verbosity.Verbose,String.Format("Handling Argument {0}: {1}",i,args[i]));
+ switch (args[i]) {
+ case "-n":
+ if (i < args.Length-1) _nick = args[++i];
+ break;
+ case "-h":
+ if (i < args.Length-1) Host = args[++i];
+ break;
+ case "-p":
+ if (i < args.Length-1) try {
+ Port = Int32.Parse(args[++i]);
+ } catch (Exception e) {
+ e.ToString();
+ Port = 6667;
+ }
+ break;
+ case "-v":
+ Raven.verbosity = Verbosity.VeryVerbose;
+ break;
+ case "-q":
+ Raven.verbosity = Verbosity.Quiet;
+ break;
+ case "-P":
+ if (i < args.Length-1) _nickServPass = args[++i];
+ break;
+ //TODO: Add daemonizing?
+ case "-a":
+ if (i < args.Length-1) _autoSend = args[++i];
+ break;
+ case "--help":
+ //TODO Add helptext
+ break;
+ case "-c":
+ if (i < args.Length-1) configDir = args[++i];
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Create a new Raven instance
+ ///
+ ///
+ /// The arguments for creating the bot
+ ///
+ public Raven(string[] args) {
+
+ ReportMessage.Log(Verbosity.Always,"Reading arguments...");
+
+ // If we have arguments
+ this.ParseArguments(args);
+
+ this.ConfigureSelfFromFiles(configDir);
+
+ ReportMessage.Log(Verbosity.VeryVerbose,"Started with these values:");
+ ReportMessage.Log(Verbosity.VeryVerbose,this.ToString());
+ }
+
+ ///
+ /// Create a raven with default settings.
+ ///
+ public Raven(String host = "localhost", int port = 6667, String nick = "TheRaven-Guest", String nickServPass = "null", String autoSend = null, String configDir = "/usr/local/etc/TheRaven-Local", Verbosity verbosity = Verbosity.Verbose) {
+ this.Host = host;
+ Port = port;
+ _nick = nick;
+ _nickServPass = nickServPass;
+ _autoSend = autoSend;
+ this.configDir = configDir;
+ Raven.verbosity = verbosity;
+ }
+
+ ///
+ /// Identify to the server and join the initial channels
+ ///
+ public void IdentifySelfToServer() {
+ ReportMessage.Log(Verbosity.Always,"Identifying to the server");
+ //Read for the initial two NOTICE messages about Hostnames
+ IRCServerMessage response = null;
+ int countNotice = 0;
+ while (countNotice < 2) {
+ response = _connection.Read();
+ if (response.msgCode.Equals("NOTICE")) countNotice += 1;
+ }
+ ReportMessage.Log(Verbosity.VeryVerbose,"Past the notices.");
+
+ //Send USER and NICK lines to identify.
+ IRCClientMessage send = new IRCClientMessage();
+ send.CreateCustomMessage(String.Format("NICK {0}\nUSER {0} * * :{0}",_nick));
+ _connection.Write(send);
+ ReportMessage.Log(Verbosity.VeryVerbose,"USER and NICK sent");
+ //thanks to cfrayne for the refactor
+
+ do {
+ response = _connection.Read();
+ if (response.msgCode != null && response.msgCode.Equals("433")) throw new AlreadyIdentifiedException();
+ } while (response.msgCode == null || !response.msgCode.Equals("266"));
+
+ //Identify to NickServ
+ send.NickServIdent(_nickServPass);
+ _connection.Write(send);
+ ReportMessage.Log(Verbosity.VeryVerbose,"Identified to NickServ");
+
+ //Send the autosend
+ send.CreateCustomMessage(_autoSend);
+ _connection.Write(send);
+ ReportMessage.Log(Verbosity.VeryVerbose,"Sent autosend");
+
+ //Join the default channels
+ foreach (String channel in channels) {
+ if (channel != null && channel.Length > 2 && channel[0] == '#') {
+ send.CreateJoinMessage(channel);
+ _connection.Write(send);
+ }
+ }
+ }
+
+ ///
+ /// Read from the connection, and for each message act appropriately.
+ ///
+ public void LoopOnTraffic() {
+ ReportMessage.Log(Verbosity.Verbose,"Looping on trafffic now! We're useful!");
+ while (true) {
+ IRCServerMessage response = _connection.Read();
+ if (response != null && response.message != null && response.message.Length > 3 && response.message.Substring(0,2).Equals("r.")) {
+ RavenCommand.Respond(_connection,response,this);
+ } else if (response != null) {
+ //Try to notify the admins when a given string is found in a given channel
+ String result;
+ if (notifications.TryGetValue(response.target,out result)) {
+ if (response.message.Contains(result)) {
+ try {
+ RavenExecute.Command(String.Format("djinni admin \"Found {1} in {0}\"",response.target,result));
+ } catch (Exception e) {
+ ReportMessage.Log(Verbosity.Error,e.ToString());
+ }
+
+ }
+ }
+
+ //TODO Implement the dialog options and link reponse
+
+ /* CROWFACTS the deserving */
+
+ if (crowFactsSubscribers.Contains(response.user) && randomSeed.Next(10) < 8) {
+ IRCClientMessage send = new IRCClientMessage();
+ int location = randomSeed.Next(crowFacts.Length);
+ send.PrivMsg(crowFacts[location],response.user);
+ _connection.Write(send);
+ }
+ }
+
+ }
+ }
+
+ ///
+ /// Close the _connection
+ ///
+ private void CloseConnection() {
+ this._connection.Dispose();
+ }
+
+ ///
+ /// Execute the work of connecting the IRCbot to the network and handle the traffic.
+ ///
+ /// The proper OS-level exit status -- if there are problems, return 1; else return 0
+ public int Run() {
+ ReportMessage.Log(Verbosity.Verbose,"Beginning...");
+
+ //create a new _connection to the Host.
+ try {
+ _connection = new Connection(this.Host,this.Port);
+ IdentifySelfToServer();
+ LoopOnTraffic();
+ // Allow the program to exit cleanly
+
+ } catch (RavenExitedException e) {
+ this.CloseConnection();
+ this.Dispose();
+ ReportMessage.Log(Verbosity.Always,String.Format("Exited cleanly.\n{0}",e.Message));
+ return 0;
+ }
+ //Cleanly exit
+ return 0;
+ }
+
+ /* Make a Raven disposable */
+
+ ///
+ /// Clean up this Connection, implementing IDisposable
+ ///
+ public void Dispose() {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ ///
+ /// Force the GarbageCollector to Dispose if programmer does not
+ ///
+ ~Raven() {
+ Dispose(false);
+ ReportMessage.Log(Verbosity.Error,"Programmer forgot to dispose of Raven. Marking for Garbage Collector");
+ }
+
+ // This bool indicates whether we have disposed of this Raven
+ public bool _isDisposed = false;
+
+ ///
+ /// Dispose of this Raven's's resources responsibly.
+ ///
+ private void Dispose(bool disposing) {
+ if (!_isDisposed) {
+ if (disposing) {
+ Host = null;
+ Port = 0;
+ _nickServPass = null;
+ _autoSend = null;
+ configDir = null;
+ whitelist = null;
+ blacklist = null;
+ magic8 = null;
+ crowFacts = null;
+ crowFactsSubscribers = null;
+ channels = null;
+ searches = null;
+
+ }
+ _connection.Dispose();
+ }
+ this._isDisposed = true;
+ }
+
+ ///
+ /// The default function
+ ///
+ static int Main(string[] args) {
+ Raven theRaven = new Raven(args);
+ ReportMessage.Log(Verbosity.Verbose,"### AniNIX::TheRaven ###");
+ //Continue until we cleanly exit.
+ while (true) {
+ try {
+ return theRaven.Run();
+ //If we are already identified, we're done.
+ } catch (AlreadyIdentifiedException e) {
+ ReportMessage.Log(Verbosity.Error,"There is already a Raven on this Host.");
+ ReportMessage.Log(Verbosity.Error,e.Message);
+ return 0;
+ // Timeouts should result in a respawn
+ } catch (RavenTimedOutException e) {
+ ReportMessage.Log(Verbosity.Always,"Connection timed out. Respawning");
+ ReportMessage.Log(Verbosity.Verbose,e.Message);
+ continue;
+ //If an exception gets here, something went wrong
+ } catch (Exception e) {
+ ReportMessage.Log(Verbosity.Error,"Unexpected exception caught!");
+ ReportMessage.Log(Verbosity.Error,e.ToString());
+ return 1;
+ }
+ }
+ }
+ }
+}
diff --git a/raven.mono b/raven.mono
new file mode 100755
index 0000000..c8563fa
Binary files /dev/null and b/raven.mono differ
diff --git a/raven.service b/raven.service
new file mode 100644
index 0000000..a6a26ae
--- /dev/null
+++ b/raven.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=AniNIX::Raven IRC Bot for ACWiki
+After=network.target
+
+[Service]
+ExecStart=/usr/bin/mono /opt/raven.mono -c /usr/local/etc/TheRaven
+ExecReload=/bin/kill -HUP $MAINPID
+KillMode=process
+Restart=always
+User=raven
+Group=raven
+
+[Install]
+WantedBy=multi-user.target
diff --git a/ravencommand.csharp b/ravencommand.csharp
new file mode 100644
index 0000000..277ed5f
--- /dev/null
+++ b/ravencommand.csharp
@@ -0,0 +1,326 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Collections.Generic;
+
+namespace AniNIX.TheRaven {
+
+ public static class RavenCommand {
+
+ // This is the string we return when people poorly format a command.
+ public static String helpString = "Bad formatting! Type r.help for help.";
+
+ ///
+ /// This is a contextual match of commands with actions for the bot to take.
+ /// This will be a very long function, but watch for the /* HEADER */ sections to subdivide it.
+ ///
+ /// The socket being used represented by a Connection
+ /// The message from the IRC server to respond to
+ /// The Raven instance -- we use this to update various lists
+ public static void Respond(Connection connection,IRCServerMessage incoming, Raven theRaven) {
+
+ IRCClientMessage send = new IRCClientMessage();
+
+ /* CANCEL ON BLACKLIST */
+ if (theRaven.blacklist.Contains(incoming.user)) { return; } // Blacklisted people can't do anything.
+
+ //Splits placed here for performance reasons
+ String[] bySpace = incoming.message.Split(' ');
+ String command = bySpace[0];
+
+ /* IDENTIFY the command and IRCClientMessage to send */
+
+ /* COMMON Commands everyone can use */
+ switch (command) {
+ case "r.raven":
+ send.ActionMsg("quoth, \"Nevermore!\"",incoming.target);
+ connection.Write(send);
+ return;
+ case "r.help":
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ connection.Write(send);
+ return;
+ case "r.magic8":
+ if (theRaven.magic8 == null) {
+ send.PrivMsg("Magic8 not loaded",incoming.target);
+ } else {
+ int location = theRaven.randomSeed.Next(theRaven.magic8.Length);
+ send.PrivMsg(theRaven.magic8[location],incoming.target);
+ }
+ connection.Write(send);
+ return;
+ case "r.tinyurl":
+ if (bySpace.Length < 2) {
+ send.PrivMsg(theRaven.helpText,incoming.user);
+ } else {
+ try {
+ send.PrivMsg(RavenExecute.Command(String.Format("wget -q -O - \"http://tiny-url.info/api/v1/create?format=text&apikey=\"$(api-keys tinyurl)\"&provider=x_co&url={0}\"",bySpace[1])),incoming.target);
+ } catch (Exception e) {
+ ReportMessage.Log(Verbosity.Error,e.ToString());
+ send.PrivMsg("TinyURL error. Could not get link.",incoming.target);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.msg":
+ if (bySpace.Length < 2) {
+ send.PrivMsg(theRaven.helpText,incoming.user);
+ } else {
+ if (!theRaven.MailerCount.ContainsKey(incoming.user)) theRaven.MailerCount.Add(incoming.user,0);
+ if (theRaven.MailerCount[incoming.user] >= 5) {
+ send.PrivMsg("You cannot send more than 5 messages to the admins between resets. This has been logged.",incoming.user);
+ connection.Write(send);
+ ReportMessage.Log(Verbosity.Error,String.Format("!!! {0} tried to send a message but couldn't -- attempts exceeded.",incoming.user));
+ return;
+ }
+ theRaven.MailerCount[incoming.user] = theRaven.MailerCount[incoming.user] + 1;
+ //Try to append the message to the log
+ StringBuilder sb = new StringBuilder(bySpace[1]);
+ for (int i = 2; i < bySpace.Length; i++) {
+ sb.Append(" ");
+ sb.Append(bySpace[i]);
+ }
+ try {
+ StreamWriter writer = new StreamWriter("/var/log/r.msg.log",true);
+ writer.Write(String.Format("{0} - {1} left a message on {2} in {3}\n{4}\n\n",DateTime.Now,incoming.user,theRaven.Host,incoming.target,sb.ToString()));
+ writer.Close();
+ } catch (Exception e) {
+ ReportMessage.Log(Verbosity.Error,e.Message);
+ ReportMessage.Log(Verbosity.Error,"Make sure user raven can write to /var/log/r.msg.log");
+ }
+ try {
+ RavenExecute.Command(String.Format("djinni admin \"Page from {0}\"",incoming.user));
+ send.PrivMsg("Sent!",incoming.user);
+ } catch (Exception e) {
+ ReportMessage.Log(Verbosity.Error,e.ToString());
+ send.PrivMsg("Mailer error. Could not send.",incoming.user);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.uptime":
+ try {
+ send.PrivMsg(RavenExecute.Command("uptime"),incoming.target);
+ } catch (Exception e) {
+ e.ToString();
+ send.PrivMsg("Can't get uptime",incoming.target);
+ }
+ connection.Write(send);
+ return;
+ case "r.heartbeat":
+ try {
+ String[] byLine = RavenExecute.Command("heartbeat-client").Split('\n');
+ for (int i = 0; i < byLine.Length; i++) {
+ send.PrivMsg(byLine[i],incoming.user);
+ connection.Write(send);
+ }
+ } catch (Exception e) {
+ e.ToString();
+ send.PrivMsg("Can't get heartbeat",incoming.user);
+ }
+ return;
+
+ }
+
+ /* SEARCHES */
+ foreach (String search in theRaven.searches) {
+ if (search == null) continue;
+ String[] byPipe = search.Split('|');
+ if (byPipe.Length < 3) {
+ ReportMessage.Log(Verbosity.Error,String.Format("Incomplete search: {0}",search));
+ continue;
+ }
+ if (byPipe[0].Equals(command)) {
+ send.PrivMsg(FormatSearch(byPipe[1],incoming.message,byPipe[2]),incoming.target);
+ connection.Write(send);
+ return;
+ }
+ }
+
+ /* ADMIN commands are allowed to whitelisted users */
+ if (theRaven.whitelist.Contains(incoming.user) && connection.IsLoggedIn(incoming.user)) switch (command) {
+ case "r.quit":
+ send.CreateCustomMessage("QUIT :Caw, caw, caw!");
+ connection.Write(send);
+ throw new RavenExitedException();
+ case "r.cf":
+ if (bySpace.Length < 2) {
+ send.PrivMsg("This is the CrowFacts list of subscribers:",incoming.user);
+ connection.Write(send);
+ foreach (String user in theRaven.crowFactsSubscribers) {
+ send.PrivMsg(user,incoming.user);
+ connection.Write(send);
+ }
+ send.PrivMsg("End subscribers",incoming.user);
+
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ } else if (theRaven.crowFacts == null) {
+ send.PrivMsg("CrowFacts not loaded.",incoming.target);
+ } else {
+ if (!theRaven.crowFactsSubscribers.Contains(bySpace[1])) {
+ theRaven.crowFactsSubscribers.Add(bySpace[1]);
+ send.PrivMsg(String.Format("{0} has been subscribed to CrowFacts!",bySpace[1]),incoming.target);
+ } else {
+ send.PrivMsg("Subscriber already added",incoming.target);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.us":
+ if (bySpace.Length < 2) {
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ } else {
+ if (theRaven.crowFactsSubscribers.Contains(bySpace[1])) {
+ theRaven.crowFactsSubscribers.Remove(bySpace[1]);
+ send.PrivMsg(String.Format("{0} has been unsubscribed.",bySpace[1]),incoming.target);
+ } else {
+ send.PrivMsg("No such subscriber",incoming.target);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.join":
+ if (bySpace.Length < 2) {
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ } else {
+ if (!theRaven.channels.Contains(bySpace[1])) {
+ theRaven.channels.Add(bySpace[1]);
+ send.CreateJoinMessage(bySpace[1]);
+ } else {
+ send.PrivMsg("Already joined channel",incoming.target);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.part":
+ if (bySpace.Length < 2) {
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ } else {
+ if (theRaven.channels.Contains(bySpace[1])) {
+ theRaven.channels.Remove(bySpace[1]);
+ send.CreatePartMessage(bySpace[1]);
+ } else {
+ send.PrivMsg("No such channel",incoming.target);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.say":
+ if (bySpace.Length < 3 || !bySpace[1].StartsWith("#")) {
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ } else {
+ StringBuilder newMsg = new StringBuilder(bySpace[2]);
+ for (int i = 3; i < bySpace.Length; i++) {
+ newMsg.Append(" ");
+ newMsg.Append(bySpace[i]);
+ }
+ send.PrivMsg(newMsg.ToString(),bySpace[1]);
+ }
+ connection.Write(send);
+ return;
+ case "r.act":
+ if (bySpace.Length < 3 || !bySpace[1].StartsWith("#")) {
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ } else {
+ StringBuilder newAction = new StringBuilder(bySpace[2]);
+ for (int i = 3; i < bySpace.Length; i++) {
+ newAction.Append(" ");
+ newAction.Append(bySpace[i]);
+ }
+ send.ActionMsg(newAction.ToString(),bySpace[1]);
+ }
+ connection.Write(send);
+ return;
+ case "r.whitelist":
+ if (bySpace.Length < 2) {
+ send.PrivMsg("This is the whitelist:",incoming.user);
+ connection.Write(send);
+ foreach (String user in theRaven.whitelist) {
+ send.PrivMsg(user,incoming.user);
+ connection.Write(send);
+ }
+ send.PrivMsg("End whitelist",incoming.user);
+ } else {
+ if (!theRaven.whitelist.Contains(bySpace[1]) && !theRaven.blacklist.Contains(bySpace[1])) {
+ theRaven.whitelist.Add(bySpace[1]);
+ send.PrivMsg(String.Format("{0} has been whitelisted.",bySpace[1]),incoming.user);
+ } else {
+ send.PrivMsg("Already whitelisted or is a blacklisted user",incoming.user);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.blacklist":
+ if (bySpace.Length < 2) {
+ send.PrivMsg("This is the blacklist:",incoming.user);
+ connection.Write(send);
+ foreach (String user in theRaven.blacklist) {
+ send.PrivMsg(user,incoming.user);
+ connection.Write(send);
+ }
+ send.PrivMsg("End blacklist",incoming.user);
+ } else {
+ if (!theRaven.blacklist.Contains(bySpace[1]) && !theRaven.whitelist.Contains(bySpace[1])) {
+ theRaven.blacklist.Add(bySpace[1]);
+ send.PrivMsg(String.Format("{0} has been blacklisted.",bySpace[1]),incoming.user);
+ } else {
+ send.PrivMsg("Already blacklisted or is an admin user",incoming.user);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.greylist":
+ if (bySpace.Length < 2) {
+ send.PrivMsg(theRaven.helpText,incoming.target);
+ } else {
+ if (theRaven.whitelist.Contains(bySpace[1])) {
+ theRaven.whitelist.Remove(bySpace[1]);
+ send.PrivMsg("User cleared from whitelist",incoming.user);
+ } else if (theRaven.blacklist.Contains(bySpace[1])) {
+ theRaven.blacklist.Remove(bySpace[1]);
+ send.PrivMsg("User cleared from blacklist",incoming.user);
+ } else {
+ send.PrivMsg("No action needed",incoming.user);
+ }
+ }
+ connection.Write(send);
+ return;
+ case "r.adminhelp":
+ send.PrivMsg("r.adminhelp, r.cf to crowfacts, r.us to unsubscribe, r.whitelist to make user bot admin, r.blacklist to block user, r.greylist to pull user off *list, r.say [channel] [message], r.join a channel, r.part a channel, r.quit to quit",incoming.user);
+ connection.Write(send);
+ return;
+ // Commenting because I believe this to be unneeded
+ // TODO Reevaluate
+ /* case "r.ident":
+ theRaven.IdentifySelfToServer();
+ return;*/
+ case "r.mailerreset":
+ theRaven.MailerCount = new Dictionary();
+ send.PrivMsg("Mailer counts have been reset",incoming.user);
+ connection.Write(send);
+ return;
+ }
+
+ return;
+ }
+
+ ///
+ /// This function returns a search string for the user to browse.
+ ///
+ /// this is the base of the search URL
+ /// This is the string requested to be recombined.
+ /// This is the character to use to combine the search arguments
+ public static String FormatSearch(String searchBase,String request,String junction) {
+ StringBuilder formattedString = new StringBuilder(searchBase);
+ String[] elements = request.Split(' ');
+ if (elements.Length < 2) return helpString;
+ formattedString.Append(elements[1]); //First element is the command
+ for (int i = 2; i < elements.Length; i++) {
+ formattedString.Append(junction);
+ formattedString.Append(elements[i]);
+ }
+ return formattedString.ToString();
+ }
+ }
+}
diff --git a/ravenconfigure.csharp b/ravenconfigure.csharp
new file mode 100644
index 0000000..1179fc6
--- /dev/null
+++ b/ravenconfigure.csharp
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace AniNIX.TheRaven {
+
+ public static class RavenConfigure {
+
+ ///
+ /// Create a new list from the line-delimited entries in a file
+ ///
+ /// the file to read
+ /// A List of Strings containing the lines.
+ public static List ReadLineDelimitedFile(String filename) {
+ String line = null;
+ int count = 0;
+ //Read all the file to join.
+ ReportMessage.Log(Verbosity.Verbose,String.Format("Reading {0}",filename)); //Path.GetFileName(filename)));
+ List newEntries = new List();
+ StreamReader fileReader = new StreamReader(filename);
+ line = fileReader.ReadLine();
+ while (line != null) {
+ if (line.Length < 1) {
+ line = fileReader.ReadLine();
+ continue;
+ }
+ line = line.Trim();
+ if (line[0] == '#') {
+ line = fileReader.ReadLine();
+ continue;
+ } //Skip lines starting with a #
+ String[] byHash = line.Split('#'); //Ignore everything after a #
+ newEntries.Add(byHash[0]);
+ count++;
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("Added entry {0} from {1}",line,Path.GetFileName(filename)));
+ line = fileReader.ReadLine();
+ }
+ fileReader.Close();
+ ReportMessage.Log(Verbosity.VeryVerbose,String.Format("Found {0} newEntries.",newEntries.Count));
+ return newEntries;
+ }
+
+ ///
+ /// Get the String[] of lines in a file -- use this for random performance
+ ///
+ /// the file to read
+ /// A String[]
+ public static String[] ReadLineDelimitedFileToArr(String filename) {
+ return RavenConfigure.ReadLineDelimitedFile(filename).ToArray();
+ }
+
+ ///
+ /// Read the first line from a file -- this is useful for allowing configuration of single strings.
+ ///
+ /// the file to read
+ /// The first line as a String
+ public static String ReadFirstLineFromFile(String filename) {
+ StreamReader fileReader = new StreamReader(filename);
+ String readString = fileReader.ReadLine();
+ fileReader.Close();
+ return readString;
+ }
+ }
+}
diff --git a/ravenexecute.csharp b/ravenexecute.csharp
new file mode 100644
index 0000000..ad24027
--- /dev/null
+++ b/ravenexecute.csharp
@@ -0,0 +1,58 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Diagnostics;
+using System.Collections.Generic;
+
+namespace AniNIX.TheRaven {
+
+ public static class RavenExecute {
+
+ ///
+ /// This method allows TheRaven to execute a command on the OS.
+ ///
+ /// The command string to run as the string argument to "bash -c 'command'"
+ /// The effective replacement for the command's stdinThe stdout of the command
+ ///
+ public static String Command(String command, String input) {
+ //Sanitize inputs.
+ if (command.Contains("\'")) {
+ throw new Exception("Command strings cannot include \'.");
+ }
+
+ //Create process.
+ Process proc = new Process();
+ proc.StartInfo.CreateNoWindow = true;
+ proc.StartInfo.FileName = "/bin/bash";
+ proc.StartInfo.Arguments = String.Format("-c \'{0}\'",command);
+ proc.StartInfo.UseShellExecute=false;
+
+ //Redirect input
+ proc.StartInfo.RedirectStandardOutput=true;
+ proc.StartInfo.RedirectStandardInput=true;
+
+ //Start process
+ proc.Start();
+
+ //Add input and read output.
+ proc.StandardInput.Write(input);
+ proc.StandardInput.Close();
+ proc.WaitForExit();
+ if (proc.ExitCode != 0) {
+ throw new Exception(String.Format("Failed to exit command with return code {0}",proc.ExitCode));
+ }
+ String stdoutString = proc.StandardOutput.ReadToEnd();
+
+ //Close up and return
+ proc.Close();
+ return stdoutString;
+ }
+
+ //Add polymorphism to allow no stdin
+ public static String Command(String command) {
+ return Command(command,null);
+ }
+
+ }
+}
diff --git a/reportmessage.csharp b/reportmessage.csharp
new file mode 100644
index 0000000..f6738c6
--- /dev/null
+++ b/reportmessage.csharp
@@ -0,0 +1,45 @@
+using System;
+
+namespace AniNIX.TheRaven {
+
+ public enum Verbosity {
+ Always = -2,
+ Error,
+ Quiet = 0,
+ Verbose,
+ VeryVerbose,
+ Explicit,
+ }
+
+ public static class ReportMessage {
+
+
+ ///
+ /// Log a new message for the user.
+ ///
+ public static void Log(Verbosity level,String message) {
+
+ if (level == Verbosity.Error) {
+ Console.Error.WriteLine(message);
+ return;
+ }
+
+ if (Raven.verbosity == Verbosity.Quiet) {
+ return;
+ }
+
+ if (level == Verbosity.Always
+ || (Raven.verbosity == Verbosity.Verbose && level == Verbosity.Verbose)
+ || (Raven.verbosity == Verbosity.VeryVerbose && (level == Verbosity.Verbose || level == Verbosity.VeryVerbose))
+ || (Raven.verbosity == Verbosity.Explicit && (level == Verbosity.Verbose || level == Verbosity.VeryVerbose || level == Verbosity.Explicit))
+ ) {
+ Console.WriteLine(message);
+ }
+
+ }
+
+ public static void Log(String message) {
+ Log(Verbosity.VeryVerbose,message);
+ }
+ }
+}
diff --git a/timedoutexception.csharp b/timedoutexception.csharp
new file mode 100644
index 0000000..6768874
--- /dev/null
+++ b/timedoutexception.csharp
@@ -0,0 +1,12 @@
+using System;
+
+namespace AniNIX.TheRaven {
+ public class RavenTimedOutException : System.Exception {
+
+ ///
+ /// Create a new RavenTimedOutException to identify this event
+ ///
+ public RavenTimedOutException(String message) : base(message) { }
+
+ }
+}