fastirc 0.1.2 → 0.2.0
raw patch · 9 files changed
+571/−63 lines, 9 filesdep +network-fancyPVP ok
version bump matches the API change (PVP)
Dependencies added: network-fancy
API changes (from Hackage documentation)
- Network.FastIRC.Session: Config :: ServerSet -> Config
- Network.FastIRC.Session: cfgServers :: Config -> ServerSet
- Network.FastIRC.Session: data Config
- Network.FastIRC.Session: type IRC = StateT Config IO
- Network.FastIRC.Users: Server :: ServerName -> UserSpec
+ Network.FastIRC.IO: hGetIRCLine :: Handle -> IO MsgString
+ Network.FastIRC.IO: hGetMessage :: Handle -> IO Message
+ Network.FastIRC.IO: hPutCommand :: Handle -> Command -> IO ()
+ Network.FastIRC.IO: hPutMessage :: Handle -> Message -> IO ()
+ Network.FastIRC.Messages: KickCmd :: (Set ChannelName) -> (Set NickName) -> (Maybe CommandArg) -> Command
+ Network.FastIRC.Messages: ModeCmd :: (Maybe (TargetName, CommandArg, [CommandArg])) -> Command
+ Network.FastIRC.Messages: NickCmd :: NickName -> (Maybe Int) -> Command
+ Network.FastIRC.Messages: NoticeCmd :: (Set TargetName) -> CommandArg -> Command
+ Network.FastIRC.Messages: PartCmd :: (Set ChannelName) -> (Maybe CommandArg) -> Command
+ Network.FastIRC.Messages: PassCmd :: CommandArg -> Command
+ Network.FastIRC.Messages: PingCmd :: ServerName -> (Maybe ServerName) -> Command
+ Network.FastIRC.Messages: PongCmd :: ServerName -> (Maybe ServerName) -> Command
+ Network.FastIRC.Messages: PrivMsgCmd :: (Set TargetName) -> CommandArg -> Command
+ Network.FastIRC.Messages: QuitCmd :: (Maybe CommandArg) -> Command
+ Network.FastIRC.Messages: TopicCmd :: ChannelName -> (Maybe CommandArg) -> Command
+ Network.FastIRC.Messages: UserCmd :: UserName -> CommandArg -> CommandArg -> CommandArg -> Command
+ Network.FastIRC.Messages: instance Read Command
+ Network.FastIRC.Messages: instance Read Message
+ Network.FastIRC.Session: BotAddHandler :: (EventHandler -> IO ()) -> (Event -> Bot ()) -> BotCommand
+ Network.FastIRC.Session: BotDispatch :: Event -> BotCommand
+ Network.FastIRC.Session: BotError :: String -> BotCommand
+ Network.FastIRC.Session: BotInfo :: Maybe NickName -> BotInfo
+ Network.FastIRC.Session: BotQuit :: (Maybe CommandArg) -> BotCommand
+ Network.FastIRC.Session: BotRecv :: Message -> BotCommand
+ Network.FastIRC.Session: BotSendCmd :: Command -> BotCommand
+ Network.FastIRC.Session: BotSendMsg :: Message -> BotCommand
+ Network.FastIRC.Session: BotSendString :: MsgString -> BotCommand
+ Network.FastIRC.Session: BotTerminate :: BotCommand
+ Network.FastIRC.Session: ConnectedEvent :: Event
+ Network.FastIRC.Session: DisconnectedEvent :: Event
+ Network.FastIRC.Session: ErrorEvent :: String -> Event
+ Network.FastIRC.Session: LoggedInEvent :: Event
+ Network.FastIRC.Session: MessageEvent :: Message -> Event
+ Network.FastIRC.Session: Params :: IO NickName -> IO UserName -> IO RealName -> Maybe CommandArg -> Address -> Params
+ Network.FastIRC.Session: QuitEvent :: Event
+ Network.FastIRC.Session: botCurrentNick :: BotInfo -> Maybe NickName
+ Network.FastIRC.Session: botGetNick :: Params -> IO NickName
+ Network.FastIRC.Session: botGetRealName :: Params -> IO RealName
+ Network.FastIRC.Session: botGetUser :: Params -> IO UserName
+ Network.FastIRC.Session: botPassword :: Params -> Maybe CommandArg
+ Network.FastIRC.Session: botServerAddr :: Params -> Address
+ Network.FastIRC.Session: data BotCommand
+ Network.FastIRC.Session: data BotInfo
+ Network.FastIRC.Session: data BotSession
+ Network.FastIRC.Session: data Event
+ Network.FastIRC.Session: data Params
+ Network.FastIRC.Session: getBotInfo :: Bot BotInfo
+ Network.FastIRC.Session: instance Eq Event
+ Network.FastIRC.Session: instance Read Event
+ Network.FastIRC.Session: instance Show Event
+ Network.FastIRC.Session: ircSendCmd :: BotSession -> Command -> IO ()
+ Network.FastIRC.Session: ircSendMsg :: BotSession -> Message -> IO ()
+ Network.FastIRC.Session: ircSendString :: BotSession -> MsgString -> IO ()
+ Network.FastIRC.Session: onConnect :: BotSession -> Bot () -> IO EventHandler
+ Network.FastIRC.Session: onDisconnect :: BotSession -> Bot () -> IO EventHandler
+ Network.FastIRC.Session: onError :: BotSession -> (String -> Bot ()) -> IO EventHandler
+ Network.FastIRC.Session: onEvent :: BotSession -> (Event -> Bot ()) -> IO EventHandler
+ Network.FastIRC.Session: onLoggedIn :: BotSession -> Bot () -> IO EventHandler
+ Network.FastIRC.Session: onMessage :: BotSession -> (Message -> Bot ()) -> IO EventHandler
+ Network.FastIRC.Session: onQuit :: BotSession -> Bot () -> IO EventHandler
+ Network.FastIRC.Session: sendBotCmd :: BotSession -> BotCommand -> IO ()
+ Network.FastIRC.Session: startBot :: Params -> IO (Either IOError BotSession)
+ Network.FastIRC.Session: type Bot = ContT () (StateT Config (ReaderT Params IO))
+ Network.FastIRC.Session: type EventHandler = Unique
+ Network.FastIRC.Types: type RealName = ByteString
+ Network.FastIRC.Types: type TargetName = ByteString
+ Network.FastIRC.Users: userIsServer :: UserSpec -> ServerSet -> Bool
- Network.FastIRC.Messages: messageParser :: ServerSet -> Parser Message
+ Network.FastIRC.Messages: messageParser :: Parser Message
- Network.FastIRC.Messages: readMessage :: ServerSet -> MsgString -> Maybe Message
+ Network.FastIRC.Messages: readMessage :: MsgString -> Maybe Message
- Network.FastIRC.Users: userParser :: ServerSet -> Parser UserSpec
+ Network.FastIRC.Users: userParser :: Parser UserSpec
Files
- Network/FastIRC.hs +4/−2
- Network/FastIRC/IO.hs +65/−0
- Network/FastIRC/Messages.hs +95/−24
- Network/FastIRC/ServerSet.hs +2/−2
- Network/FastIRC/Session.hs +377/−11
- Network/FastIRC/Types.hs +5/−1
- Network/FastIRC/Users.hs +13/−15
- Network/FastIRC/Utils.hs +2/−2
- fastirc.cabal +8/−6
Network/FastIRC.hs view
@@ -3,12 +3,13 @@ -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3 -- Maintainer: Ertugrul Soeylemez--- Stability: experimental+-- Stability: alpha -- -- Fast IRC parsing and connection library. module Network.FastIRC- ( module Network.FastIRC.Messages,+ ( module Network.FastIRC.IO,+ module Network.FastIRC.Messages, module Network.FastIRC.ServerSet, module Network.FastIRC.Types, module Network.FastIRC.Users,@@ -16,6 +17,7 @@ ) where +import Network.FastIRC.IO import Network.FastIRC.Messages import Network.FastIRC.ServerSet import Network.FastIRC.Types
+ Network/FastIRC/IO.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module: Network.FastIRC.IO+-- Copyright: (c) 2010 Ertugrul Soeylemez+-- License: BSD3+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>+-- Stability: alpha+--+-- This module helps you with doing input and output on IRC connections+-- or even log files.++module Network.FastIRC.IO+ ( hGetIRCLine,+ hGetMessage,+ hPutCommand,+ hPutMessage+ )+ where++import qualified Data.ByteString.Char8 as B+import Network.FastIRC.Messages+import Network.FastIRC.Types+import Network.FastIRC.Utils+import System.IO+++-- | Read an IRC message string.++hGetIRCLine :: Handle -> IO MsgString+hGetIRCLine h = getl B.empty+ where+ getl :: MsgString -> IO MsgString+ getl buf = do+ c <- hGetChar h+ if isIRCEOLChar c+ then return buf+ else getl (B.snoc buf c)+++-- | Read the next valid IRC message.++hGetMessage :: Handle -> IO Message+hGetMessage h = do+ line <- hGetIRCLine h+ if B.null line+ then hGetMessage h+ else+ case readMessage line of+ Just msg -> return msg+ Nothing -> hGetMessage h+++-- | Write an IRC command with no origin.++hPutCommand :: Handle -> Command -> IO ()+hPutCommand h cmd =+ B.hPutStr h $ B.append (showCommand cmd) "\r\n"+++-- | Write an IRC message.++hPutMessage :: Handle -> Message -> IO ()+hPutMessage h msg =+ B.hPutStr h $ B.append (showMessage msg) "\r\n"
Network/FastIRC/Messages.hs view
@@ -4,8 +4,8 @@ -- Module: Network.FastIRC.Messages -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3--- Maintainer: Ertugrul Soeylemez--- Stability: experimental+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>+-- Stability: alpha -- -- Parser and printer for IRC messages. @@ -25,12 +25,14 @@ import qualified Data.ByteString.Char8 as B import qualified Data.Map as M+import qualified Data.Set as S import Control.Applicative+import Control.Monad import Data.Attoparsec.Char8 as P hiding (many) import Data.Char import Data.Map (Map) import Data.Maybe-import Network.FastIRC.ServerSet+import Data.Set (Set) import Network.FastIRC.Types import Network.FastIRC.Users import Network.FastIRC.Utils@@ -44,7 +46,7 @@ msgOrigin :: !(Maybe UserSpec), -- ^ Message origin (user/server). msgCommand :: !Command -- ^ Message command or numeric. }- deriving (Eq, Show)+ deriving (Eq, Read, Show) -- | Data type for IRC commands.@@ -53,10 +55,21 @@ = StringCmd CommandName [CommandArg] -- ^ Arbitrary string command. | NumericCmd Integer [CommandArg] -- ^ Arbitrary numeric command. - -- | Join command with a list of channels as well as channel keys.- | JoinCmd (Map ChannelName (Maybe ChannelKey))+ | JoinCmd (Map ChannelName (Maybe ChannelKey))+ | KickCmd (Set ChannelName) (Set NickName) (Maybe CommandArg)+ | ModeCmd (Maybe (TargetName, CommandArg, [CommandArg]))+ | NickCmd NickName (Maybe Int)+ | NoticeCmd (Set TargetName) CommandArg+ | PartCmd (Set ChannelName) (Maybe CommandArg)+ | PassCmd CommandArg+ | PingCmd ServerName (Maybe ServerName)+ | PongCmd ServerName (Maybe ServerName)+ | PrivMsgCmd (Set TargetName) CommandArg+ | QuitCmd (Maybe CommandArg)+ | TopicCmd ChannelName (Maybe CommandArg)+ | UserCmd UserName CommandArg CommandArg CommandArg - deriving (Eq, Show)+ deriving (Eq, Read, Show) -- | Parser for IRC commands and their arguments.@@ -76,45 +89,72 @@ lastArg :: Parser CommandArg lastArg = char ':' *> P.takeWhile isMessageChar + commaArg :: Parser (Set CommandArg)+ commaArg = S.filter (not . B.null) . S.fromList . B.split ',' <$> cmdArg++ intArg :: Parser (Maybe Int)+ intArg = option Nothing (fmap fst . B.readInt <$> cmdArg)+ joinCmd :: Parser Command joinCmd = do channels <- B.split ',' <$> cmdArg keys <- option [] $ B.split ',' <$> cmdArg many cmdArg- return . JoinCmd . M.fromList $ zip channels (map Just keys ++ repeat Nothing)+ return . JoinCmd . M.fromList $+ zip channels (map Just keys ++ repeat Nothing) numCmd :: Parser Command numCmd = NumericCmd <$> decimal <*> many cmdArg + optArg :: Parser (Maybe CommandArg)+ optArg = option Nothing (Just <$> cmdArg)+ stringCmd :: Parser Command stringCmd = do cmd <- B.map toUpper <$> takeWhile1 isCommandChar case cmd of "JOIN" -> joinCmd+ "KICK" -> KickCmd <$> commaArg <*> commaArg <*> optArg <* many cmdArg+ "MODE" ->+ try ((\a b c -> ModeCmd (Just (a,b,c)))+ <$> cmdArg+ <*> cmdArg+ <*> many cmdArg)+ <|> (many cmdArg >>= guard . null >> pure (ModeCmd Nothing))+ "NICK" -> NickCmd <$> cmdArg <*> intArg <* many cmdArg+ "NOTICE" -> NoticeCmd <$> commaArg <*> cmdArg <* many cmdArg+ "PART" -> PartCmd <$> commaArg <*> optArg <* many cmdArg+ "PASS" -> PassCmd <$> cmdArg <* many cmdArg+ "PING" -> PingCmd <$> cmdArg <*> optArg <* many cmdArg+ "PONG" -> PongCmd <$> cmdArg <*> optArg <* many cmdArg+ "PRIVMSG" -> PrivMsgCmd <$> commaArg <*> cmdArg <* many cmdArg+ "QUIT" -> QuitCmd <$> optArg <* many cmdArg+ "TOPIC" -> TopicCmd <$> cmdArg <*> optArg <* many cmdArg+ "USER" -> UserCmd <$> cmdArg <*> cmdArg <*> cmdArg <*> cmdArg <* many cmdArg _ -> StringCmd cmd <$> many cmdArg -- | Parser for IRC messages. -messageParser :: ServerSet -> Parser Message-messageParser servers =+messageParser :: Parser Message+messageParser = Message <$> option Nothing (Just <$> try userSpec) <*> commandParser where userSpec :: Parser UserSpec- userSpec = char ':' *> userParser servers <* skipMany1 (char ' ')+ userSpec = char ':' *> userParser <* skipMany1 (char ' ') -- | Run the 'messageParser' parser. -readMessage :: ServerSet -> MsgString -> Maybe Message-readMessage = parseComplete . messageParser+readMessage :: MsgString -> Maybe Message+readMessage = parseComplete messageParser --- | Turn a 'Command' into a 'B.ByteString'. Please note that a command--- does not contain an origin specification. You should use--- 'showMessage' to format a an IRC message to be sent to the server.+-- | Turn a 'Command' into a 'B.ByteString'. If you need to specify an+-- origin for the command, you should use 'Message' together with+-- 'showMessage'. showCommand :: Command -> MsgString showCommand cmd =@@ -126,10 +166,40 @@ JoinCmd channels -> case formatJoins channels of- (chanList, "") -> B.append "JOIN" (showArgs [chanList])- (chanList, keyList) -> B.append "JOIN" (showArgs [chanList, keyList])+ (chanList, "") -> "JOIN" +-+ [chanList]+ (chanList, keyList) -> "JOIN" +-+ [chanList, keyList]+ KickCmd channels nicks Nothing ->+ "KICK" +-+ [commaList channels, commaList nicks]+ KickCmd channels nicks (Just reason) ->+ "KICK" +-+ [commaList channels, commaList nicks, reason]+ ModeCmd Nothing -> "MODE"+ ModeCmd (Just (target, mode, args)) ->+ "MODE" +-+ [target, mode] ++ args+ NickCmd nick (Just hc) -> "NICK" +-+ [nick, B.pack (show hc)]+ NickCmd nick Nothing -> "NICK" +-+ [nick]+ NoticeCmd targets text -> "NOTICE" +-+ [commaList targets, text]+ PartCmd chans Nothing -> "PART" +-+ [commaList chans]+ PartCmd chans (Just reason) ->+ "PART" +-+ [commaList chans, reason]+ PassCmd pwd -> "PASS" +-+ [pwd]+ PingCmd srv1 Nothing -> "PING" +-+ [srv1]+ PingCmd srv1 (Just srv2) -> "PING" +-+ [srv1, srv2]+ PongCmd srv1 Nothing -> "PONG" +-+ [srv1]+ PongCmd srv1 (Just srv2) -> "PONG" +-+ [srv1, srv2]+ PrivMsgCmd targets text -> "PRIVMSG" +-+ [commaList targets, text]+ QuitCmd Nothing -> "QUIT" +-+ []+ QuitCmd (Just reason) -> "QUIT" +-+ [reason]+ TopicCmd channel Nothing -> "TOPIC" +-+ [channel]+ TopicCmd channel (Just newTopic) ->+ "TOPIC" +-+ [channel, newTopic]+ UserCmd user vhost vport realName ->+ "USER" +-+ [user, vhost, vport, realName] where+ (+-+) :: B.ByteString -> [B.ByteString] -> B.ByteString+ cmd +-+ args = B.append cmd (showArgs args)+ infix 4 +-++ formatJoins :: Map ChannelName (Maybe ChannelKey) -> (CommandArg, CommandArg) formatJoins channels = (chanList, keyList)@@ -140,6 +210,9 @@ M.keys withoutKey keyList = B.intercalate "," $ map (fromJust . snd) chanWithKeyAssocs + commaList :: Set CommandArg -> CommandArg+ commaList = B.intercalate "," . S.toList+ showArgs :: [CommandArg] -> MsgString showArgs [] = B.empty showArgs [arg]@@ -151,14 +224,12 @@ B.append (B.cons ' ' arg) (showArgs args) --- | Turn a 'Message' into a 'B.ByteString'. It will already contain--- \"\\r\\n\" and can be sent as is to the IRC server.+-- | Turn a 'Message' into a 'B.ByteString'. showMessage :: Message -> MsgString showMessage (Message origin cmd) = case origin of- Nothing -> B.append (showCommand cmd) "\r\n"+ Nothing -> showCommand cmd Just o ->- B.concat [ ':' `B.cons` showUserSpec o,- ' ' `B.cons` showCommand cmd,- "\r\n" ]+ B.append (':' `B.cons` showUserSpec o)+ (' ' `B.cons` showCommand cmd)
Network/FastIRC/ServerSet.hs view
@@ -2,8 +2,8 @@ -- Module: Network.FastIRC.ServerSet -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3--- Maintainer: Ertugrul Soeylemez--- Stability: experimental+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>+-- Stability: alpha -- -- Functions for dealing with sets of IRC servers. Note that servers -- are compared case-insensitively.
Network/FastIRC/Session.hs view
@@ -1,33 +1,399 @@+{-# LANGUAGE OverloadedStrings #-}+ -- | -- Module: Network.FastIRC.Session -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3--- Maintainer: Ertugrul Soeylemez--- Stability: experimental+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>+-- Stability: alpha ----- This module implements the 'IRC' monad, which you can use to write--- IRC applications.+-- This module implements a framework for IRC client software.+-- Essentially it consists of a dumb bot, which connects to and stays on+-- an IRC server waiting for commands. ----- This module is completely useless for now.+-- Using the 'onEvent' function (or the convenience functions+-- 'onConnect', 'onDisconnect', etc.) you can attach event handlers to+-- certain events. These event handlers are run in the 'Bot' monad,+-- which encapsulates the current state of the bot.+--+-- Please note that even though unlikely you should expect that parts of+-- this interface will be changed in future revisions. module Network.FastIRC.Session- ( Config(..),- IRC+ ( -- * Types+ Bot,+ BotCommand(..),+ BotInfo(..),+ BotSession,+ Event(..),+ EventHandler,+ Params(..),++ -- * Functions+ ircSendCmd,+ ircSendMsg,+ ircSendString,+ onEvent,+ sendBotCmd,+ startBot,++ -- * Event utility functions+ onConnect,+ onDisconnect,+ onError,+ onLoggedIn,+ onMessage,+ onQuit,++ -- * Bot monad+ getBotInfo ) where +import qualified Data.ByteString.Char8 as B+import qualified Data.Map as M+import Control.Applicative+import Control.Concurrent+import Data.Map (Map)+import Data.Unique import MonadLib+import Network.Fancy+import Network.FastIRC.IO+import Network.FastIRC.Messages import Network.FastIRC.ServerSet+import Network.FastIRC.Types+import System.IO --- | Session configuration.+-- | Bot monad. +type Bot = ContT () (StateT Config (ReaderT Params IO))+++-- | Commands to be sent to the bot.++data BotCommand+ -- | Add an event handler.+ = BotAddHandler (EventHandler -> IO ()) (Event -> Bot ())+ | BotDispatch Event -- ^ Dispatch simulated event.+ | BotError String -- ^ Simulate an error.+ | BotQuit (Maybe CommandArg) -- ^ Send a quit message.+ | BotRecv Message -- ^ Simulate receiving of a message.+ | BotSendCmd Command -- ^ Send a command to the IRC server.+ | BotSendMsg Message -- ^ Send a message to the IRC server.+ | BotSendString MsgString -- ^ Send a raw string to the IRC server.+ | BotTerminate -- ^ Immediately kill the bot.+++-- | Runtime bot information.++data BotInfo =+ BotInfo {+ botCurrentNick :: Maybe NickName+ }+++-- | Bot session descriptor.++data BotSession =+ BotSession {+ botCmdChan :: Chan BotCommand -- ^ Command channel.+ }+++-- | Bot configuration at run-time.+ data Config = Config {- cfgServers :: ServerSet+ -- | Event handlers.+ botEventHandlers :: Map EventHandler (Event -> Bot ()),+ botEventChan :: Chan Event, -- ^ Event channel.+ botHandle :: Handle, -- ^ Connection handle.+ botInfo :: BotInfo, -- ^ Current information.+ botIsQuitting :: Bool, -- ^ Quit command issued?+ botKillerThread :: Maybe ThreadId, -- ^ Killer thread.+ botServers :: ServerSet, -- ^ Nicknames known to be servers.+ botSession :: BotSession -- ^ Session information. } --- | Monad for IRC sessions.+-- | A bot event. -type IRC = StateT Config IO+data Event+ = ConnectedEvent -- ^ Bot connected.+ | DisconnectedEvent -- ^ Bot disconnected (either error or on demand).+ | ErrorEvent String -- ^ Connection failed or disconnected on error.+ | LoggedInEvent -- ^ Bot logged in (received numeric 001).+ | MessageEvent Message -- ^ Received message from server.+ | QuitEvent -- ^ Bot disconnected on demand.+ deriving (Eq, Read, Show)+++-- | Event handler identifier.++type EventHandler = Unique+++-- | Parameters for an IRC client connection.++data Params =+ Params {+ botGetNick :: IO NickName, -- ^ IRC nick name generator.+ botGetUser :: IO UserName, -- ^ IRC user name generator.+ botGetRealName :: IO RealName, -- ^ IRC real name generator.+ botPassword :: Maybe CommandArg, -- ^ IRC server password.+ botServerAddr :: Address -- ^ IRC server address.+ }+++-- | Core bot management thread.++botManager :: Params -> Config -> IO ()+botManager params cfg = do+ -- Initialize bot.+ let eventChan = botEventChan $ cfg+ cmdChan = botCmdChan . botSession $ cfg+ h = botHandle $ cfg++ writeChan eventChan ConnectedEvent++ dispatchThread <- forkIO $+ getChanContents eventChan >>= writeList2Chan cmdChan . map BotDispatch++ netThread <- forkIO $ networkHandler cmdChan (botHandle cfg)++ -- Main loop.+ runBot params cfg $ do+ sendLogin+ forever $ do+ bcmd <- inBase $ readChan cmdChan+ case bcmd of+ BotAddHandler reportId f -> do+ hid <- inBase newUnique+ handlers <- botEventHandlers <$> get+ sets_ (\cfg -> cfg { botEventHandlers = M.insert hid f handlers })+ inBase $ reportId hid++ BotDispatch ev -> do+ handlerList <- M.elems . botEventHandlers <$> get+ mapM_ ($ ev) handlerList++ BotError err -> do+ isQuitting <- botIsQuitting <$> get+ unless isQuitting . inBase . writeChan eventChan $ ErrorEvent err+ die++ BotQuit reason -> do+ inBase $ hPutCommand h (QuitCmd reason)+ ktid <- inBase . forkIO $+ threadDelay 1000000 >>+ writeChan cmdChan BotTerminate+ sets_ $ \cfg -> cfg { botIsQuitting = True,+ botKillerThread = Just ktid }++ BotRecv msg ->+ inBase (writeChan eventChan $ MessageEvent msg) >>+ handleMsg msg++ BotSendCmd cmd -> inBase $ hPutCommand h cmd+ BotSendMsg msg -> inBase $ hPutMessage h msg+ BotSendString str -> inBase $ B.hPutStr h str+ BotTerminate -> die++ -- Clean up.+ killThread dispatchThread+ killThread netThread++ where+ networkHandler :: Chan BotCommand -> Handle -> IO ()+ networkHandler cmdChan h = do+ res <- try $ hGetMessage h+ case res of+ Left err -> writeChan cmdChan $ BotError (show err)+ Right msg ->+ writeChan cmdChan (BotRecv msg) >>+ networkHandler cmdChan h++ die :: Bot ()+ die = do+ isQuitting <- botIsQuitting <$> get+ ktidM <- botKillerThread <$> get+ handlerList <- M.elems . botEventHandlers <$> get+ when isQuitting $ mapM_ ($ QuitEvent) handlerList+ case ktidM of+ Just ktid -> inBase $ killThread ktid+ Nothing -> return ()+ mapM_ ($ DisconnectedEvent) handlerList+ abort ()+++-- | Default bot information.++defBotInfo :: BotInfo+defBotInfo =+ BotInfo { botCurrentNick = Nothing }+++-- | Handle an incoming IRC message.++handleMsg :: Message -> Bot ()+handleMsg msg = do+ h <- botHandle <$> get+ let origin = msgOrigin msg+ cmd = msgCommand msg+ eventChan <- botEventChan <$> get++ case cmd of+ NumericCmd 1 (myNick:_) -> do+ inBase $ writeChan eventChan LoggedInEvent+ sets_ $ \cfg -> let bi = (botInfo cfg) { botCurrentNick = Just myNick }+ in cfg { botInfo = bi }++ PingCmd a b -> inBase $ hPutCommand h (PongCmd a b)++ _ -> return ()+++-- | Send a command to the IRC server.++ircSendCmd :: BotSession -> Command -> IO ()+ircSendCmd bs = sendBotCmd bs . BotSendCmd+++-- | Send a message (with origin) to the IRC server. Note that IRC+-- servers ignore the origin prefix, so in general you would want to use+-- 'ircSendCmd' instead.++ircSendMsg :: BotSession -> Message -> IO ()+ircSendMsg bs = sendBotCmd bs . BotSendMsg+++-- | Send a raw message string to the IRC server. This is what most IRC+-- clients call /quote.++ircSendString :: BotSession -> MsgString -> IO ()+ircSendString bs = sendBotCmd bs . BotSendString+++-- | Add an event handler.++onEvent :: BotSession -> (Event -> Bot ()) -> IO EventHandler+onEvent bs f = do+ let cmdChan = botCmdChan bs+ answerVar <- newEmptyMVar+ writeChan cmdChan $ BotAddHandler (putMVar answerVar) f+ takeMVar answerVar+++-- | Run a 'Bot' monad computation.++runBot :: Params -> Config -> Bot () -> IO ()+runBot params cfg =+ fmap fst .+ runReaderT params .+ runStateT cfg .+ runContT return+++-- | Send bot command to a bot.++sendBotCmd :: BotSession -> BotCommand -> IO ()+sendBotCmd bs cmd = writeChan (botCmdChan bs) cmd+++-- | Send login commands.++sendLogin :: Bot ()+sendLogin = do+ h <- botHandle <$> get+ nick <- asks botGetNick >>= inBase+ user <- asks botGetUser >>= inBase+ real <- asks botGetRealName >>= inBase+ addr <- asks botServerAddr+ pass <- asks botPassword+ let (host, port) =+ case addr of+ IP h p -> (B.pack h, B.pack $ show p)+ IPv4 h p -> (B.pack h, B.pack $ show p)+ IPv6 h p -> (B.pack h, B.pack $ show p)+ _ -> ("localhost", "6667")++ inBase $ do+ case pass of+ Just pwd -> hPutCommand h $ PassCmd pwd+ Nothing -> return ()++ hPutCommand h $ NickCmd nick Nothing+ hPutCommand h $ UserCmd user host port real+++-- | Launch an IRC bot.++startBot :: Params -> IO (Either IOError BotSession)+startBot params = do+ cmdChan <- newChan+ eventChan <- newChan+ errorVar <- newEmptyMVar+ let session = BotSession { botCmdChan = cmdChan }++ forkIO $+ let comp =+ withStream (botServerAddr params) $ \h ->+ let cfg =+ Config {+ botEventHandlers = M.empty,+ botEventChan = eventChan,+ botHandle = h,+ botInfo = defBotInfo,+ botIsQuitting = False,+ botKillerThread = Nothing,+ botServers = emptyServers,+ botSession = session+ }+ in do+ hSetBuffering h NoBuffering+ putMVar errorVar Nothing+ res <- try $ botManager params cfg+ case res of+ Left err -> do+ hPutStrLn stderr "Warning (fastirc): unexpected exception:"+ hPrint stderr err+ hPutStrLn stderr "Please report this to the author."+ Right _ -> return ()+ in comp `catch` (putMVar errorVar . Just)++ error <- takeMVar errorVar+ case error of+ Nothing -> return (Right session)+ Just err -> return (Left err)+++-- | Action to run on connect.+onConnect :: BotSession -> Bot () -> IO EventHandler+onConnect bs c = onEvent bs $ \ev -> case ev of ConnectedEvent -> c; _ -> return ()++-- | Action to run on disconnect.+onDisconnect :: BotSession -> Bot () -> IO EventHandler+onDisconnect bs c = onEvent bs $ \ev -> case ev of DisconnectedEvent -> c; _ -> return ()++-- | Action to run on error (connection failed/aborted).+onError :: BotSession -> (String -> Bot ()) -> IO EventHandler+onError bs f = onEvent bs $ \ev -> case ev of ErrorEvent str -> f str; _ -> return ()++-- | Action to run after login (numeric 001 received).+onLoggedIn :: BotSession -> Bot () -> IO EventHandler+onLoggedIn bs c = onEvent bs $ \ev -> case ev of LoggedInEvent -> c; _ -> return ()++-- | Action to run when a message arrives.+onMessage :: BotSession -> (Message -> Bot ()) -> IO EventHandler+onMessage bs f = onEvent bs $ \ev -> case ev of MessageEvent msg -> f msg; _ -> return ()++-- | Action to run on quit.+onQuit :: BotSession -> Bot () -> IO EventHandler+onQuit bs c = onEvent bs $ \ev -> case ev of QuitEvent -> c; _ -> return ()+++-- | Get current bot information.++getBotInfo :: Bot BotInfo+getBotInfo = botInfo <$> get
Network/FastIRC/Types.hs view
@@ -3,7 +3,7 @@ -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3 -- Maintainer: Ertugrul Soeylemez <es@ertes.de>--- Stability: experimental+-- Stability: alpha -- -- A number of convenient type aliases. @@ -16,7 +16,9 @@ HostName, MsgString, NickName,+ RealName, ServerName,+ TargetName, UserName ) where@@ -31,5 +33,7 @@ type HostName = B.ByteString type MsgString = B.ByteString type NickName = B.ByteString+type RealName = B.ByteString type ServerName = B.ByteString+type TargetName = B.ByteString type UserName = B.ByteString
Network/FastIRC/Users.hs view
@@ -2,20 +2,20 @@ -- Module: Network.FastIRC.Users -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3--- Maintainer: Ertugrul Soeylemez--- Stability: experimental+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>+-- Stability: alpha -- -- This module includes parsers for IRC users. module Network.FastIRC.Users ( UserSpec(..),+ userIsServer, showUserSpec, userParser ) where import qualified Data.ByteString.Char8 as B import Control.Applicative-import Control.Monad import Data.Attoparsec.Char8 as P import Network.FastIRC.ServerSet import Network.FastIRC.Types@@ -29,33 +29,31 @@ = Nick NickName -- | Nickname, username and hostname. | User NickName UserName HostName- -- | IRC server.- | Server ServerName deriving (Eq, Read, Show) +-- | Check whether a given nickname is a server.++userIsServer :: UserSpec -> ServerSet -> Bool+userIsServer (User _ _ _) _ = False+userIsServer (Nick nick) servers = isServer nick servers++ -- | Turn a 'UserSpec' into a 'B.ByteString' in a format suitable to be -- sent to the IRC server. showUserSpec :: UserSpec -> MsgString showUserSpec (Nick n) = n showUserSpec (User n u h) = B.concat [ n, B.cons '!' u, B.cons '@' h ]-showUserSpec (Server sh) = sh -- | A 'Parser' for IRC users and servers. -userParser :: ServerSet -> Parser UserSpec-userParser servers =- try server <|> try full <|> nickOnly+userParser :: Parser UserSpec+userParser =+ try full <|> nickOnly where- server :: Parser UserSpec- server = do- srv <- P.takeWhile1 isIRCTokChar- guard $ srv `isServer` servers- return (Server srv)- full :: Parser UserSpec full = User <$> P.takeWhile1 isNickChar <* char '!'
Network/FastIRC/Utils.hs view
@@ -2,8 +2,8 @@ -- Module: Network.FastIRC.Utils -- Copyright: (c) 2010 Ertugrul Soeylemez -- License: BSD3--- Maintainer: Ertugrul Soeylemez--- Stability: experimental+-- Maintainer: Ertugrul Soeylemez <es@ertes.de>+-- Stability: alpha -- -- Utility functions for parsing IRC messages.
fastirc.cabal view
@@ -1,5 +1,5 @@ Name: fastirc-Version: 0.1.2+Version: 0.2.0 Category: Network Synopsis: Fast Internet Relay Chat (IRC) library Package-URL: http://code.haskell.org/fastirc/@@ -9,12 +9,12 @@ License: BSD3 License-file: LICENSE Build-type: Simple-Stability: experimental+Stability: alpha Cabal-version: >= 1.6 Description: Fast Internet Relay Chat (IRC) library. This library implements a- attoparsec-based fast parser for IRC messages as well as a (currently- yet nonexistent) network manager for servers and clients.+ attoparsec-based fast parser for IRC messages as well as a network+ manager for IRC clients (user agents and bots). Source-repository head Type: darcs@@ -28,12 +28,14 @@ bytestring >= 0.9.1.4, bytestring-show >= 0.3.3, containers >= 0.2.0.1,- monadLib >= 3.6.1+ monadLib >= 3.6.1,+ network-fancy >= 0.1.4 GHC-Options: -W Extensions: OverloadedStrings Exposed-modules: Network.FastIRC+ Network.FastIRC.IO Network.FastIRC.Messages Network.FastIRC.ServerSet Network.FastIRC.Session@@ -43,7 +45,7 @@ -- Executable test--- Build-depends: base >= 4 && < 5+-- Build-depends: base >= 4 && < 5, network -- Main-is: Test.hs -- GHC-Options: -W -- Other-modules: