packages feed

xdcc-1.0.0: src/Irc.hs

{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}

module Irc ( IrcIO
           , Connection
           , Network
           , Nickname
           , Channel
           , Pack
           , EventFunc
           , connectTo
           , sendAndWaitForAck
           , send
           , disconnectFrom
           , onMessage
           , onCtcpMessage
           , from
           , and
           , msgHasPrefix
           , logMsg
           ) where

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        hiding (length, map, zip)
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           Network.SimpleIRC

type Network = String
type Nickname = String
type Channel = CI String
type Pack = Int
type Connection = MIrc

type IrcIO = ExceptT String IO

config :: Network -> Nickname -> [(Channel, Broadcast ())] -> IrcConfig
config network nick chansWithBroadcasts = (mkDefaultConfig network nick)
    { cChannels = map (CI.original . fst) chansWithBroadcasts
    , cEvents   = map asOnJoinEvent chansWithBroadcasts }
  where asOnJoinEvent (chan, b) = Join (onJoin chan b)

connectTo :: Network -> Nickname -> [Channel] -> Bool -> IO () -> IO ()
          -> IrcIO Connection
connectTo network nick chans withDebug onConnected onJoined =
  do bcs <- lift $ replicateM (length chans) Broadcast.new
     con <- connect' (config' bcs) withDebug
     lift onConnected
     joined <- lift $ waitForAll bcs
     case sequence joined of
       Just _ -> do lift onJoined
                    return con
       Nothing -> throwE "Timeout when waiting on joining all channels."
  where config' bcs = config network nick (chans `zip` bcs)

connect' :: IrcConfig -> Bool -> IrcIO Connection
connect' conf withDebug =
    do con <- lift $ hoistEither <$> connect conf True withDebug
       catchE con (throwE . ioeGetErrorString)

waitForAll :: [Broadcast ()] -> IO [Maybe ()]
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

disconnectFrom :: Connection -> IO ()
disconnectFrom = flip disconnect "bye"

onJoin :: Channel -> Broadcast () -> EventFunc
onJoin channel notifyJoined con ircMessage =
  do curNick <- getNickname con
     onMessage (for curNick `and` msgEqCi channel) (\_ ->
            broadcast notifyJoined ()) 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