diff options
Diffstat (limited to 'Hirc')
| -rw-r--r-- | Hirc/Bot.hs | 79 | ||||
| -rw-r--r-- | Hirc/Format.hs | 15 | ||||
| -rw-r--r-- | Hirc/Parser.hs | 8 | ||||
| -rw-r--r-- | Hirc/Types.hs | 43 | 
4 files changed, 145 insertions, 0 deletions
| diff --git a/Hirc/Bot.hs b/Hirc/Bot.hs new file mode 100644 index 0000000..9cdeeaf --- /dev/null +++ b/Hirc/Bot.hs @@ -0,0 +1,79 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE RecordWildCards #-} + +module Hirc.Bot where + +import Prelude hiding (read) +import Network +import System.IO +import Control.Concurrent.STM +import Control.Monad.Reader +import Control.Exception +import Hirc.Parser as P +import Hirc.Types +import Text.Parsec (parse) + +runBot :: Config -> Hooks -> IO () +runBot Config{..} Hooks{..} = +    bracket (connect config_server) disconnect run +  where +    connect Server{..} = do +      socket <- connectTo hostname (PortNumber (fromIntegral port)) +      nick <- atomically $ newTVar config_nick +      chan <- atomically $ newTVar config_chan +      hSetBuffering socket NoBuffering +      return Bot { +        bot_server = config_server, +        bot_nick = nick, +        bot_chan = chan, +        bot_socket = socket +      } + +    disconnect bot@Bot{bot_socket=h} = do +        hClose h +        runReaderT hooks_onDisconnect bot + +    run bot = do +      runReaderT (hooks_onConnect >> receive hooks_onMessage hooks_onError) bot + +    receive :: (Message -> Net ()) -> (Error -> Net ()) -> Net () +    receive onMessage onError = do +      server <- asks bot_server +      socket <- asks bot_socket +      forever $ do +        s <- init <$> liftIO (hGetLine socket) +        case parse P.message (show server) s of +          Right m -> do +              case m of +                  Message _ "PING" [x] -> do +                      h <- asks bot_socket +                      liftIO $ hPutStr h $ "PONG :" ++ x ++ "\r\n" +                  _ -> return () +              onMessage m +          e -> onError $ BadMessage $ show e -- TODO + +atomic :: (Bot -> a) -> (a -> STM b) -> Net b +atomic v f = asks v >>= liftIO . atomically . f + +read :: (Bot -> TVar a) -> Net a +read v = atomic v readTVar + +write :: (Bot -> TVar a) -> a -> Net () +write v = atomic v . flip writeTVar + + +bumpNick :: Net String +bumpNick = +    atomic bot_nick $ flip updateTVar $ \nick -> +      case parse P.nickNum "bumpNick" nick of +        Right (n,i) -> n ++ show (i+1) +        _ -> nick ++ "_" + + +-- Like modifyTVar but returns the new value. +updateTVar :: TVar a -> (a -> a) -> STM a +updateTVar v f = do +    x <- readTVar v +    let x' = f x +    writeTVar v x' +    return x' diff --git a/Hirc/Format.hs b/Hirc/Format.hs new file mode 100644 index 0000000..bf6546a --- /dev/null +++ b/Hirc/Format.hs @@ -0,0 +1,15 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE RecordWildCards #-} +module Hirc.Format where + +import Hirc.Types + +formatMessage :: Message -> String +formatMessage Message{..} = +    maybe "" prefix m_prefix ++ m_command ++ params +  where +    prefix Prefix{..} = +      ":" ++ p_name ++ maybe "" user p_user ++ maybe "" host p_host ++ " " +    user x = "!" ++ x +    host x = "@" ++ x +    params = concatMap (" "++) (init m_params) ++ " :" ++ last m_params diff --git a/Hirc/Parser.hs b/Hirc/Parser.hs index f52564b..7014171 100644 --- a/Hirc/Parser.hs +++ b/Hirc/Parser.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE LambdaCase #-}  module Hirc.Parser where  import Data.Char @@ -21,3 +22,10 @@ message =      trailing = char ':' *> many anyChar      middle = many1 nonspace      nonspace = satisfy (not . isSpace) + + + +nickNum :: Parser (String, Int) +nickNum = +  (,) <$> (many1 (satisfy (not . isDigit))) +      <*> ((digitToInt <$> digit) <|> pure 0) diff --git a/Hirc/Types.hs b/Hirc/Types.hs index 2567b53..c516ba7 100644 --- a/Hirc/Types.hs +++ b/Hirc/Types.hs @@ -1,5 +1,11 @@ +{-# LANGUAGE RecordWildCards #-} +  module Hirc.Types where +import System.IO (Handle) +import Control.Concurrent.STM (TVar) +import Control.Monad.Reader (ReaderT) +  type Command = String  type Param = String  type Receiver = String @@ -12,6 +18,10 @@ data Message =    }    deriving Show +data Error = +    BadMessage String +  deriving Show +  data Prefix =    Prefix {      p_name :: String, @@ -20,3 +30,36 @@ data Prefix =    }    deriving Show +type Net = ReaderT Bot IO + +data Bot = Bot { +  bot_server :: Server, +  bot_nick :: TVar String, +  bot_chan :: TVar String, +  bot_socket :: Handle +} + +data Config = +    Config { +      config_server :: Server, +      config_nick :: String, +      config_chan :: String +    } +  deriving Show + +data Hooks = +    Hooks { +      hooks_onConnect :: Net (), +      hooks_onDisconnect :: Net (), +      hooks_onError :: Error -> Net (), +      hooks_onMessage :: Message -> Net () +      --hooks_shell :: Net() +    } + +data Server = +    Server { +      hostname :: String, +      port :: Int +    } +instance Show Server where +    show Server{..} = hostname ++ ":" ++ show port | 
