1 module ircbod.client; 2 3 import ircbod.socket, ircbod.message; 4 import std.regex, std.container, std.datetime, std.conv; 5 6 alias void delegate(IRCMessage message) MessageHandler; 7 alias void delegate(IRCMessage message, string[] args) MessageHandlerWithArgs; 8 9 class IRCClient 10 { 11 private: 12 struct PatternMessageHandler { 13 MessageHandler callback; 14 MessageHandlerWithArgs callbackWithArgs; 15 Regex!char pattern; 16 } 17 18 alias DList!PatternMessageHandler HandlerList; 19 20 IRCSocket sock; 21 string nickname; 22 string password; 23 string[] channels; 24 HandlerList[IRCMessage.Type] handlers; 25 bool running; 26 27 static Regex!char MATCHALL = regex(".*"); 28 29 public: 30 31 this(string server, ushort port, string nickname, string password = null, string[] channels = []) 32 { 33 this.sock = new IRCSocket(server.dup, port); 34 this.nickname = nickname; 35 this.password = password; 36 this.channels = channels; 37 this.running = true; 38 } 39 40 string name() { 41 return this.nickname; 42 } 43 44 void connect() 45 { 46 this.sock.connect(); 47 48 if (!this.sock.connected()) { 49 throw new Exception("Could not connect to irc server!"); 50 } 51 52 if (this.password) { 53 this.sock.pass(this.password); 54 } 55 56 this.sock.nick(this.nickname); 57 this.sock.user(this.nickname, 0, "*", "ircbod"); 58 59 foreach(c; this.channels) { 60 this.sock.join(c); 61 } 62 } 63 64 bool connected() 65 { 66 return this.sock.connected(); 67 } 68 69 void disconnect() 70 { 71 this.sock.disconnect(); 72 } 73 74 void reconnect() 75 { 76 disconnect(); 77 connect(); 78 } 79 80 void on(IRCMessage.Type type, MessageHandler callback) 81 { 82 on(type, MATCHALL, callback); 83 } 84 85 void on(IRCMessage.Type type, MessageHandlerWithArgs callback) 86 { 87 on(type, MATCHALL, callback); 88 } 89 90 void on(IRCMessage.Type type, string pattern, MessageHandler callback) 91 { 92 on(type, regex(pattern), callback); 93 } 94 95 void on(IRCMessage.Type type, string pattern, MessageHandlerWithArgs callback) 96 { 97 on(type, regex(pattern), callback); 98 } 99 100 void on(IRCMessage.Type type, Regex!char regex, MessageHandler callback) 101 { 102 if(type == IRCMessage.Type.MESSAGE) { 103 on(IRCMessage.Type.CHAN_MESSAGE, regex, callback); 104 on(IRCMessage.Type.PRIV_MESSAGE, regex, callback); 105 return; 106 } 107 108 PatternMessageHandler handler = { callback, null, regex }; 109 if(type !in this.handlers) { 110 this.handlers[type] = HandlerList([handler]); 111 } else { 112 this.handlers[type].insertBack(handler); 113 } 114 } 115 116 void on(IRCMessage.Type type, Regex!char regex, MessageHandlerWithArgs callback) 117 { 118 if(type == IRCMessage.Type.MESSAGE) { 119 on(IRCMessage.Type.CHAN_MESSAGE, regex, callback); 120 on(IRCMessage.Type.PRIV_MESSAGE, regex, callback); 121 return; 122 } 123 124 PatternMessageHandler handler = { null, callback, regex }; 125 if(type !in this.handlers) { 126 this.handlers[type] = HandlerList([handler]); 127 } else { 128 this.handlers[type].insertBack(handler); 129 } 130 } 131 132 133 void run() 134 { 135 if(!connected()) 136 connect(); 137 138 scope(exit) disconnect(); 139 140 string line; 141 while (this.running && (line = this.sock.read()).length > 0) { 142 std.stdio.writeln(line); 143 processLine(line); 144 } 145 } 146 147 bool isRunning() 148 { 149 return this.running; 150 } 151 152 void quit() 153 { 154 this.running = false; 155 } 156 157 void sendMessageToChannel(string message, string channel) 158 { 159 this.sock.privmsg(channel, message); 160 } 161 162 void sendMessageToUser(string message, string nickname) 163 { 164 this.sock.privmsg(nickname, message); 165 } 166 167 void broadcast(string message) 168 { 169 foreach(c; this.channels) { 170 sendMessageToChannel(message, c); 171 } 172 } 173 174 private: 175 176 IRCMessage.Type typeForString(string typeStr) 177 { 178 IRCMessage.Type type; 179 switch(typeStr) { 180 case "JOIN": 181 return IRCMessage.Type.JOIN; 182 case "PART": 183 return IRCMessage.Type.PART; 184 case "QUIT": 185 return IRCMessage.Type.QUIT; 186 default: 187 return IRCMessage.Type.CHAN_MESSAGE; 188 } 189 } 190 191 void processLine(string message) 192 { 193 if (auto matcher = match(message, r"^:(\S+)\!\S+ (JOIN|PART|QUIT) :?(\S+).*")) { 194 auto user = matcher.captures[1]; 195 auto typeStr = matcher.captures[2]; 196 auto channel = matcher.captures[3]; 197 auto time = to!DateTime(Clock.currTime()); 198 auto type = typeForString(typeStr); 199 IRCMessage ircMessage = { 200 type, 201 typeStr, 202 user, 203 channel, 204 time, 205 this 206 }; 207 208 return handleMessage(ircMessage); 209 } 210 211 if (auto matcher = match(message, r"^:(\S+)\!\S+ PRIVMSG (\S+) :(.*)$")) { 212 auto user = matcher.captures[1]; 213 auto channel = matcher.captures[2]; 214 auto text = matcher.captures[3]; 215 auto time = to!DateTime(Clock.currTime()); 216 auto type = channel[0] == '#' ? IRCMessage.Type.CHAN_MESSAGE : IRCMessage.Type.PRIV_MESSAGE; 217 IRCMessage ircMessage = { 218 type, 219 text, 220 user, 221 channel, 222 time, 223 this 224 }; 225 226 return handleMessage(ircMessage); 227 } 228 229 if (auto matcher = match(message, r"^PING (.+)$")) { 230 auto server = matcher.captures[1]; 231 this.sock.pong(server); 232 } 233 } 234 235 void handleMessage(IRCMessage message) 236 { 237 if(message.type in this.handlers) { 238 foreach(PatternMessageHandler h; this.handlers[message.type]) { 239 if(auto matcher = match(message.text, h.pattern)) { 240 string[] args = []; 241 foreach(string m; matcher.captures) { 242 args ~= m; 243 } 244 if(h.callback) 245 h.callback(message); 246 if(h.callbackWithArgs) 247 h.callbackWithArgs(message, args[1..$]); 248 } 249 } 250 } 251 } 252 }