{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Network.IRC.Client -- Copyright : (c) 2016 Michael Walker -- License : MIT -- Maintainer : Michael Walker -- Stability : experimental -- Portability : OverloadedStrings -- -- A simple IRC client library. Typical usage will be of this form: -- -- > run :: ByteString -> Int -> Text -> IO () -- > run host port nick = do -- > let conn = plainConnection host port -- > let cfg = defaultInstanceConfig nick & handlers %~ (yourCustomEventHandlers:) -- > runClient conn cfg () -- -- You shouldn't really need to tweak anything other than the event -- handlers, as everything has been designed to be as simple as -- possible. module Network.IRC.Client ( -- * Configuration -- | The configuration is logically split into two parts: the -- /connection/ configuration (the 'ConnectionConfig' type) and the -- /instance/ configuration (the 'InstanceConfig' type). -- -- - Connection configuration details how to connect to the IRC -- server, and cannot be modified after the client has started -- (although it can be read). -- -- - Instance configuration is everything else: the client's -- nick, and version, handlers for received messages, and so -- on. It can be modified after the client has started. -- ** Connection configuration -- | The following values can be changed with the exported lenses: -- -- - 'username' (default: \"irc-client\"). The username sent to -- the server in the \"USER\" command. -- -- - 'realname' (default: \"irc-client\"). The real name sent to -- the server in the \"USER\" command. -- -- - 'password' (default: @Nothing@). If set, the password sent to the -- server in the \"PASS\" command. -- -- - 'flood' (default: @1@). The minimum time between sending -- messages, to avoid flooding. -- -- - 'timeout' (default: @300@). The amount of time to wait for a -- message from the server before locally timing out. -- -- - 'onconnect' (default: 'defaultOnConnect'). The action to -- perform after sending the \"USER\" and \"PASS\" commands. -- -- - 'ondisconnect' (default: 'defaultOnDisconnect'). The action -- to perform after disconnecting from the server -- -- - 'logfunc' (default: 'noopLogger'). The function to log received -- and sent messages. ConnectionConfig , plainConnection , TLSConfig(..) , tlsConnection -- *** Logging -- | The logging functions are told whether the message came from -- the server or the client, and are given the raw bytestring. , Origin(..) , stdoutLogger , fileLogger , noopLogger -- ** Instance configuration -- | The following values can be changed with the exported lenses: -- -- - 'nick'. The nick that 'defaultOnConnect' sends to the -- server. This is also modified during runtime by the -- 'welcomeNick' and 'nickMangler' default event handlers. -- -- - 'channels' (default: @[]@). The channels that -- 'joinOnWelcome' joins. This is also modified during runtime -- by the 'joinHandler' default event handler. -- -- - 'version' (default: \"irc-client-$VERSION\"). The -- version that 'ctcpVersionHandler' sends. -- -- - 'handlers' (default: 'defaultEventHandlers'). The list of -- event handlers. -- -- - 'ignore' (default: @[]@). The ignore list, events from -- matching nicks are not handled. , InstanceConfig , defaultInstanceConfig -- * Writing IRC clients -- | With this library, IRC clients are mostly composed of event -- handlers. Event handlers are monadic actions operating in the -- 'IRC' monad. , IRC , send , sendBS , disconnect , reconnect -- ** From event handlers , module Network.IRC.Client.Events -- ** From the outside -- | The 'ConnectionConfig', 'InstanceConfig', and some other stuff -- are combined in the 'IRCState' type. This can be used to interact -- with a client from the outside, by providing a way to run @IRC s -- a@ actions. , IRCState , getIRCState , runIRCAction , ConnectionState(..) , getConnectionState -- * Execution , runClient -- | If an 'IRCState' is constructed with 'newIRCState' and a client -- started with 'runClientWith', then 'runIRCAction' can be used to -- interact with that client. , newIRCState , runClientWith -- | If the client times out from the server, the 'Timeout' -- exception will be thrown, killing it. , Timeout(..) -- * Concurrency -- | A client can manage a collection of threads, which get thrown -- the 'Disconnect' exception whenever the client disconnects for -- any reason (including a call to 'reconnect'). These can be -- created from event handlers to manage long-running tasks. , U.fork , Disconnect(..) -- * Lenses , module Network.IRC.Client.Lens -- * Utilities , module Network.IRC.Client.Utils , C.rawMessage , C.toByteString ) where import Control.Concurrent.STM (newTVarIO) import Control.Concurrent.STM.TBMChan (newTBMChanIO) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.ByteString (ByteString) import qualified Data.Conduit.Network.TLS as TLS import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T import Data.Version (showVersion) import qualified Data.X509 as X import qualified Data.X509.CertificateStore as X import qualified Data.X509.Validation as X import Network.Connection as TLS (TLSSettings(..)) import qualified Network.IRC.Conduit as C import qualified Network.TLS as TLS import Network.IRC.Client.Events import Network.IRC.Client.Internal import Network.IRC.Client.Lens -- I think exporting 'fork' with 'Disconnect' gives better documentation. import Network.IRC.Client.Utils hiding (fork) import qualified Network.IRC.Client.Utils as U import qualified Paths_irc_client as Paths ------------------------------------------------------------------------------- -- Configuration -- | Connect to a server without TLS. plainConnection :: ByteString -- ^ The hostname -> Int -- ^ The port -> ConnectionConfig s plainConnection host port_ = setupInternal (C.ircClient port_ host) defaultOnConnect defaultOnDisconnect noopLogger host port_ -- | How to connect to a server over TLS. data TLSConfig = WithDefaultConfig ByteString Int -- ^ Use @@. | WithClientConfig TLS.TLSClientConfig -- ^ Use the given configuration. The hostname and port are stored -- as fields of the 'TLS.TLSClientConfig'. | WithVerifier ByteString Int (X.CertificateStore -> TLS.ValidationCache -> X.ServiceID -> X.CertificateChain -> IO [X.FailedReason]) -- ^ Use @@, -- with the given certificate verifier. The certificate verifier is -- a function which returns a list of reasons to reject the -- certificate. -- | Connect to a server with TLS. tlsConnection :: TLSConfig -- ^ How to initiate the TLS connection -> ConnectionConfig s tlsConnection (WithDefaultConfig host port_) = setupInternal (C.ircTLSClient port_ host) defaultOnConnect defaultOnDisconnect noopLogger host port_ tlsConnection (WithClientConfig cfg) = setupInternal (C.ircTLSClient' cfg) defaultOnConnect defaultOnDisconnect noopLogger host port_ where host = TLS.tlsClientHost cfg port_ = TLS.tlsClientPort cfg tlsConnection (WithVerifier host port_ verifier) = setupInternal (C.ircTLSClient' cfg) defaultOnConnect defaultOnDisconnect noopLogger host port_ where cfg = let cfg0 = C.defaultTLSConfig port_ host -- this is a partial pattern match, but because I'm the -- author of irc-conduit I can do this. TLS.TLSSettings cTLSSettings = TLS.tlsClientTLSSettings cfg0 cHooks = TLS.clientHooks cTLSSettings in cfg0 { TLS.tlsClientTLSSettings = TLS.TLSSettings cTLSSettings { TLS.clientHooks = cHooks { TLS.onServerCertificate = verifier } } } -- | Construct a default IRC configuration from a nick defaultInstanceConfig :: Text -- ^ The nick -> InstanceConfig s defaultInstanceConfig n = InstanceConfig { _nick = n , _channels = [] , _version = T.append "irc-client-" (T.pack $ showVersion Paths.version) , _handlers = defaultEventHandlers , _ignore = [] } ------------------------------------------------------------------------------- -- Execution -- | Connect to the IRC server and run the client: receiving messages -- and handing them off to handlers as appropriate. runClient :: MonadIO m => ConnectionConfig s -> InstanceConfig s -> s -- ^ The initial value for the user state. -> m () runClient cconf iconf ustate = newIRCState cconf iconf ustate >>= runClientWith -- | Like 'runClient', but use the provided initial -- 'IRCState'. -- -- Multiple clients should not be run with the same 'IRCState'. The -- utility of this is to be able to run @IRC s a@ actions in order to -- interact with the client from the outside. runClientWith :: MonadIO m => IRCState s -> m () runClientWith = runIRCAction runner ------------------------------------------------------------------------------- -- State -- | Construct a new IRC state newIRCState :: MonadIO m => ConnectionConfig s -> InstanceConfig s -> s -- ^ The initial value for the user state. -> m (IRCState s) newIRCState cconf iconf ustate = liftIO $ IRCState cconf <$> newTVarIO ustate <*> newTVarIO iconf <*> (newTVarIO =<< newTBMChanIO 16) <*> newTVarIO Disconnected <*> newTVarIO S.empty