graceful exit: fix #5
[cloogle-irc.git] / cloogle.icl
index 29cee80..b2c7bdf 100644 (file)
@@ -2,14 +2,16 @@ module cloogle
 
 import Cloogle
 import GenPrint
-import IRC
 import StdEnv
 
 import Data.Functor
 import Data.Maybe
-from Data.Func import $
+import Data.Either
+from Data.Func import $, mapSt
 from Text import class Text(..), instance Text String, instance + String
 
+import Internet.HTTP
+
 import Text.JSON
 
 import Text.URI
@@ -26,85 +28,17 @@ import Data.Functor
 import Data.Tuple
 
 import TCPIP
-
-commands :: [String]
-commands = map toString
-       [NICK "clooglebot"
-       ,USER "cloogle" 0 "Cloogle bot"
-       ,JOIN [("#cloogle", Nothing)]
-       ]
+import IRC
+import IRCBot
 
 TIMEOUT :== Just 10000
 SERVER :== "irc.freenode.net"
 
-KEY :== "PRIVMSG #cloogle :!"
-
-doRequest :: HTTPRequest *World -> *(MaybeErrorString HTTPResponse, *World)
-doRequest req w
-# (ip,w) = lookupIPAddress server_name w
-| isNothing ip
-       = (Error $ "DNS lookup for " + server_name + " failed.", w)
-# (Just ip) = ip
-# (rpt,chan,w) = connectTCP_MT TIMEOUT (ip, req.server_port) w
-| rpt == TR_Expired
-       = (Error $ "Connection to " + toString ip + " timed out.", w)
-| rpt == TR_NoSuccess
-       = (Error $ "Could not connect to " + server_name + ".", w)
-# (Just {sChannel,rChannel}) = chan
-# (rpt,i,sChannel,w) = send_MT TIMEOUT (toByteSeq req) sChannel w
-| rpt <> TR_Success
-       = (Error $ "Could not send request to " + server_name + ".", w)
-# (rpt,resp,rChannel,w) = receive_MT TIMEOUT rChannel w
-| rpt <> TR_Success
-       = (Error $ "Did not receive a reply from " + server_name + ".", w)
-# resp = 'CM'.join $ parseResponse <$> toString <$> resp
-| isNothing resp
-       # w = closeChannel sChannel (closeRChannel rChannel w)
-       = (Error $ "Server did not respond with HTTP.", w)
-# (resp,rChannel,w) = receiveRest (fromJust resp) rChannel w
-# w = closeChannel sChannel (closeRChannel rChannel w)
-= (resp,w)
-where
-       server_name = req.server_name
-       receiveRest resp chan w
-       # cl = lookup "Content-Length" resp.HTTPResponse.rsp_headers
-       | isNothing cl
-               = (Ok resp, chan, w)
-       | size resp.rsp_data >= toInt (fromJust cl)
-               = (Ok resp, chan, w)
-       # (rpt,newresp,chan,w) = receive_MT TIMEOUT chan w
-       | rpt <> TR_Success
-               = (Error $ server_name + " hung up during transmission.", chan, w)
-       = receiveRest {resp & rsp_data=resp.rsp_data + toString (fromJust newresp)} chan w
-
-import StdMisc
-import StdDebug
-
-doRequestL :: HTTPRequest Int *World -> *(MaybeErrorString HTTPResponse, *World)
-doRequestL req 0 w = (Error "Maximal redirect numbe exceeded", w)
-doRequestL req maxRedirects w
-| not (trace_tn $ toString req) = undef
-# (er, w) = doRequest req w
-| isError er = (er, w)
-# resp = fromOk er
-| isMember resp.HTTPResponse.rsp_code [301, 302, 303, 307, 308]
-       = case lookup "Location" resp.HTTPResponse.rsp_headers of
-               Nothing = (Error $ "Redirect given but no Location header", w)
-               Just loc = case parseURI loc of
-                       Nothing = (Error $ "Redirect URI couldn't be parsed", w)
-                       Just uri = doRequestL {req 
-                               & server_name = maybe loc id uri.uriRegName
-                               , server_port = maybe 80 id uri.uriPort
-                               , req_path = uri.uriPath
-                               , req_query = maybe "" ((+++) "?") uri.uriQuery
-                               } (maxRedirects-1) w
-= (er, w)
-
 shorten :: String *World -> (String, *World)
 shorten s w 
 # s = if (startsWith "http://" s) s (if (startsWith "https://" s) s ("http://" + s))
 # data = "type=regular&url="+urlEncode s+"&token=a"
-# (mer, w) = doRequest 
+# (mer, w) = doHTTPRequest 
                { newHTTPRequest
                & req_method = HTTP_POST
                , req_path = "/"
@@ -114,26 +48,27 @@ shorten s w
                        [("Content-Type", "application/x-www-form-urlencoded")
                        ,("Content-Length", toString $ size data)
                        ,("Accept", "*/*")]
-               , req_data = data} w
+               , req_data = data} 10000 w
 | isError mer = ("request failed: " + fromError mer, w)
 # resp = fromOk mer
 = (resp.rsp_data, w)
 
 cloogle :: String *World -> (String, *World)
 cloogle data w
-# (mer, w) = doRequestL
+# (mer, w) = doHTTPRequestL
                { newHTTPRequest
                & req_path = "/api.php"
                , req_query = "?str=" + urlEncode data
                , req_headers = 'DM'.fromList [("User-Agent", "cloogle-irc")]
                , server_name = "cloogle.org"
-               , server_port = 80} 10 w
+               , server_port = 80} 10000 10 w
 | isError mer = ("request failed: " + fromError mer, w)
 # resp = fromOk mer
 = case fromJSON $ fromString resp.HTTPResponse.rsp_data of
        Nothing = ("couldn't parse json", w)
-       Just clr = ("Results for " + data + " -- https://cloogle.org/#" + urlEncode data + "\n" +
-                       processResults clr, w)
+       Just clr = ("Results for " + data + " -- https://cloogle.org/#" +
+               replaceSubString "+" "%20" (urlEncode data) + "\n" +
+               processResults clr, w)
        where
                processResults :: Response -> String
                processResults resp
@@ -148,8 +83,6 @@ cloogle data w
                processResult (ClassResult (br, {class_name,class_funs}))
                        = "Class in " +++ br.library +++ ": " +++ br.modul +++ "\n" +++ class_name +++ " with "
                                +++ toString (length class_funs) +++ " class functions"
-               processResult (MacroResult (br, {macro_name}))
-                       = "Macro in " +++ br.library +++ ": " +++ br.modul +++ "\n" +++ macro_name
                processResult (ModuleResult (br, _))
                        = "Module in " +++ br.library +++ ": " +++ br.modul
 
@@ -164,81 +97,67 @@ cloogle data w
                | size s > 80 = subString 0 77 s + "..."
                = s
 
-send :: [String] TCP_DuplexChannel *World -> (TCP_DuplexChannel, *World)
-send [] chan w = (chan, w)
-send [msg:msgs] {sChannel,rChannel} w
-# (_, w) = sleep 250000 w
-# (rpt,i,sChannel,w) = send_MT TIMEOUT (toByteSeq msg) sChannel w
-| rpt <> TR_Success = abort "Could not send request\n"
-= send msgs {sChannel=sChannel,rChannel=rChannel} w
+
+Start :: *World -> (MaybeErrorString (), *World)
+Start w = bot ("irc.freenode.net", 6667) startup shutdown () process w
        where
-               sleep :: !Int !*World -> (!Int, *World)
-               sleep i w = code {
-                               ccall usleep "I:I:A"
-                       }
-
-recv :: TCP_DuplexChannel *World -> (Maybe String, TCP_DuplexChannel, *World)
-recv {sChannel,rChannel} w
-# (rpt, resp, rChannel, w) = receive_MT TIMEOUT rChannel w
-| rpt == TR_Expired = (Nothing, {sChannel=sChannel,rChannel=rChannel}, w)
-| rpt == TR_NoSuccess || isNothing resp = abort "Halp?\n"
-= (toString <$> resp, {sChannel=sChannel,rChannel=rChannel}, w)
-
-msg :: (String -> IRCCommands)
-msg = PRIVMSG "#cloogle"
-
-process :: *File TCP_DuplexChannel *World -> (*File, TCP_DuplexChannel, *World)
-process io chan w 
-# (mr, chan, w) = recv chan w
-| isNothing mr = process io chan w
-# resp = fromJust mr
-#! io = io <<< ("Received: " +++ resp +++ "\n")
-# ind = indexOf KEY resp
-| ind >= 0
-       # cmd = split " " $ rtrim $ subString (ind + size KEY) (size resp) resp
-       #! io =  io <<< ("Received command: " +++ printToString cmd +++ "\n")
-       # (w, toSend) = case cmd of
-               ["stop":_] = (w, Nothing)
-               ["ping":xs] = (w, Just [msg $ "pong " +++ join " " xs])
-               ["query":xs]
-                       # (s, w) = cloogle (join " " xs) w
-                       = (w, Just $ map msg $ split "\n" s)
-               ["short"] = (w, Just [msg $ "short requires an url argument"])
-               ["short":xs]
-                       # (s, w) = shorten (join " " xs) w
-                       = (w, Just [msg s])
-               ["help"] = (w, Just 
-                       [msg "type !help cmd for command specific help"
-                       ,msg "available commands: help, ping, query, short"])
-               ["help":c:_] = (w, case c of
-                       "help"  = Just [msg "help  [CMD] - I will print general help or the help of CMD"] 
-                       "ping"  = Just [msg "ping  [TXT] - I will reply with pong and the optionar TXT"] 
-                       "query" = Just [msg "query QUERY - I will send QUERY to cloogle and post the results"]
-                       "short" = Just [msg "short  URL  - I will give the url to https://cloo.gl shortening service and post back the result"]
-                       _ = Just [msg "Unknown command"])
-               [c:_] = (w, Just [msg $ join " " ["unknown command: " , c, ",  type !help to get help"]])
-       | isNothing toSend = (io, chan, w)
-       # (chan, w) = send (map toString $ fromJust toSend) chan w
-       = process io chan w
-| indexOf "PING :" resp >= 0
-       # cmd = rtrim $ subString (indexOf "PING :" resp + size "PING :") (size resp) resp
-       #! io = io <<< (toString $ PONG cmd Nothing) <<< "\n"
-       # (chan, w) = send [toString $ PONG cmd Nothing] chan w
-       = process io chan w
-= process io chan w
-
-Start :: *World -> *World
-Start w
-# (io, w) = stdio w
-# (ip, w) = lookupIPAddress SERVER w
-| isNothing ip = abort $ "DNS lookup for " +++ SERVER +++ " failed\n"
-# (Just ip) = ip
-# (rpt,chan,w) = connectTCP_MT TIMEOUT (ip, 6667) w
-| rpt == TR_Expired = abort $ "Connection to " +++ SERVER +++ " timed out\n"
-| rpt == TR_NoSuccess = abort $ "Could not connect to " +++ SERVER +++ "\n"
-# chan = fromJust chan
-# (chan, w) = send commands chan w
-# (io, chan, w) = process io chan w
-# ({sChannel,rChannel}, w) = send [toString $ QUIT Nothing] chan w
-# (_, w) = fclose io w
-= closeChannel sChannel (closeRChannel rChannel w)
+               toPrefix c = {irc_prefix=Nothing,irc_command=Right c}
+               startup = map toPrefix
+                       [NICK "clooglebot" Nothing
+                       ,USER "cloogle" "cloogle" "cloogle" "Cloogle bot"
+                       ,JOIN (CSepList ["#cloogle", "#cleanlang"]) Nothing]
+               shutdown = map toPrefix [QUIT $ Just "Bye"]
+
+               process :: IRCMessage () *World -> (Maybe [IRCMessage], (), *World)
+               process im s w = case im.irc_command of
+                       Left numr = (Just [], (), w)
+                       Right cmd = case process` im.irc_prefix cmd w of
+                               (Nothing, w) = (Nothing, (), w)
+                               (Just cs, w) = (Just $ map toPrefix cs, (), w)
+
+               process` :: (Maybe (Either IRCUser String)) IRCCommand *World -> (Maybe [IRCCommand], *World)
+               process` (Just (Left user)) (PRIVMSG t m) w
+                       | m == "!restart" = (Nothing, w)
+                       | m.[0] == '!'
+                               # (msgs, w) = realProcess (split " " $ m % (1, size m)) w
+                               = (Just $ map (PRIVMSG recipient) msgs, w)
+                       = (Just [], w)
+               where
+                       recipient = case (\(CSepList [t:_]) -> t.[0]) t of
+                               '#' -> t
+                               _   -> CSepList [user.irc_nick]
+               process` _ (PING t mt) w = (Just [PONG t mt], w)
+               process` _ _ w = (Just [], w)
+
+               realProcess :: [String] *World -> ([String], *World)
+               realProcess ["help",x:xs] w = ((case x of
+                       "help" =
+                               [ "Usage: !help [ARG]"
+                               , "Show this help, or the specific help of the argument"]
+                       "ping" =
+                               [ "Usage: !ping [ARG [ARG ...]]"
+                               , "Ping the bot, it will pong the arguments back"]
+                       "shorten" =
+                               [ "Usage: !shorten URL [URL [URL ...]]"
+                               , "Shorten the given urls with the cloo.gl url shortener"]
+                       "query" =
+                               [ "Usage: !query QUERY"
+                               , "Query QUERY in cloogle and return the results"]
+                       "restart" =
+                               [ "Usage: !restart"
+                               , "Restart the bot"]
+                       x = ["Unknown command: " +++ x]
+                       ), w)
+               realProcess ["help"] w = (
+                       ["Type !help cmd for command specific help"
+                       ,"available commands: help, ping, shorten, query, restart"], w)
+               realProcess ["ping":xs] w = (["pong " +++ join " " xs], w)
+               realProcess ["shorten":xs] w = case xs of
+                       [] = (["shorten requires at least one argument"], w)
+                       xs = mapSt shorten xs w
+               realProcess ["query":xs] w = case xs of
+                       [] = (["query requires one or more arguments"], w)
+                       xs = appFst (split "\n") $ cloogle (join " " xs) w
+               realProcess ["restart":_] w = (["restart takes no arguments"], w)
+               realProcess [c:_] w = ([join " " [
+                       "Unknown cmd: ", c, ",  type !help to get help"]], w)