9 from Data.Func import $
10 from Text import class Text(..), instance Text String, instance + String
14 import Control.Applicative
15 import qualified Control.Monad as CM
16 import qualified Data.Map as DM
17 from Control.Monad import class Monad, instance Monad Maybe
18 from Text.Encodings.UrlEncoding import urlEncode
28 commands = map toString
30 ,USER "cloogle" 0 "Cloogle bot"
31 ,JOIN [("#cloogle", Nothing)]
34 TIMEOUT :== Just 10000
35 SERVER :== "irc.freenode.net"
37 KEY :== "PRIVMSG #cloogle :!"
39 doRequest :: HTTPRequest *World -> *(MaybeErrorString HTTPResponse, *World)
41 # (ip,w) = lookupIPAddress server_name w
43 = (Error $ "DNS lookup for " + server_name + " failed.", w)
45 # (rpt,chan,w) = connectTCP_MT TIMEOUT (ip, req.server_port) w
47 = (Error $ "Connection to " + toString ip + " timed out.", w)
49 = (Error $ "Could not connect to " + server_name + ".", w)
50 # (Just {sChannel,rChannel}) = chan
51 # (rpt,i,sChannel,w) = send_MT TIMEOUT (toByteSeq req) sChannel w
53 = (Error $ "Could not send request to " + server_name + ".", w)
54 # (rpt,resp,rChannel,w) = receive_MT TIMEOUT rChannel w
56 = (Error $ "Did not receive a reply from " + server_name + ".", w)
57 # resp = 'CM'.join $ parseResponse <$> toString <$> resp
59 # w = closeChannel sChannel (closeRChannel rChannel w)
60 = (Error $ "Server did not respond with HTTP.", w)
61 # (resp,rChannel,w) = receiveRest (fromJust resp) rChannel w
62 # w = closeChannel sChannel (closeRChannel rChannel w)
65 server_name = req.server_name
66 receiveRest resp chan w
67 # cl = lookup "Content-Length" resp.HTTPResponse.rsp_headers
70 | size resp.rsp_data >= toInt (fromJust cl)
72 # (rpt,newresp,chan,w) = receive_MT TIMEOUT chan w
74 = (Error $ server_name + " hung up during transmission.", chan, w)
75 = receiveRest {resp & rsp_data=resp.rsp_data + toString (fromJust newresp)} chan w
80 doRequestL :: HTTPRequest Int *World -> *(MaybeErrorString HTTPResponse, *World)
81 doRequestL req 0 w = (Error "Maximal redirect numbe exceeded", w)
82 doRequestL req maxRedirects w
83 | not (trace_tn $ toString req) = undef
84 # (er, w) = doRequest req w
85 | isError er = (er, w)
87 | isMember resp.HTTPResponse.rsp_code [301, 302, 303, 307, 308]
88 = case lookup "Location" resp.HTTPResponse.rsp_headers of
89 Nothing = (Error $ "Redirect given but no Location header", w)
90 Just loc = case parseURI loc of
91 Nothing = (Error $ "Redirect URI couldn't be parsed", w)
92 Just uri = doRequestL {req
93 & server_name = maybe loc id uri.uriRegName
94 , server_port = maybe 80 id uri.uriPort
95 , req_path = uri.uriPath
96 , req_query = maybe "" ((+++) "?") uri.uriQuery
100 shorten :: String *World -> (String, *World)
102 # s = if (startsWith "http://" s) s (if (startsWith "https://" s) s ("http://" + s))
103 # data = "type=regular&url="+urlEncode s+"&token=a"
104 # (mer, w) = doRequest
106 & req_method = HTTP_POST
108 , server_name = "cloo.gl"
110 , req_headers = 'DM'.fromList
111 [("Content-Type", "application/x-www-form-urlencoded")
112 ,("Content-Length", toString $ size data)
115 | isError mer = ("request failed: " + fromError mer, w)
119 cloogle :: String *World -> (String, *World)
121 # (mer, w) = doRequestL
123 & req_path = "/api.php"
124 , req_query = "?str=" + urlEncode data
125 , server_name = "cloogle.org"
126 , server_port = 80} 10 w
127 | isError mer = ("request failed: " + fromError mer, w)
131 send :: [String] TCP_DuplexChannel *World -> (TCP_DuplexChannel, *World)
132 send [] chan w = (chan, w)
133 send [msg:msgs] {sChannel,rChannel} w
134 # (rpt,i,sChannel,w) = send_MT TIMEOUT (toByteSeq msg) sChannel w
135 | rpt <> TR_Success = abort "Could not send request\n"
136 = send msgs {sChannel=sChannel,rChannel=rChannel} w
138 recv :: TCP_DuplexChannel *World -> (Maybe String, TCP_DuplexChannel, *World)
139 recv {sChannel,rChannel} w
140 # (rpt, resp, rChannel, w) = receive_MT TIMEOUT rChannel w
141 | rpt == TR_Expired = (Nothing, {sChannel=sChannel,rChannel=rChannel}, w)
142 | rpt == TR_NoSuccess || isNothing resp = abort "Halp?\n"
143 = (toString <$> resp, {sChannel=sChannel,rChannel=rChannel}, w)
145 msg :: (String -> IRCCommands)
146 msg = PRIVMSG "#cloogle"
148 process :: *File TCP_DuplexChannel *World -> (*File, TCP_DuplexChannel, *World)
150 # (mr, chan, w) = recv chan w
151 | isNothing mr = process io chan w
153 #! io = io <<< ("Received: " +++ resp +++ "\n")
154 # ind = indexOf KEY resp
156 # cmd = split " " $ rtrim $ subString (ind + size KEY) (size resp) resp
157 #! io = io <<< ("Received command: " +++ printToString cmd +++ "\n")
158 # (w, toSend) = case cmd of
159 ["stop":_] = (w, Nothing)
160 ["ping":xs] = (w, Just [msg $ "pong " +++ join " " xs])
162 # (s, w) = cloogle (join " " xs) w
164 ["short"] = (w, Just [msg $ "short requires an url argument"])
166 # (s, w) = shorten (join " " xs) w
169 [msg "type !help cmd for command specific help"
170 ,msg "available commands: help, ping, query, short"])
171 ["help":c:_] = (w, case c of
172 "help" = Just [msg "help [CMD] - I will print general help or the help of CMD"]
173 "ping" = Just [msg "ping [TXT] - I will reply with pong and the optionar TXT"]
174 "query" = Just [msg "query QUERY - I will send QUERY to cloogle and post the results"]
175 "short" = Just [msg "short URL - I will give the url to https://cloo.gl shortening service and post back the result"]
176 _ = Just [msg "Unknown command"])
177 [c:_] = (w, Just [msg $ join " " ["unknown command: " , c, ", type !help to get help"]])
178 | isNothing toSend = (io, chan, w)
179 # (chan, w) = send (map toString $ fromJust toSend) chan w
181 | indexOf "PING :" resp >= 0
182 # cmd = rtrim $ subString (indexOf "PING :" resp + size "PING :") (size resp) resp
183 #! io = io <<< (toString $ PONG cmd Nothing) <<< "\n"
184 # (chan, w) = send [toString $ PONG cmd Nothing] chan w
188 Start :: *World -> (String, *World)
189 Start w = cloogle "Monad" w
190 //Start :: *World -> *World
192 //# (io, w) = stdio w
193 //# (ip, w) = lookupIPAddress SERVER w
194 //| isNothing ip = abort $ "DNS lookup for " +++ SERVER +++ " failed\n"
196 //# (rpt,chan,w) = connectTCP_MT TIMEOUT (ip, 6667) w
197 //| rpt == TR_Expired = abort $ "Connection to " +++ SERVER +++ " timed out\n"
198 //| rpt == TR_NoSuccess = abort $ "Could not connect to " +++ SERVER +++ "\n"
199 //# chan = fromJust chan
200 //# (chan, w) = send commands chan w
201 //# (io, chan, w) = process io chan w
202 //# ({sChannel,rChannel}, w) = send [toString $ QUIT Nothing] chan w
203 //# (_, w) = fclose io w
204 //= closeChannel sChannel (closeRChannel rChannel w)