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