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 }