Put back \r\n in toString of IRCCommand even though it makes the output look weird
[cloogle-irc.git] / cloogleirc.icl
1 module cloogleirc
2
3 import Cloogle
4 import GenPrint
5 import StdEnv
6
7 import Data.Functor
8 import Data.Maybe
9 import Data.Either
10 from Data.Func import $, mapSt
11 from Text import class Text(..), instance Text String, instance + String
12
13 import Internet.HTTP
14
15 import Text.JSON
16
17 import Text.URI
18 import System.Time
19
20 import Control.Applicative
21 import qualified Control.Monad as CM
22 import qualified Data.Map as DM
23 from Control.Monad import class Monad, instance Monad Maybe, >>=
24 from Text.Encodings.UrlEncoding import urlEncode
25 import System.CommandLine
26 import Internet.HTTP
27 import Data.Error
28 import Data.List
29 import Data.Functor
30 import Data.Tuple
31
32 import TCPIP
33 import IRC
34 import IRCBot
35
36 import StdMisc, StdDebug
37
38 shorten :: String *World -> (String, *World)
39 shorten s w
40 # s = if (startsWith "http://" s) s (if (startsWith "https://" s) s ("http://" + s))
41 # data = "type=regular&url="+urlEncode s+"&token=a"
42 # (mer, w) = doHTTPRequest
43 { newHTTPRequest
44 & req_method = HTTP_POST
45 , req_path = "/"
46 , server_name = "cloo.gl"
47 , server_port = 80
48 , req_headers = 'DM'.fromList
49 [("Content-Type", "application/x-www-form-urlencoded")
50 ,("Content-Length", toString $ size data)
51 ,("Accept", "*/*")]
52 , req_data = data} 10000 w
53 | isError mer = ("request failed: " + fromError mer, w)
54 # resp = fromOk mer
55 = (resp.rsp_data, w)
56
57 cloogle :: String *World -> (String, *World)
58 cloogle data w
59 # (mer, w) = doHTTPRequestFollowRedirects
60 { newHTTPRequest
61 & req_path = "/api.php"
62 , req_query = "?str=" + urlEncode data
63 , req_headers = 'DM'.fromList [("User-Agent", "cloogle-irc")]
64 , server_name = "cloogle.org"
65 , server_port = 80} 10000 10 w
66 | isError mer = ("request failed: " + fromError mer, w)
67 # resp = fromOk mer
68 = case fromJSON $ fromString resp.HTTPResponse.rsp_data of
69 Nothing = ("couldn't parse json", w)
70 Just clr = ("Results for " + data + " -- https://cloogle.org/#" +
71 replaceSubString "+" "%20" (urlEncode data) + "\n" +
72 processResults clr, w)
73 where
74 processResults :: Response -> String
75 processResults resp
76 | resp.return > 127 = "Failed: return code: " + toString resp.return + ", " + resp.msg
77 = join "\n" $ map processResult $ take 3 resp.data
78
79 processResult :: Result -> String
80 processResult (FunctionResult (br, {func}))
81 = "Function in " +++ br.library +++ ": " +++ br.modul +++ "\n" +++ func
82 processResult (TypeResult (br, {type}))
83 = "Type in " +++ br.library +++ ": " +++ br.modul +++ "\n" +++ limitResults type
84 processResult (ClassResult (br, {class_name,class_funs}))
85 = "Class in " +++ br.library +++ ": " +++ br.modul +++ "\n" +++ class_name +++ " with "
86 +++ toString (length class_funs) +++ " class functions"
87 processResult (ModuleResult (br, _))
88 = "Module in " +++ br.library +++ ": " +++ br.modul
89 processResult (SyntaxResult (br, re))
90 = "Clean syntax: " +++ re.syntax_title +++ "\n"
91 +++ concat (intersperse "; " re.syntax_code)
92
93 limitResults :: String -> String
94 limitResults s
95 # lines = split "\n" s
96 | length lines > 4 = limitResults (join "\n" (take 3 lines) + "\n...")
97 = join "\n" (map maxWidth lines)
98
99 maxWidth :: String -> String
100 maxWidth s = if (size s > 80) (subString 0 77 s + "...") s
101
102 :: BotSettings =
103 { bs_nick :: String
104 , bs_nickserv :: Maybe String
105 , bs_autojoin :: [String]
106 , bs_port :: Int
107 , bs_server :: String
108 , bs_strftime :: String
109 }
110
111 Start :: *World -> (Maybe String, *World)
112 Start w
113 # ([cmd:args], w) = getCommandLine w
114 # (io, w) = stdio w
115 # bs = parseCLI cmd args
116 | isError bs
117 # io = io <<< fromError bs <<< "\n"
118 = (Nothing, snd $ fclose io w)
119 # (Ok bs) = bs
120 # (merr, io, w) = bot (bs.bs_server, bs.bs_port) (startup bs) shutdown io (process bs.bs_strftime) w
121 = (merr, snd $ fclose io w)
122 where
123 parseCLI :: String [String] -> MaybeErrorString BotSettings
124 parseCLI _ [] = Ok
125 { bs_nick = "clooglebot"
126 , bs_nickserv = Nothing
127 , bs_autojoin = []
128 , bs_port = 6667
129 , bs_server = "irc.freenode.net"
130 , bs_strftime = "%s"
131 }
132 parseCLI cmd [a:as]
133 | a == "-f" || a == "--strftime"
134 = arg1 "--strftime" as \a c->{c & bs_strftime=a}
135 | a == "-n" || a == "--nick"
136 = arg1 "--nick" as \a c->{c & bs_nick=a}
137 | a == "-ns" || a == "--nickserv"
138 = arg1 "--nickserv" as \a c->{c & bs_nickserv=Just a}
139 | a == "-a" || a == "--autojoin"
140 = arg1 "--autojoin" as \a c->{c & bs_autojoin=c.bs_autojoin ++ [a]}
141 | a == "-p" || a == "--port"
142 = arg1 "--port" as \a c->{c & bs_port=toInt a}
143 | a == "-s" || a == "--server"
144 = arg1 "--server" as \a c->{c & bs_server=a}
145 | a == "-h" || a == "--help" = Error $ join "\n" $
146 [ "Usage: " + cmd + " [OPTS]"
147 , "Options:"
148 , "\t--strftime/-f FORMAT strftime format used in the output. default: %s\n"
149 , "\t--nick/-n NICKNAME Use the given nickname instead of clooglebot"
150 , "\t--nickserv/-ns PW Identify via the given password with NickServ"
151 , "\t--port/-p PORT Use the given port instead of port 6667"
152 , "\t--server/-s SERVER Use the given server instead of irc.freenode.net"
153 , "\t--autojoin/-a CHANNEL Add CHANNEL to the autojoin list. This command "
154 , "\t can be called multiple times. Beware that #"
155 , "\t has to be escaped in most shells"
156 ]
157 = Error $ "Unknown option: " +++ a
158 where
159 arg1 name [] _ = Error $ name +++ " requires an argument"
160 arg1 name [a:as] f = parseCLI cmd as >>= Ok o f a
161
162 nickserv pw = PRIVMSG (CSepList ["NickServ"]) $ "IDENTIFY " +++ pw
163
164 toPrefix c = {irc_prefix=Nothing,irc_command=Right c}
165
166 startup bs = map toPrefix $
167 [ NICK bs.bs_nick Nothing
168 , USER "cloogle" "cloogle" "cloogle" "Cloogle bot"
169 ]++ maybe [] (pure o nickserv) bs.bs_nickserv
170 ++ if (isEmpty bs.bs_autojoin) []
171 [JOIN (CSepList bs.bs_autojoin) Nothing]
172 shutdown = map toPrefix [QUIT $ Just "Bye"]
173
174 process :: String !IRCMessage *File !*World -> (Maybe [IRCMessage], *File, !*World)
175 process strf im io w
176 #! (io, w) = log strf " (r): " im (io, w)
177 = case im.irc_command of
178 Left numr = (Just [], io, w)
179 Right cmd = case process` im.irc_prefix cmd w of
180 (Nothing, w) = (Nothing, io, w)
181 (Just cs, w)
182 # msgs = map toPrefix cs
183 #! (io, w) = foldr (log strf " (s): ") (io, w) msgs
184 = (Just msgs, io, w)
185
186 log :: String String IRCMessage (!*File, !*World) -> (!*File, !*World)
187 log strf pref m (io, w)
188 #! (t, w) = localTime w
189 = (io <<< strfTime strf t <<< pref <<< toString m <<< "\n", w)
190
191 process` :: (Maybe (Either IRCUser String)) IRCCommand *World -> (Maybe [IRCCommand], *World)
192 process` (Just (Left user)) (PRIVMSG t m) w
193 | m == "!restart" = (Nothing, w)
194 | m.[0] == '!'
195 # (msgs, w) = realProcess (split " " $ m % (1, size m)) w
196 = (Just $ map reply msgs, w)
197 | m % (0,4) == "\001PING" = (Just [reply m], w)
198 = (Just [], w)
199 where
200 reply = case (\(CSepList [t:_]) -> t.[0]) t of
201 '#' -> PRIVMSG t
202 _ -> NOTICE user.irc_nick
203 process` _ (PING t mt) w = (Just [PONG t mt], w)
204 process` _ _ w = (Just [], w)
205
206 realProcess :: [String] *World -> ([String], *World)
207 realProcess ["help",x:xs] w = ((case x of
208 "help" =
209 [ "Usage: !help [ARG]"
210 , "Show this help, or the specific help of the argument"]
211 "ping" =
212 [ "Usage: !ping [ARG [ARG ...]]"
213 , "Ping the bot, it will pong the arguments back"]
214 "shorten" =
215 [ "Usage: !shorten URL [URL [URL ...]]"
216 , "Shorten the given urls with the cloo.gl url shortener"]
217 "query" =
218 [ "Usage: !query QUERY"
219 , "Query QUERY in cloogle and return the results"]
220 "restart" =
221 [ "Usage: !restart"
222 , "Restart the bot"]
223 x = ["Unknown command: " +++ x]
224 ), w)
225 realProcess ["help"] w = (
226 ["Type !help cmd for command specific help"
227 ,"available commands: help, ping, shorten, query, restart"], w)
228 realProcess ["ping":xs] w = (["pong " +++ join " " xs], w)
229 realProcess ["shorten":xs] w = case xs of
230 [] = (["shorten requires at least one argument"], w)
231 xs = mapSt shorten xs w
232 realProcess ["query":xs] w = case xs of
233 [] = (["query requires one or more arguments"], w)
234 xs = appFst (split "\n") $ cloogle (join " " xs) w
235 realProcess ["restart":_] w = (["restart takes no arguments"], w)
236 realProcess [c:_] w = ([join " " [
237 "Unknown cmd: ", c, ", type !help to get help"]], w)