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 }