1 /++
2 +
3 +/
4 module virc.numerics.misc;
5 
6 import virc.numerics.definitions;
7 
8 import std.typecons : Nullable;
9 /++
10 +
11 +/
12 auto parseNumeric(Numeric numeric)() if (numeric.among(noInformationNumerics)) {
13 	static assert(0, "Cannot parse "~numeric~": No information to parse.");
14 }
15 /++
16 +
17 +/
18 struct TopicWhoTime {
19 	import std.datetime : SysTime;
20 	import virc.common : User;
21 	User me;
22 	///Channel that the topic was set on.
23 	string channel;
24 	///The nickname or full mask of the user who set the topic.
25 	User setter;
26 	///The time the topic was set. Will always be UTC.
27 	SysTime timestamp;
28 }
29 /++
30 + Parse RPL_TOPICWHOTIME (aka RPL_TOPICTIME) numeric replies.
31 +
32 + Format is `333 <user> <channel> <setter> <timestamp>`
33 +/
34 auto parseNumeric(Numeric numeric : Numeric.RPL_TOPICWHOTIME, T)(T input) {
35 	import virc.numerics.magicparser : autoParse;
36 	return autoParse!TopicWhoTime(input);
37 }
38 ///
39 @safe pure nothrow unittest {
40 	import std.datetime : DateTime, SysTime, UTC;
41 	import std.range : only, takeNone;
42 	{
43 		immutable result = parseNumeric!(Numeric.RPL_TOPICWHOTIME)(only("Someone", "#test", "Another!id@hostmask", "1496101944"));
44 		assert(result.get.channel == "#test");
45 		assert(result.get.setter.nickname == "Another");
46 		assert(result.get.setter.ident == "id");
47 		assert(result.get.setter.host == "hostmask");
48 		static immutable time = SysTime(DateTime(2017, 05, 29, 23, 52, 24), UTC());
49 		assert(result.get.timestamp == time);
50 	}
51 	{
52 		immutable badResult = parseNumeric!(Numeric.RPL_TOPICWHOTIME)(takeNone(only("")));
53 		assert(badResult.isNull);
54 	}
55 	{
56 		immutable badResult = parseNumeric!(Numeric.RPL_TOPICWHOTIME)(only("Someone"));
57 		assert(badResult.isNull);
58 	}
59 	{
60 		immutable badResult = parseNumeric!(Numeric.RPL_TOPICWHOTIME)(only("Someone", "#test"));
61 		assert(badResult.isNull);
62 	}
63 	{
64 		immutable badResult = parseNumeric!(Numeric.RPL_TOPICWHOTIME)(only("Someone", "#test", "Another!id@hostmask"));
65 		assert(badResult.isNull);
66 	}
67 	{
68 		immutable badResult = parseNumeric!(Numeric.RPL_TOPICWHOTIME)(only("Someone", "#test", "Another!id@hostmask", "invalidTimestamp"));
69 		assert(badResult.isNull);
70 	}
71 }
72 
73 struct NoPrivsError {
74 	import virc.common : User;
75 	User me;
76 	///The missing privilege that prompted this error reply.
77 	string priv;
78 	///Human-readable error message.
79 	string message;
80 }
81 /++
82 + Parse ERR_NOPRIVS numeric replies.
83 +
84 + Format is `723 <user> <priv> :Insufficient oper privileges.`
85 +/
86 auto parseNumeric(Numeric numeric : Numeric.ERR_NOPRIVS, T)(T input) {
87 	import virc.numerics.magicparser : autoParse;
88 	return autoParse!NoPrivsError(input);
89 }
90 ///
91 @safe pure nothrow unittest {
92 	import std.range : only, takeNone;
93 	{
94 		immutable result = parseNumeric!(Numeric.ERR_NOPRIVS)(only("Someone", "rehash", "Insufficient oper privileges."));
95 		assert(result.get.priv == "rehash");
96 		assert(result.get.message == "Insufficient oper privileges.");
97 	}
98 	{
99 		immutable badResult = parseNumeric!(Numeric.ERR_NOPRIVS)(takeNone(only("")));
100 		assert(badResult.isNull);
101 	}
102 	{
103 		immutable badResult = parseNumeric!(Numeric.ERR_NOPRIVS)(only("Someone"));
104 		assert(badResult.isNull);
105 	}
106 }
107 /++
108 + Parser for RPL_WHOISSECURE
109 +
110 + Format is `671 <client> <nick> :is using a secure connection`
111 +/
112 auto parseNumeric(Numeric numeric : Numeric.RPL_WHOISSECURE, T)(T input) {
113 	import virc.numerics.magicparser : autoParse;
114 	import virc.numerics.rfc1459 : InfolessWhoisReply;
115 	return autoParse!InfolessWhoisReply(input);
116 }
117 ///
118 @safe pure nothrow unittest {
119 	import virc.common : User;
120 	import std.range : only, takeNone;
121 	{
122 		auto reply = parseNumeric!(Numeric.RPL_WHOISSECURE)(only("someone", "whoisuser", "is using a secure connection"));
123 		assert(reply.get.user == User("whoisuser"));
124 	}
125 	{
126 		immutable reply = parseNumeric!(Numeric.RPL_WHOISSECURE)(only("someone", "whoisuser"));
127 		assert(reply.isNull);
128 	}
129 	{
130 		immutable reply = parseNumeric!(Numeric.RPL_WHOISSECURE)(only("someone"));
131 		assert(reply.isNull);
132 	}
133 	{
134 		immutable reply = parseNumeric!(Numeric.RPL_WHOISSECURE)(takeNone(only("")));
135 		assert(reply.isNull);
136 	}
137 }
138 /++
139 + Parser for RPL_WHOISREGNICK
140 +
141 + Format is `307 <client> <nick> :is a registered nick`
142 +/
143 auto parseNumeric(Numeric numeric : Numeric.RPL_WHOISREGNICK, T)(T input) {
144 	import virc.numerics.magicparser : autoParse;
145 	import virc.numerics.rfc1459 : InfolessWhoisReply;
146 	return autoParse!InfolessWhoisReply(input);
147 }
148 ///
149 @safe pure nothrow unittest {
150 	import virc.common : User;
151 	import std.range : only, takeNone;
152 	{
153 		auto reply = parseNumeric!(Numeric.RPL_WHOISREGNICK)(only("someone", "whoisuser", "is a registered nick"));
154 		assert(reply.get.user == User("whoisuser"));
155 	}
156 	{
157 		immutable reply = parseNumeric!(Numeric.RPL_WHOISREGNICK)(only("someone", "whoisuser"));
158 		assert(reply.isNull);
159 	}
160 	{
161 		immutable reply = parseNumeric!(Numeric.RPL_WHOISREGNICK)(only("someone"));
162 		assert(reply.isNull);
163 	}
164 	{
165 		immutable reply = parseNumeric!(Numeric.RPL_WHOISREGNICK)(takeNone(only("")));
166 		assert(reply.isNull);
167 	}
168 }
169 struct WhoisAccountReply {
170 	import virc.common : User;
171 	User me;
172 	///User who is being queried.
173 	User user;
174 	///Account name for this user.
175 	string account;
176 	///Human-readable numeric message.
177 	string message;
178 }
179 /++
180 + Parser for RPL_WHOISACCOUNT
181 +
182 + Format is `330 <client> <nick> <account> :is logged in as`
183 +/
184 auto parseNumeric(Numeric numeric : Numeric.RPL_WHOISACCOUNT, T)(T input) {
185 	import virc.numerics.magicparser : autoParse;
186 	return autoParse!WhoisAccountReply(input);
187 }
188 ///
189 @safe pure nothrow unittest {
190 	import virc.common : User;
191 	import std.range : only, takeNone;
192 	{
193 		auto reply = parseNumeric!(Numeric.RPL_WHOISACCOUNT)(only("someone", "whoisuser", "accountname", "is logged in as"));
194 		assert(reply.get.user == User("whoisuser"));
195 		assert(reply.get.account == "accountname");
196 	}
197 	{
198 		immutable reply = parseNumeric!(Numeric.RPL_WHOISACCOUNT)(only("someone", "whoisuser", "accountname"));
199 		assert(reply.isNull);
200 	}
201 	{
202 		immutable reply = parseNumeric!(Numeric.RPL_WHOISACCOUNT)(only("someone", "whoisuser"));
203 		assert(reply.isNull);
204 	}
205 	{
206 		immutable reply = parseNumeric!(Numeric.RPL_WHOISACCOUNT)(only("someone"));
207 		assert(reply.isNull);
208 	}
209 	{
210 		immutable reply = parseNumeric!(Numeric.RPL_WHOISACCOUNT)(takeNone(only("")));
211 		assert(reply.isNull);
212 	}
213 }
214 struct WHOXReply {
215 	Nullable!string token;
216 	Nullable!string channel;
217 	Nullable!string ident;
218 	Nullable!string ip;
219 	Nullable!string host;
220 	Nullable!string server;
221 	Nullable!string nick;
222 	Nullable!string flags;
223 	Nullable!string hopcount;
224 	Nullable!string idle;
225 	Nullable!string account;
226 	Nullable!string oplevel;
227 	Nullable!string realname;
228 }
229 /++
230 + Parser for RPL_WHOSPCRPL
231 +
232 + Format is `354 <client> [token] [channel] [user] [ip] [host] [server] [nick] [flags] [hopcount] [idle] [account] [oplevel] [:realname]`
233 +/
234 auto parseNumeric(Numeric numeric : Numeric.RPL_WHOSPCRPL, T)(T input, string flags) {
235 	WHOXReply reply;
236 	enum map = [
237 		't': 0,
238 		'c': 1,
239 		'u': 2,
240 		'i': 3,
241 		'h': 4,
242 		's': 5,
243 		'n': 6,
244 		'f': 7,
245 		'd': 8,
246 		'l': 9,
247 		'a': 10,
248 		'o': 11,
249 		'r': 12,
250 	];
251 	if (input.empty) {
252 		return Nullable!WHOXReply.init;
253 	}
254 	input.popFront();
255 	bool[13] expectedFields;
256 	foreach (flag; flags) {
257 		expectedFields[map.get(flag, throw new Exception("Flag not supported"))] = true;
258 	}
259 	size_t currentField;
260 	static foreach (idx; 0 .. WHOXReply.tupleof.length) {
261 		if (expectedFields[idx]) {
262 			if (input.empty) {
263 				return Nullable!WHOXReply.init;
264 			}
265 			static if (idx == map['a']) {
266 				if (input.front != "0") {
267 					reply.tupleof[idx] = input.front;
268 				}
269 			} else static if (idx == map['c']) {
270 				if (input.front != "*") {
271 					reply.tupleof[idx] = input.front;
272 				}
273 			} else static if (idx == map['i']) {
274 				if (input.front != "255.255.255.255") {
275 					reply.tupleof[idx] = input.front;
276 				}
277 			} else {
278 				reply.tupleof[idx] = input.front;
279 			}
280 			input.popFront();
281 		}
282 	}
283 	return Nullable!WHOXReply(reply);
284 }
285 ///
286 @safe pure unittest {
287 	import virc.common : User;
288 	import std.range : only, takeNone;
289 	{
290 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick", "#ircv3", "~cooluser", "coolhost", "cooluser", "coolaccount", "Cool User"), "cuhnar");
291 		assert(reply.get.channel.get == "#ircv3");
292 		assert(reply.get.ident.get == "~cooluser");
293 		assert(reply.get.host.get == "coolhost");
294 		assert(reply.get.nick.get == "cooluser");
295 		assert(reply.get.account.get == "coolaccount");
296 		assert(reply.get.realname.get == "Cool User");
297 	}
298 	{
299 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick", "#ircv3", "~cooluser", "coolhost", "cooluser", "0", "Cool User"), "cuhnar");
300 		assert(reply.get.channel.get == "#ircv3");
301 		assert(reply.get.ident.get == "~cooluser");
302 		assert(reply.get.host.get == "coolhost");
303 		assert(reply.get.nick.get == "cooluser");
304 		assert(reply.get.account.isNull);
305 		assert(reply.get.realname.get == "Cool User");
306 	}
307 	{
308 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick", "#ircv3", "~cooluser", "coolhost", "cooluser", "coolaccount"), "cuhnar");
309 		assert(reply.isNull);
310 	}
311 	{
312 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick", "#ircv3", "~cooluser", "coolhost", "cooluser"), "cuhnar");
313 		assert(reply.isNull);
314 	}
315 	{
316 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick", "#ircv3", "~cooluser", "coolhost"), "cuhnar");
317 		assert(reply.isNull);
318 	}
319 	{
320 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick", "#ircv3", "~cooluser"), "cuhnar");
321 		assert(reply.isNull);
322 	}
323 	{
324 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick", "#ircv3"), "cuhnar");
325 		assert(reply.isNull);
326 	}
327 	{
328 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(only("mynick"), "cuhnar");
329 		assert(reply.isNull);
330 	}
331 	{
332 		immutable reply = parseNumeric!(Numeric.RPL_WHOSPCRPL)(takeNone(only("")), "cuhnar");
333 		assert(reply.isNull);
334 	}
335 }