xdcc-1.0.3: src/Irc.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Irc ( IrcIO
, IrcParams (..)
, Connection
, Network
, Nickname
, Password
, Channel
, Pack
, EventFunc
, connectTo
, sendAndWaitForAck
, send
, disconnectFrom
, onMessage
, onCtcpMessage
, from
, and
, msgHasPrefix
, logMsg
) where
import IRC.Types
import Control.Concurrent.Broadcast (Broadcast, broadcast)
import qualified Control.Concurrent.Broadcast as Broadcast (listenTimeout, new)
import Control.Error
import Control.Monad (replicateM, when)
import Control.Monad.Trans.Class (lift)
import Data.ByteString.Char8 (ByteString, isPrefixOf, pack,
unpack)
import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI
import Data.Monoid ((<>))
import Network.IRC.CTCP (CTCPByteString, asCTCP)
import Prelude hiding (and)
import System.Console.Concurrent (outputConcurrent)
import System.IO.Error (ioeGetErrorString)
import Control.Exception.Lifted
import Network.SimpleIRC
connectTo :: IrcParams
-> Bool
-> IO ()
-> (Channel -> IO ())
-> IrcIO Connection
connectTo params withDebug onConnected onJoined = do
(conf, bcs) <- lift $ config params
con <- connect' conf withDebug
joined <- lift ( do mapM_ (`onConnect` con) (hooks params)
onConnected
waitForJoin bcs onJoined )
`catch` rethrowAfter (disconnectFrom params con)
case joined of
Just _ -> return con
Nothing -> throwE "Timeout when waiting on joining all channels."
disconnectFrom :: IrcParams -> Connection -> IrcIO ()
disconnectFrom IrcParams { hooks } con =
lift ( do mapM_ (`onDisconnect` con) hooks
disconnect con "bye" )
`catch` ignore
config :: IrcParams -> IO (IrcConfig, [Broadcast Channel])
config IrcParams {..} = do
bcs <- replicateM (length channels) Broadcast.new
let conf = (mkDefaultConfig host nickname)
{ cPort = fromIntegral port
, cSecure = secure
, cUsername = username
, cPass = password
, cChannels = map CI.original channels
, cEvents = concatMap events hooks
++ zipWith ((Join .) . onJoin) channels bcs }
return (conf, bcs)
connect' :: IrcConfig -> Bool -> IrcIO Connection
connect' conf withDebug = do
con <- lift $ hoistEither <$> connect conf True withDebug
catchE con (throwE . ioeGetErrorString)
waitForJoin :: [Broadcast Channel] -> (Channel -> IO ()) -> IO (Maybe [Channel])
waitForJoin bcs onJoined = do
joined <- sequence <$> waitForAll bcs
case joined of
Just chans -> mapM_ onJoined chans
Nothing -> return ()
return joined
waitForAll :: [Broadcast a] -> IO [Maybe a]
waitForAll = mapM (`Broadcast.listenTimeout` 30000000)
sendAndWaitForAck :: Connection
-> Nickname
-> ByteString
-> (Nickname -> Broadcast b -> EventFunc)
-> String
-> IrcIO b
sendAndWaitForAck con rNick cmd broadCastIfMsg errMsg = do
bc <- lift Broadcast.new
v <- lift $ do changeEvents con
[ Privmsg (broadCastIfMsg rNick bc)
, Notice logMsg ]
send con rNick cmd
v <- Broadcast.listenTimeout bc 30000000
changeEvents con [ Notice logMsg ]
return v
failWith errMsg v
send :: Connection -> Nickname -> ByteString -> IO ()
send con rNick msg = do
sendCmd con command
outputConcurrent (show command ++ "\n")
where command = MPrivmsg (pack rNick) msg
onJoin :: Channel -> Broadcast Channel -> EventFunc
onJoin channel notifyJoined con ircMessage = do
curNick <- getNickname con
onMessage (for curNick `and` msgEqCi channel)
(\_ -> broadcast notifyJoined channel)
ircMessage
logMsg :: EventFunc
logMsg _ IrcMessage { mOrigin = Just origin, mCode, mMsg } =
outputConcurrentBs (mCode <> " " <> origin <>": " <> mMsg <> "\n")
logMsg _ IrcMessage { mCode, mMsg } =
outputConcurrentBs (mCode <> ": " <> mMsg <> "\n")
outputConcurrentBs :: ByteString -> IO ()
outputConcurrentBs = outputConcurrent . unpack
onMessage :: (a -> Bool)
-> (a -> IO ())
-> a
-> IO ()
onMessage p f x = when (p x) $ f x
onCtcpMessage :: (IrcMessage -> Bool)
-> (CTCPByteString -> IO ())
-> IrcMessage
-> IO ()
onCtcpMessage p f = onMessage p (sequence_ . fmap f . asCTCP . mMsg)
for :: ByteString -> IrcMessage -> Bool
for nick IrcMessage { mNick = Just recipient } = nick == recipient
for _ _ = False
from :: Nickname -> IrcMessage -> Bool
from rNick IrcMessage { mOrigin = Just origin } = pack rNick == origin
from _ _ = False
msgEqCi :: CI String -> IrcMessage -> Bool
msgEqCi msg IrcMessage { mMsg } = msg == CI.mk (unpack mMsg)
msgHasPrefix :: ByteString -> IrcMessage -> Bool
msgHasPrefix prefix IrcMessage { mMsg } = prefix `isPrefixOf` mMsg
and :: (a -> Bool) -> (a -> Bool) -> a -> Bool
and f g x = f x && g x
rethrowAfter :: IrcIO a -> SomeException -> IrcIO b
rethrowAfter f ex = f >> throw ex
ignore :: SomeException -> IrcIO ()
ignore _ = return ()