1 {-# OPTIONS_HADDOCK ignore-exports, prune #-}
4 -- NOTE: This is the static build of infinity,
5 -- documentation for imain and imain' has not
9 -- NOTE: This is the dynamic build of infinity,
10 -- documentation for staticmain has not been
13 module Infinity
.Main
(
21 import qualified Network
.IRC
as IRC
22 import qualified Data
.Map
as M
23 import qualified Data
.Set
as S
24 import qualified Config
as C
25 import System
.Console
.Readline
26 import Control
.Concurrent
.STM
27 import Control
.Concurrent
38 -- infinity-related stuff
39 import Infinity
.Plugins
48 type Reboot
= (Module
-> Bot
-> IO ())
51 -- | Data that can go over the remote channel
53 = Str
String -- ^ A regular string
54 | Quit
(Server
,Handle) -- ^ A quit message from a server
55 | Join
(Server
,Channel
) -- ^ Joined a channel
56 | Part
(Server
,Channel
) -- ^ Parted the channel
57 | Reboot
-- ^ Reboot message sent
58 | Nil
-- ^ Signifies thread death,
59 -- only happens after reboot
63 -- | The bot\'s static entry point; used
64 -- when not built with -fdynamic
67 -- connect to all servers
70 logs Normal
"Initializing plugins..."
71 plugins
<- initplugins
(C
.enabledplugins C
.config
)
73 -- setup static servers
75 mapM_ setup
(M
.toList x
)
76 mapM_ (forkIO
. listener plugins rchan Nothing
) (M
.toList x
)
77 let bot
' = joinservs
(M
.toList x
) bot
78 logs Normal
"Joined channels and identified..."
79 monitor plugins rchan
undefined bot
'
83 -- | Bot\'s FIRST dynamic entry point
84 imain
:: Module
-> Reboot
-> IO ()
85 imain
mod reboot
= imain
' mod reboot newbot
87 -- | The bot\'s dynamic entry point that we jump to from 'imain'
88 imain
' :: Module
-> Reboot
-> Bot
-> IO ()
89 imain
' mod reboot bot
= do
90 logs Normal
"Initializing plugins..."
91 plugins
<- initplugins
(C
.enabledplugins C
.config
)
92 rchan
<- newChan
:: IO (Chan RemoteLine
)
93 rebootvar
<- atomically
$ newEmptyTMVar
-- when a reboot is sent, this is set
95 -- we connect to any newly added servers
96 let newservs
= filter (not . servexists bot
) (C
.servers C
.config
)
97 servs
' <- mapM servcon newservs
100 -- set up all new servers
101 let bot
' = joinservs servs
' bot
103 mapM_ (forkIO
. listener plugins rchan
(Just rebootvar
)) (M
.toList x
)
104 logs Normal
"Joined channels & Identified..."
105 b
' <- monitor plugins rchan rebootvar bot
'
109 -- | If infinity is built and the '-offline' command is specified,
110 -- then it will jump here and it will be a console interface.
113 x
<- initplugins
(C
.enabledplugins C
.config
)
114 shell x
$ IContext Nothing x Nothing
115 where shell plugs ctx
= do
118 Nothing
-> shell plugs ctx
119 Just
"quit" -> exitWith ExitSuccess
122 let (cmd
,av
) = span
(/=' ') s
'
123 av
' = if null av
then Nothing
124 else Just
(drop 1 av
)
125 (str
,x
') <- runplugin ctx plugs cmd av
'
126 putStrLn str
>> shell x
' ctx
128 -- | This is the bot's main loop. After it gets rebooted or starts
129 -- for the first time, it initializes all plugins, spawns a thread
130 -- for each server, and then jumps here. This monitors when threads
131 -- quit, it waits for them to join, serializes plugin state, and
133 monitor
:: [IModule
] -> Chan RemoteLine
-> TMVar
Bool -> Bot
-> IO Bot
134 monitor plugins rchan rebootvar bot
= do
135 mlog Normal
"Server's connected, threads forked..."
137 where check b
= when (servnum b
== 0) $ do
138 mlog Normal
"All servers disconnected, quitting..."
140 wait n s i
= when (i
< n
) $
142 Nil
:xs
-> wait n xs
$! i
+1
148 Str s
-> putStrLn s
>> loop b
151 loop
$! (partserv s b
)
152 Join
(s
,c
) -> let x
= updateserv
(joinchan c s
) b
in loop
$! x
153 Part
(s
,c
) -> let x
= updateserv
(joinchan c s
) b
in loop
$! x
158 mlog Normal
"Got Reboot message"
159 atomically
$ putTMVar rebootvar
True
160 s
<- getChanContents rchan
165 -- | This is the entry point for threads that listen on sockets for
167 listener
:: [IModule
] -> Chan RemoteLine
-> Maybe (TMVar
Bool) -> (Server
,Handle) -> IO ()
168 listener plugins rchan rebootvar x
@(serv
,handle
) = infinity
$ do
169 str
<- hGetLine handle
170 writeChan rchan
(Str str
)
171 if ping str
then pong handle str
172 else eval
(ircParser str
)
173 where eval s |
(Err e
) <- s
= logs Error
("eval err: "++e
)
174 |
(Line u c x
) <- s
= irclog
(address serv
) (u
,c
,x
)
175 |
(Cmd u c
(cmd
,av
)) <- s
= do
178 Nothing
-> irclog
(address serv
) (u
,c
,cmd
)
179 Just args
-> irclog
(address serv
) (u
,c
,unwords [cmd
,args
])
180 unless (null cmd
') (parseCmds u cmd
' av c
$ IContext
(Just serv
) plugins
(Just handle
))
181 parseCmds u c av chan ctx
182 |
"join" == c
= when (isadmin u serv
) $ case av
of
183 Just av
' -> joinC handle av
' >> writeChan rchan
(Join
(serv
,av
'))
184 Nothing
-> privmsg handle chan
"Need a channel to join..."
185 |
"part" == c
= when (isadmin u serv
) $ case av
of
186 Just av
' -> partC handle av
' >> writeChan rchan
(Part
(serv
,av
'))
187 Nothing
-> partC handle chan
>> writeChan rchan
(Part
(serv
,chan
))
188 -- | "quit" == c = writeChan rchan (Quit (serv,handle))
189 -- | "reboot" == c = writeChan rchan Reboot
191 (str
,plugins
') <- runplugin ctx plugins c av
193 logs Normal
$ "Output of \'"++cmd
'++"\', with args \'"++(show av
)++"\': "++str
195 mapM_ (\s
-> privmsg handle chan s
>>
196 irclog
(address serv
) ((nickname serv
),chan
,s
)) (lines str
)
199 --------------------------------
201 --------------------------------
202 -- | Loops a function unless 'b' is False
203 infinity
:: IO () -> IO ()
204 infinity a
= a
>> infinity a
206 -- | Sends a message over a Handle
207 sendH
:: Handle -> String -> IO ()
208 sendH h
= hPrintf h
"%s\r\n"
210 -- | Sends a private message over a handle
211 privmsg
:: Handle -> String -> String -> IO ()
212 privmsg h c s
= sendH h
$ IRC
.encode
(IRC
.privmsg c s
)
214 -- | Joins a channel by sending the message over the handle
215 joinC
:: Handle -> String -> IO ()
216 joinC h
= sendH h
. IRC
.encode
. IRC
.joinChan
218 -- | Parts a channel by sending the message over the handle
219 partC
:: Handle -> String -> IO ()
220 partC h
= sendH h
. IRC
.encode
. IRC
.part
222 -- | Check if a message is a PING
223 ping
:: String -> Bool
224 ping
= isPrefixOf "PING :"
226 -- | Send a pong message given a ping message
228 pong
:: Handle -> String -> IO ()
229 pong h s
= sendH h
$ "PONG " ++ (drop 5 s
)
231 -- | our parser for irc msgs.
232 ircParser
= parseIRCmsg
(C
.commandPrefixes C
.config
)
234 -- | Starts up the bot with initial configuration and
235 -- | whatnot, used by staticmain
237 hSetBuffering stdout NoBuffering
238 logs Normal
"Connecting to servers..."
239 servs
<- mapM servcon
(C
.servers C
.config
)
240 -- add these new servers to bot state
241 let bot
= joinservs servs newbot
242 logs Normal
"Connected to servers..."
245 -- | Connects to a server
249 h
<- connectTo name
(PortNumber
$ fromIntegral portn
)
250 hSetBuffering h NoBuffering
254 -- | Sets up a newly created server connection,
255 -- by sending the User, nick, and password messages
256 -- as well as joining channels.
257 setup x
@(serv
,handle
) = do
258 -- setup authentication and stuff
259 sendstr handle
(IRC
.encode
$ IRC
.nick
(nickname serv
)) -- nick
260 sendstr handle
(IRC
.encode
$ IRC
.user
(nickname serv
) "0" "*" (realname serv
)) -- realname
261 when (not . null $ (password serv
)) $ do
262 sendstr handle
(IRC
.encode
$ IRC
.privmsg
"nickserv" ("identify "++(password serv
))) -- password
263 mapM_ (sendstr handle
. IRC
.encode
. IRC
.joinChan
) (S
.toList
$ channels serv
) -- channels
266 -- | Send a string over handle
267 sendstr h str
= hPrintf h
"%s\r\n" str