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 }