1 module testclient.app; 2 3 import std.algorithm; 4 import std.json; 5 import std.range; 6 import std.stdio; 7 import vibe.core.core; 8 import vibe.core.net; 9 import vibe.stream.operations; 10 import vibe.stream.stdio; 11 import vibe.stream.tls; 12 import vibe.stream.wrapper; 13 import virc; 14 import virc.client; 15 16 interface IStream : Output { 17 ubyte[] readLine(); 18 bool empty(); 19 } 20 21 class WrapStream : IStream { 22 Stream stream; 23 this(Stream stream) nothrow { 24 this.stream = stream; 25 } 26 override void put(char c) { 27 stream.write([c]); 28 } 29 override ubyte[] readLine() { 30 return stream.readLine(); 31 } 32 override bool empty() { 33 return stream.empty; 34 } 35 } 36 class WrapConnection : IStream { 37 TCPConnection connection; 38 this(TCPConnection conn) nothrow { 39 connection = conn; 40 } 41 override void put(char c) { 42 connection.write([c]); 43 } 44 override ubyte[] readLine() { 45 return connection.readLine(); 46 } 47 override bool empty() { 48 return connection.empty; 49 } 50 } 51 52 string currentChannel; 53 string[] channelsToJoin; 54 void autoJoinChannel(string chan) @safe { 55 channelsToJoin ~= chan; 56 } 57 58 auto runClient(JSONValue settings, IStream stream) { 59 import std.typecons; 60 auto client = ircClient(stream, NickInfo(settings["nickname"].str, settings["identd"].str, settings["real name"].str)); 61 void writeLine(string line) { 62 import std.string : toLower; 63 if (line.startsWith("/")) { 64 auto split = line[1..$].splitter(" "); 65 switch(split.front.toLower()) { 66 default: 67 write(line[1..$]); 68 break; 69 } 70 } else { 71 client.msg(currentChannel, line); 72 } 73 } 74 client.onMessage = (const User user, const Target target, const Message msg, const MessageMetadata metadata) @safe { 75 if (msg.isCTCP) { 76 if (msg.ctcpCommand == "ACTION") { 77 writefln("<%s> * %s %s", metadata.time, user, msg.ctcpArgs); 78 } else if (msg.ctcpCommand == "VERSION") { 79 client.ctcpReply(Target(user), "VERSION", "virc-testclient"); 80 } else { 81 writefln("<%s> [%s:%s] %s", metadata.time, user, msg.ctcpCommand, msg.ctcpArgs); 82 } 83 } else if (!msg.isReplyable) { 84 writefln("<%s> -%s- %s", metadata.time, user.nickname, msg.msg); 85 } else if (msg.isReplyable) { 86 writefln("<%s> <%s:%s> %s", metadata.time, user.nickname, target, msg.msg); 87 } 88 }; 89 90 client.onJoin = (const User user, const Channel channel, const MessageMetadata metadata) @safe { 91 writefln("<%s> *** %s joined %s", metadata.time, user, channel); 92 currentChannel = channel.name; 93 }; 94 95 client.onPart = (const User user, const Channel channel, const string message, const MessageMetadata metadata) @safe { 96 writefln("<%s> *** %s parted %s: %s", metadata.time, user, channel, message); 97 }; 98 99 client.onQuit = (const User user, const string message, const MessageMetadata metadata) @safe { 100 writefln("<%s> *** %s quit IRC: %s", metadata.time, user, message); 101 }; 102 103 client.onNick = (const User user, const User newname, const MessageMetadata metadata) @safe { 104 writefln("<%s> *** %s changed name to %s", metadata.time, user, newname); 105 }; 106 107 client.onKick = (const User user, const Channel channel, const User initiator, const string message, const MessageMetadata metadata) @safe { 108 writefln("<%s> *** %s was kicked from %s by %s: %s", metadata.time, user, channel, initiator, message); 109 }; 110 111 client.onLogin = (const User user, const MessageMetadata metadata) @safe { 112 writefln("<%s> *** %s logged in", metadata.time, user); 113 }; 114 115 client.onLogout = (const User user, const MessageMetadata metadata) @safe { 116 writefln("<%s> *** %s logged out", metadata.time, user); 117 }; 118 119 client.onOtherUserAwayReply = (const User user, const string message, const MessageMetadata metadata) @safe { 120 writefln("<%s> *** %s is away: %s", metadata.time, user, message); 121 }; 122 123 client.onBack = (const User user, const MessageMetadata metadata) @safe { 124 writefln("<%s> *** %s is no longer away", metadata.time, user); 125 }; 126 127 client.onTopicChange = (const User user, const Channel channel, const string topic, const MessageMetadata metadata) @safe { 128 writefln("<%s> *** %s changed topic on %s to %s", metadata.time, user, channel, topic); 129 }; 130 131 client.onMode = (const User user, const Target target, const ModeChange mode, const MessageMetadata metadata) @safe { 132 writefln("<%s> *** %s changed modes on %s: %s", metadata.time, user, target, mode); 133 }; 134 client.onWhois = (const User user, const WhoisResponse whoisResponse) @safe { 135 writefln("%s is %s@%s (%s)", user, whoisResponse.username, whoisResponse.hostname, whoisResponse.realname); 136 if (whoisResponse.isOper) { 137 writefln("%s is an IRC operator", user); 138 } 139 if (whoisResponse.isSecure) { 140 writefln("%s is on a secure connection", user); 141 } 142 if (whoisResponse.isRegistered && whoisResponse.account.isNull) { 143 writefln("%s is a registered nick", user); 144 } 145 if (!whoisResponse.account.isNull) { 146 writefln("%s is logged in as %s", user, whoisResponse.account); 147 } 148 if (!whoisResponse.idleTime.isNull) { 149 writefln("%s has been idle for %s", user, whoisResponse.idleTime.get); 150 } 151 if (!whoisResponse.connectedTime.isNull) { 152 writefln("%s connected on %s", user, whoisResponse.connectedTime.get); 153 } 154 if (!whoisResponse.connectedTo.isNull) { 155 writefln("%s is connected to %s", user, whoisResponse.connectedTo); 156 } 157 }; 158 client.onConnect = () @safe { 159 foreach (channel; channelsToJoin) { 160 client.join(channel); 161 } 162 }; 163 foreach (channel; settings["channels to join"].arrayNoRef) { 164 autoJoinChannel(channel.str); 165 } 166 167 void readIRC() nothrow { 168 try { 169 while(!stream.empty) { 170 put(client, stream.readLine().idup); 171 } 172 } catch (Exception e) { 173 assert(0, e.msg); 174 } 175 } 176 void readCLI() nothrow { 177 try { 178 auto standardInput = new StdinStream; 179 while (true) { 180 auto str = cast(string)readLine(standardInput); 181 writeLine(str); 182 } 183 } catch (Exception e) { 184 assert(0, e.msg); 185 } 186 } 187 runTask(&readIRC); 188 runTask(&readCLI); 189 return runApplication(); 190 } 191 192 int main() { 193 import std.file : exists, readText; 194 import std.json : JSONType, parseJSON; 195 if (exists("settings.json")) { 196 auto settings = readText("settings.json").parseJSON(); 197 auto conn = connectTCP(settings["address"].str, cast(ushort)settings["port"].integer); 198 Stream stream; 199 if (settings["ssl"].type == JSONType.true_) { 200 auto sslctx = createTLSContext(TLSContextKind.client); 201 sslctx.peerValidationMode = TLSPeerValidationMode.none; 202 try { 203 stream = createTLSStream(conn, sslctx); 204 } catch (Exception) { 205 writeln("SSL connection failed!"); 206 return 1; 207 } 208 return runClient(settings, new WrapStream(stream)); 209 } else { 210 return runClient(settings, new WrapConnection(conn)); 211 } 212 } else { 213 writeln("No settings file found"); 214 return 1; 215 } 216 }