077334ad66511806e776b4c5e8aaf3b3aaef967e
[cloogle-irc.git] / IRC.icl
1 implementation module IRC
2
3 import StdList
4 import GenPrint
5 import StdOverloaded
6 import Data.Maybe
7 import Data.Either
8 import StdFunc
9 import StdString
10 import StdChar
11 import StdBool
12
13 import Text.Parsers.Simple.Core
14 import Text.Parsers.Simple.Chars
15 import Data.Tuple
16 import Control.Monad
17 import Control.Applicative
18 from Data.Functor import <$>
19
20 from Data.Func import $
21 from Text import class Text(indexOf,concat), instance Text String
22 import qualified Text
23 from StdMisc import undef
24
25 jon :== 'Text'.join
26
27 derive gPrint IRCCommand, IRCReplies, IRCErrors, (,), Maybe, (), Either
28
29 //Start = runParser parseMessage $ fromString ":frobnicator!~frobnicat@92.110.128.124 QUIT\r\n"
30 //Start = runParser parseMessage $ fromString ":frobnicator!~frobnicat@92.110.128.124 AWAY test\r\n"
31 //Start = runParser parseMessage $ fromString ":frobnicator!~frobnicat@92.110.128.124 AWAY :test with spaces\r\n"
32 //Start = runParser parseMessage $ fromString ":cherryh.freenode.net NOTICE * :*** Found your hostname\r\n"
33 Start = runParser parseMessage $ fromString ":cherryh.freenode.net QUIT\r\n"
34
35 (<+) infixr 5 :: a b -> String | toString a & toString b
36 (<+) a b = toString a +++ toString b
37
38 parseIRCMessage :: (String -> Either [Error] IRCMessage)
39 parseIRCMessage = parse parseMessage o fromString
40
41 parseMessage :: Parser Char IRCMessage
42 parseMessage = optional (parseEither parseHost parseUser) <* spaceParser
43 >>= \mprefix->parseCommand
44 <* pToken '\r' <* pToken '\n'
45 >>= \cmd->pure {IRCMessage | irc_prefix=mprefix, irc_command=cmd}
46
47 spaceParser :: Parser Char [Char]
48 spaceParser = pMany $ pToken ' '
49
50 parseServer :: Parser Char String
51 parseServer = pFail
52
53 parseEither :: (Parser a b) (Parser a c) -> Parser a (Either b c)
54 parseEither p q = Left <$> p <|> Right <$> q
55
56 parseUser :: Parser Char IRCUser
57 parseUser = pToken ':' >>| parseNick
58 >>= \nick->optional (pToken '!' >>| parseUsr)
59 >>= \muser->optional (pToken '@' >>| parseHost)
60 >>= \mhost->pure {IRCUser | irc_nick=nick, irc_user=muser, irc_host=mhost}
61
62 parseUsr :: Parser Char String
63 parseUsr = toString <$> pSome (pNoneOf [' ', '@':illegal])
64
65 parseNick :: Parser Char String
66 parseNick = pAlpha >>= \c->pMany (pAlpha <|> pDigit <|> pSpecial)
67 >>= \cs->pure (toString [c:cs])
68
69 pSpecial :: Parser Char Char
70 pSpecial = pOneOf ['-', '[', ']', '\\', '\`', '^', '{', '}']
71
72 parseHost :: Parser Char String
73 parseHost = pToken ':' >>| parseName
74 >>= \nm->pMany (pToken '.' >>| parseName)
75 >>= \nms->pure (concat [nm:nms])
76 where
77 parseName :: Parser Char String
78 parseName = toString <$> pSome (pAlpha <|> pDigit <|> pToken '.')
79
80 instance toString IRCMessage where
81 toString m = maybe "" (\s->either id ((<+) ":") s <+ " ") m.irc_prefix <+ m.irc_command
82
83 instance toString IRCUser where
84 toString m = m.irc_nick <+ maybe "" ((<+) "!") m.irc_user
85 <+ maybe "" ((<+) "@") m.irc_host
86
87 cons :: a [a] -> [a]
88 cons a as = [a:as]
89
90 pMiddle :: Parser Char String
91 pMiddle = fmap toString $
92 spaceParser >>| liftM2 cons (pNotSatisfy ((==)':')) (pMany $ pNoneOf [' ':illegal])
93
94 pTrailing :: Parser Char String
95 pTrailing = fmap toString $
96 spaceParser >>| pToken ':' >>| pMany (pNoneOf illegal)
97
98 pParam :: Parser Char String
99 pParam = pMiddle <|> pTrailing
100
101 pNoneOf :: [a] -> Parser a a | Eq a
102 pNoneOf l = pSatisfy (not o flip isMember l)
103
104 pNotSatisfy :: (a -> Bool) -> Parser a a | Eq a
105 pNotSatisfy f = pSatisfy (not o f)
106
107 pInt :: Parser Char Int
108 pInt = toInt o toString <$> (spaceParser >>| pSome pDigit)
109
110 illegal :: [Char]
111 illegal = ['\x00','\r','\n']
112
113 pCommand :: String -> Parser Char [Char]
114 pCommand s = pList (map pToken $ fromString s)
115
116 pCommand0 :: String IRCCommand -> Parser Char IRCCommand
117 pCommand0 s c = pCommand s >>| pure c
118
119 pCommand1 :: String (Parser Char a) (a -> IRCCommand) -> Parser Char IRCCommand
120 pCommand1 s p c = pCommand s >>| liftM c p
121
122 pCommand2 :: String (Parser Char a) (Parser Char b) (a b -> IRCCommand) -> Parser Char IRCCommand
123 pCommand2 s p q c = pCommand s >>| liftM2 c p q
124
125 pCommand3 :: String (Parser Char a) (Parser Char b) (Parser Char c) (a b c -> IRCCommand) -> Parser Char IRCCommand
126 pCommand3 s p q r c = pCommand s >>| liftM3 c p q r
127
128 pCommand4 :: String (Parser Char a) (Parser Char b) (Parser Char c) (Parser Char d) (a b c d -> IRCCommand) -> Parser Char IRCCommand
129 pCommand4 s p q r t c = pCommand s >>| liftM4 c p q r t
130
131 pCommand5 :: String (Parser Char a) (Parser Char b) (Parser Char c) (Parser Char d) (Parser Char e) (a b c d e -> IRCCommand) -> Parser Char IRCCommand
132 pCommand5 s p q r t u c = pCommand s >>| liftM5 c p q r t u
133
134 pMode :: Parser Char String
135 pMode = toString <$> pSome (pOneOf ['+','-','o','p','i','t','n','b','v','w','s'])
136
137 parseCommand :: Parser Char IRCCommand
138 parseCommand =
139 pCommand1 "ADMIN" (optional pMiddle) ADMIN
140 <|> pCommand1 "AWAY" pParam AWAY
141 <|> pCommand2 "CONNECT" pParam (optional $ liftM2 tuple pInt (optional pParam)) CONNECT
142 <|> pCommand0 "DIE" DIE
143 <|> pCommand1 "ERROR" pParam ERROR
144 <|> pCommand1 "INFO" (optional pParam) INFO
145 <|> pCommand2 "INVITE" pMiddle pMiddle INVITE
146 <|> pCommand1 "ISON" (pSome pMiddle) ISON
147 <|> pCommand1 "JOIN" (pSepBy (liftM2 tuple pMiddle $ optional pMiddle) pComma) JOIN
148 <|> pCommand3 "KICK" pMiddle pMiddle (optional pParam) KICK
149 <|> pCommand2 "KILL" pMiddle pParam KILL
150 <|> pCommand1 "LINKS" (optional $ liftM2 tuple (optional pMiddle) pMiddle) LINKS
151 <|> pCommand1 "LIST" (optional $ liftM2 tuple (pSepBy pMiddle pComma) $ optional pMiddle) LIST
152 <|> pCommand1 "LUSERS" (optional $ liftM2 tuple pMiddle $ optional pMiddle) LUSERS
153 <|> pCommand5 "MODE" pMiddle pMode (optional pMiddle) (optional pMiddle) (optional pMiddle) MODE
154 <|> pCommand1 "MOTD" (optional pMiddle) MOTD
155 <|> pCommand1 "NAMES" (pSepBy pMiddle pComma) NAMES
156 //NJOIN
157 <|> pCommand2 "NOTICE" pParam pParam NOTICE
158 //OPER String String
159 //PART [String]
160 //PASS String
161 <|> pCommand2 "PING" pMiddle (optional pMiddle) PING
162 <|> pCommand2 "PONG" pMiddle (optional pMiddle) PONG
163 <|> pCommand2 "PRIVMSG" (pSepBy pMiddle pComma) pParam PRIVMSG
164 <|> pCommand1 "QUIT" (optional pParam) QUIT
165 //REHASH
166 //RESTART
167 //SERVER
168 //SERVICE String String String String
169 //SERVLIST (Maybe (String, Maybe String))
170 //SQUERY String String
171 //SQUIRT
172 //SQUIT String String
173 //STATS (Maybe (String, Maybe String))
174 //SUMMON String (Maybe (String, Maybe String))
175 //TIME (Maybe String)
176 //TOPIC String (Maybe String)
177 //TRACE (Maybe String)
178 <|> pCommand3 "USER" pMiddle pMiddle (pMiddle >>| pParam) USER
179 //USERHOST [String]
180 //USERS (Maybe String)
181 //VERSION (Maybe String)
182 //WALLOPS String
183 //WHO (Maybe String)
184 //WHOIS (Maybe String) [String]
185 //WHOWAS (Maybe String) [String]
186
187 instance toString IRCCommand where
188 toString r = jon " " (print r) +++ "\r\n"
189
190 print :: IRCCommand -> [String]
191 print r = case r of
192 ADMIN mm = ["ADMIN":maybeToList mm]
193 //AWAY String
194 //CONNECT String (Maybe (Int, Maybe String))
195 //DIE
196 //ERROR String
197 //INFO (Maybe String)
198 //INVITE String String
199 //ISON [String]
200 JOIN chs = ["JOIN",if (isEmpty chs) "0"
201 (jon ", " [jon " " [ch:maybeToList mk]\\(ch, mk)<-chs])]
202 //KICK String String (Maybe String)
203 //KILL String String
204 //LINKS (Maybe (Maybe String, String))
205 //LIST (Maybe ([String], Maybe String))
206 //LUSERS (Maybe (String, Maybe String))
207 //MODE String String (Maybe String) (Maybe String) (Maybe String)
208 //MOTD (Maybe String)
209 //NAMES [String]
210 NICK n ms = ["NICK", n]
211 //NJOIN
212 //NOTICE String String
213 //OPER String String
214 //PART [String]
215 //PASS String
216 PING a mb = ["PING",a:maybeToList mb]
217 PONG a mb = ["PONG",a:maybeToList mb]
218 PRIVMSG dest msg = ["PRIVMSG",jon "," dest,formatMSG msg]
219 QUIT msg = ["QUIT":maybeToList msg]
220 //REHASH
221 //RESTART
222 //SERVER
223 //SERVICE String String String String
224 //SERVLIST (Maybe (String, Maybe String))
225 //SQUERY String String
226 //SQUIRT
227 //SQUIT String String
228 //STATS (Maybe (String, Maybe String))
229 //SUMMON String (Maybe (String, Maybe String))
230 //TIME (Maybe String)
231 //TOPIC String (Maybe String)
232 //TRACE (Maybe String)
233 USER login mode rn = ["USER", login, mode, "*", ":"+++rn]
234 //USERHOST [String]
235 //USERS (Maybe String)
236 //VERSION (Maybe String)
237 //WALLOPS String
238 //WHO (Maybe String)
239 //WHOIS (Maybe String) [String]
240 //WHOWAS (Maybe String) [String]
241 _ = [printToString r]
242
243 formatMSG :: String -> String
244 formatMSG s = if (indexOf " " s > 0 || indexOf " " s > 0) (":" +++ s) s
245
246
247 instance toString IRCReplies where toString r = printToString r
248 instance toString IRCErrors where toString r = printToString r
249
250 instance fromInt IRCReplies where
251 fromInt r = case r of
252 1 = RPL_WELCOME
253 2 = RPL_YOURHOST
254 3 = RPL_CREATED
255 4 = RPL_MYINFO
256 5 = RPL_BOUNCE
257 200 = RPL_TRACELINK
258 201 = RPL_TRACECONNECTING
259 202 = RPL_TRACEHANDSHAKE
260 203 = RPL_TRACEUNKNOWN
261 204 = RPL_TRACEOPERATOR
262 205 = RPL_TRACEUSER
263 206 = RPL_TRACESERVER
264 207 = RPL_TRACESERVICE
265 208 = RPL_TRACENEWTYPE
266 209 = RPL_TRACECLASS
267 210 = RPL_TRACERECONNECT
268 211 = RPL_STATSLINKINFO
269 212 = RPL_STATSCOMMANDS
270 219 = RPL_ENDOFSTATS
271 221 = RPL_UMODEIS
272 234 = RPL_SERVLIST
273 235 = RPL_SERVLISTEND
274 242 = RPL_STATSUPTIME
275 243 = RPL_STATSOLINE
276 251 = RPL_LUSERCLIENT
277 252 = RPL_LUSEROP
278 253 = RPL_LUSERUNKNOWN
279 254 = RPL_LUSERCHANNELS
280 255 = RPL_LUSERME
281 256 = RPL_ADMINME
282 257 = RPL_ADMINLOC1
283 258 = RPL_ADMINLOC2
284 259 = RPL_ADMINEMAIL
285 261 = RPL_TRACELOG
286 262 = RPL_TRACEEND
287 263 = RPL_TRYAGAIN
288 301 = RPL_AWAY
289 302 = RPL_USERHOST
290 303 = RPL_ISON
291 304 = RPL_UNAWAY
292 305 = RPL_NOWAWAY
293 311 = RPL_WHOISUSER
294 312 = RPL_WHOISSERVER
295 313 = RPL_WHOISOPERATOR
296 314 = RPL_WHOWASUSER
297 315 = RPL_ENDOFWHO
298 317 = RPL_WHOISIDLE
299 318 = RPL_ENDOFWHOIS
300 319 = RPL_WHOISCHANNELS
301 321 = RPL_LISTSTART
302 322 = RPL_LIST
303 323 = RPL_LISTEND
304 324 = RPL_CHANNELMODEIS
305 325 = RPL_UNIQOPIS
306 331 = RPL_NOTOPIC
307 332 = RPL_TOPIC
308 341 = RPL_INVITING
309 342 = RPL_SUMMONING
310 346 = RPL_INVITELIST
311 347 = RPL_ENDOFINVITELIST
312 348 = RPL_EXCEPTLIST
313 349 = RPL_ENDOFEXCEPTLIST
314 351 = RPL_VERSION
315 352 = RPL_WHOREPLY
316 353 = RPL_NAMREPLY
317 364 = RPL_LINKS
318 365 = RPL_ENDOFLINKS
319 366 = RPL_ENDOFNAMES
320 367 = RPL_BANLIST
321 368 = RPL_ENDOFBANLIST
322 369 = RPL_ENDOFWHOWAS
323 371 = RPL_INFO
324 372 = RPL_MOTD
325 374 = RPL_ENDOFINFO
326 375 = RPL_MOTDSTART
327 376 = RPL_ENDOFMOTD
328 381 = RPL_YOUREOPER
329 382 = RPL_REHASHING
330 383 = RPL_YOURESERVICE
331 391 = RPL_TIME
332 392 = RPL_USERSSTART
333 393 = RPL_USERS
334 394 = RPL_ENDOFUSERS
335 395 = RPL_NOUSERS
336 _ = undef
337
338 instance toInt IRCReplies where
339 toInt r = case r of
340 RPL_WELCOME = 1
341 RPL_YOURHOST = 2
342 RPL_CREATED = 3
343 RPL_MYINFO = 4
344 RPL_BOUNCE = 5
345 RPL_TRACELINK = 200
346 RPL_TRACECONNECTING = 201
347 RPL_TRACEHANDSHAKE = 202
348 RPL_TRACEUNKNOWN = 203
349 RPL_TRACEOPERATOR = 204
350 RPL_TRACEUSER = 205
351 RPL_TRACESERVER = 206
352 RPL_TRACESERVICE = 207
353 RPL_TRACENEWTYPE = 208
354 RPL_TRACECLASS = 209
355 RPL_TRACERECONNECT = 210
356 RPL_STATSLINKINFO = 211
357 RPL_STATSCOMMANDS = 212
358 RPL_ENDOFSTATS = 219
359 RPL_UMODEIS = 221
360 RPL_SERVLIST = 234
361 RPL_SERVLISTEND = 234
362 RPL_STATSUPTIME = 242
363 RPL_STATSOLINE = 243
364 RPL_LUSERCLIENT = 251
365 RPL_LUSEROP = 252
366 RPL_LUSERUNKNOWN = 253
367 RPL_LUSERCHANNELS = 254
368 RPL_LUSERME = 255
369 RPL_ADMINME = 256
370 RPL_ADMINLOC1 = 257
371 RPL_ADMINLOC2 = 258
372 RPL_ADMINEMAIL = 259
373 RPL_TRACELOG = 261
374 RPL_TRACEEND = 262
375 RPL_TRYAGAIN = 263
376 RPL_AWAY = 301
377 RPL_USERHOST = 302
378 RPL_ISON = 303
379 RPL_UNAWAY = 304
380 RPL_NOWAWAY = 305
381 RPL_WHOISUSER = 311
382 RPL_WHOISSERVER = 312
383 RPL_WHOISOPERATOR = 313
384 RPL_WHOWASUSER = 314
385 RPL_ENDOFWHO = 315
386 RPL_WHOISIDLE = 317
387 RPL_ENDOFWHOIS = 318
388 RPL_WHOISCHANNELS = 319
389 RPL_LISTSTART = 321
390 RPL_LIST = 322
391 RPL_LISTEND = 323
392 RPL_CHANNELMODEIS = 324
393 RPL_UNIQOPIS = 325
394 RPL_NOTOPIC = 331
395 RPL_TOPIC = 332
396 RPL_INVITING = 341
397 RPL_SUMMONING = 342
398 RPL_INVITELIST = 346
399 RPL_ENDOFINVITELIST = 347
400 RPL_EXCEPTLIST = 348
401 RPL_ENDOFEXCEPTLIST = 349
402 RPL_VERSION = 351
403 RPL_WHOREPLY = 352
404 RPL_NAMREPLY = 353
405 RPL_LINKS = 364
406 RPL_ENDOFLINKS = 365
407 RPL_ENDOFNAMES = 366
408 RPL_BANLIST = 367
409 RPL_ENDOFBANLIST = 367
410 RPL_ENDOFWHOWAS = 369
411 RPL_INFO = 371
412 RPL_MOTD = 372
413 RPL_ENDOFINFO = 374
414 RPL_MOTDSTART = 375
415 RPL_ENDOFMOTD = 376
416 RPL_YOUREOPER = 381
417 RPL_REHASHING = 382
418 RPL_YOURESERVICE = 383
419 RPL_TIME = 391
420 RPL_USERSSTART = 392
421 RPL_USERS = 393
422 RPL_ENDOFUSERS = 394
423 RPL_NOUSERS = 395
424
425 instance fromInt IRCErrors where
426 fromInt r = case r of
427 401 = ERR_NOSUCHNICK
428 402 = ERR_NOSUCHSERVER
429 403 = ERR_NOSUCHCHANNEL
430 404 = ERR_CANNOTSENDTOCHAN
431 405 = ERR_TOOMANYCHANNELS
432 406 = ERR_WASNOSUCHNICK
433 407 = ERR_TOOMANYTARGETS
434 408 = ERR_NOSUCHSERVICE
435 409 = ERR_NOORIGIN
436 411 = ERR_NORECIPIENT
437 412 = ERR_NOTEXTTOSEND
438 413 = ERR_NOTOPLEVEL
439 414 = ERR_WILDTOPLEVEL
440 415 = ERR_BADMASK
441 421 = ERR_UNKNOWNCOMMAND
442 422 = ERR_NOMOTD
443 423 = ERR_NOADMININFO
444 424 = ERR_FILEERROR
445 431 = ERR_NONICKNAMEGIVEN
446 432 = ERR_ERRONEUSNICKNAME
447 433 = ERR_NICKNAMEINUSE
448 436 = ERR_NICKCOLLISION
449 437 = ERR_UNAVAILRESOURCE
450 441 = ERR_USERNOTINCHANNEL
451 442 = ERR_NOTONCHANNEL
452 443 = ERR_USERONCHANNEL
453 444 = ERR_NOLOGIN
454 445 = ERR_SUMMONDISABLED
455 446 = ERR_USERSDISABLED
456 451 = ERR_NOTREGISTERED
457 461 = ERR_NEEDMOREPARAMS
458 462 = ERR_ALREADYREGISTRED
459 463 = ERR_NOPERMFORHOST
460 464 = ERR_PASSWDMISMATCH
461 465 = ERR_YOUREBANNEDCREEP
462 466 = ERR_YOUWILLBEBANNED
463 467 = ERR_KEYSET
464 471 = ERR_CHANNELISFULL
465 472 = ERR_UNKNOWNMODE
466 473 = ERR_INVITEONLYCHAN
467 474 = ERR_BANNEDFROMCHAN
468 475 = ERR_BADCHANNELKEY
469 476 = ERR_BADCHANMASK
470 477 = ERR_NOCHANMODES
471 478 = ERR_BANLISTFULL
472 481 = ERR_NOPRIVILEGES
473 482 = ERR_CHANOPRIVSNEEDED
474 483 = ERR_CANTKILLSERVER
475 484 = ERR_RESTRICTED
476 485 = ERR_UNIQOPPRIVSNEEDED
477 491 = ERR_NOOPERHOST
478 501 = ERR_UMODEUNKNOWNFLAG
479 502 = ERR_USERSDONTMATCH
480
481 instance toInt IRCErrors where
482 toInt r = case r of
483 ERR_NOSUCHNICK = 401
484 ERR_NOSUCHSERVER = 402
485 ERR_NOSUCHCHANNEL = 403
486 ERR_CANNOTSENDTOCHAN = 404
487 ERR_TOOMANYCHANNELS = 405
488 ERR_WASNOSUCHNICK = 406
489 ERR_TOOMANYTARGETS = 407
490 ERR_NOSUCHSERVICE = 408
491 ERR_NOORIGIN = 409
492 ERR_NORECIPIENT = 411
493 ERR_NOTEXTTOSEND = 412
494 ERR_NOTOPLEVEL = 413
495 ERR_WILDTOPLEVEL = 414
496 ERR_BADMASK = 415
497 ERR_UNKNOWNCOMMAND = 421
498 ERR_NOMOTD = 422
499 ERR_NOADMININFO = 423
500 ERR_FILEERROR = 424
501 ERR_NONICKNAMEGIVEN = 431
502 ERR_ERRONEUSNICKNAME = 432
503 ERR_NICKNAMEINUSE = 433
504 ERR_NICKCOLLISION = 436
505 ERR_UNAVAILRESOURCE = 437
506 ERR_USERNOTINCHANNEL = 441
507 ERR_NOTONCHANNEL = 442
508 ERR_USERONCHANNEL = 443
509 ERR_NOLOGIN = 444
510 ERR_SUMMONDISABLED = 445
511 ERR_USERSDISABLED = 446
512 ERR_NOTREGISTERED = 451
513 ERR_NEEDMOREPARAMS = 461
514 ERR_ALREADYREGISTRED = 462
515 ERR_NOPERMFORHOST = 463
516 ERR_PASSWDMISMATCH = 464
517 ERR_YOUREBANNEDCREEP = 465
518 ERR_YOUWILLBEBANNED = 466
519 ERR_KEYSET = 467
520 ERR_CHANNELISFULL = 471
521 ERR_UNKNOWNMODE = 472
522 ERR_INVITEONLYCHAN = 473
523 ERR_BANNEDFROMCHAN = 474
524 ERR_BADCHANNELKEY = 475
525 ERR_BADCHANMASK = 476
526 ERR_NOCHANMODES = 477
527 ERR_BANLISTFULL = 478
528 ERR_NOPRIVILEGES = 481
529 ERR_CHANOPRIVSNEEDED = 482
530 ERR_CANTKILLSERVER = 483
531 ERR_RESTRICTED = 484
532 ERR_UNIQOPPRIVSNEEDED = 485
533 ERR_NOOPERHOST = 491
534 ERR_UMODEUNKNOWNFLAG = 501
535 ERR_USERSDONTMATCH = 502