Break data manipulation functions into separate modules; add some haddocks

master
Levi Pearson 2014-02-12 01:31:48 -07:00
parent 9278620b75
commit b6299f59ba
8 changed files with 477 additions and 226 deletions

View File

@ -23,9 +23,9 @@ import Pipes.IRC.Message.Parse
import Pipes.IRC.Message.Render import Pipes.IRC.Message.Render
import Pipes.IRC.Message.Types import Pipes.IRC.Message.Types
import Pipes.IRC.Server.EventHandler import Pipes.IRC.Server.EventHandler
import Pipes.IRC.Server.IrcMonad
import Pipes.IRC.Server.Log import Pipes.IRC.Server.Log
import Pipes.IRC.Server.MessageHandler import Pipes.IRC.Server.MessageHandler
import Pipes.IRC.Server.Server
import Pipes.IRC.Server.Types import Pipes.IRC.Server.Types
import Pipes.Network.TCP as PN import Pipes.Network.TCP as PN

View File

@ -0,0 +1,153 @@
{-# LANGUAGE OverloadedStrings #-}
-- | This module contains various pure functions for transforming the state of
-- 'IrcChannel' structures as a result of executing IRC commands.
module Pipes.IRC.Server.Channel
( newChannel
, chanAddModeFlag
, chanDelModeFlag
, chanAddUser
, chanDelUser
, chanHasUser
, chanSetTopic
, chanHasTopic
, chanHasModeFlag
, chanSigil
, chanUserSigil
, chanSetPass
, chanHasPass
, chanCheckPass
, chanAddOper
, chanDelOper
, chanAddVoice
, chanDelVoice
, chanAddInvite
, chanDelInvite
, chanChangeNick
, chanUserIsOper
, chanUserHasVoice
, chanUserIsInvited
, chanUserMaySpeak
, chanUserMayJoin
, chanUserMaySetTopic
)
where
import Control.Lens
import Data.ByteString.Char8 (ByteString)
import Data.Maybe (isJust)
import Data.Set (delete, empty, fromList, insert,
member)
import Pipes.IRC.Server.Types
newChannel :: NickKey -> IrcChannel
newChannel creator = IrcChannel { _chanTopic = Nothing
, _chanPass = Nothing
, _chanModeFlags = empty
, _chanUsers = fromList [creator]
, _chanOpers = fromList [creator]
, _chanVoices = empty
, _chanInvites = empty
}
chanAddModeFlag, chanDelModeFlag :: IrcChanModeFlags -> IrcChannel -> IrcChannel
chanAddModeFlag cm = chanModeFlags %~ insert cm
chanDelModeFlag cm = chanModeFlags %~ delete cm
chanAddUser, chanDelUser :: NickKey -> IrcChannel -> IrcChannel
chanAddUser un = chanUsers %~ insert un
chanDelUser un = (chanUsers %~ delete un)
. (chanOpers %~ delete un)
. (chanVoices %~ delete un)
chanHasUser :: NickKey -> IrcChannel -> Bool
chanHasUser un ch = member un (ch ^. chanUsers)
chanSetTopic :: ByteString -> IrcChannel -> IrcChannel
chanSetTopic top = chanTopic .~ Just top
chanHasTopic :: IrcChannel -> Bool
chanHasTopic ch = isJust $ ch ^. chanTopic
chanHasModeFlag :: IrcChanModeFlags -> IrcChannel -> Bool
chanHasModeFlag cm ch = member cm $ ch ^. chanModeFlags
chanSigil :: IrcChannel -> ByteString
chanSigil ch | chanHasModeFlag Secret ch = "@"
| chanHasModeFlag Private ch = "*"
| otherwise = "="
chanUserSigil :: NickKey -> IrcChannel -> ByteString
chanUserSigil un ch | member un $ ch ^. chanOpers = "@"
| member un $ ch ^. chanVoices = "+"
| otherwise = ""
chanSetPass :: ByteString -> IrcChannel -> IrcChannel
chanSetPass pwd = chanPass .~ Just pwd
chanHasPass :: IrcChannel -> Bool
chanHasPass ch = isJust $ ch ^. chanPass
chanCheckPass :: ByteString -> IrcChannel -> Bool
chanCheckPass pwd ch = case ch ^. chanPass of
Just chPass -> pwd == chPass
Nothing -> True
chanAddOper, chanDelOper :: NickKey -> IrcChannel -> IrcChannel
chanAddOper un = chanOpers %~ insert un
chanDelOper un = chanOpers %~ delete un
chanAddVoice, chanDelVoice :: NickKey -> IrcChannel -> IrcChannel
chanAddVoice un = chanVoices %~ insert un
chanDelVoice un = chanVoices %~ delete un
chanAddInvite, chanDelInvite :: NickKey -> IrcChannel -> IrcChannel
chanAddInvite un = chanInvites %~ insert un
chanDelInvite un = chanInvites %~ delete un
chanChangeNick :: NickKey -> NickKey -> IrcChannel -> IrcChannel
chanChangeNick old new ch = (chOps . chVoice . chInvite . chUsers) ch
where
chOps
| chanUserIsOper old ch = chanDelOper old . chanAddOper new
| otherwise = id
chVoice
| chanUserHasVoice old ch = chanDelVoice old . chanAddVoice new
| otherwise = id
chInvite
| chanUserIsInvited old ch = chanDelInvite old . chanAddInvite new
| otherwise = id
chUsers
| chanHasUser old ch = chanDelUser old . chanAddUser new
| otherwise = id
chanUserIsOper :: NickKey -> IrcChannel -> Bool
chanUserIsOper un ch = member un $ ch ^. chanOpers
chanUserHasVoice :: NickKey -> IrcChannel -> Bool
chanUserHasVoice un ch = member un $ ch ^. chanVoices
chanUserIsInvited :: NickKey -> IrcChannel -> Bool
chanUserIsInvited un ch = member un $ ch ^. chanInvites
chanUserMaySpeak :: NickKey -> IrcChannel -> Bool
chanUserMaySpeak un ch
| (chanHasUser un ch || not (chanHasModeFlag NoOutsideMsgs ch))
&& not (chanHasModeFlag Moderated ch) = True
| chanUserIsOper un ch = True
| chanUserHasVoice un ch = True
| otherwise = False
chanUserMayJoin :: NickKey -> IrcChannel -> Bool
chanUserMayJoin un ch
| not $ chanHasModeFlag InviteOnly ch = True
| chanUserIsInvited un ch = True
| otherwise = False
chanUserMaySetTopic :: NickKey -> IrcChannel -> Bool
chanUserMaySetTopic un ch
| not (chanHasModeFlag TopicOperOnly ch) &&
chanHasUser un ch = True
| chanUserIsOper un ch = True
| otherwise = False

View File

@ -5,17 +5,18 @@ where
import Control.Lens import Control.Lens
import Control.Monad.RWS import Control.Monad.RWS
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (catMaybes, fromJust, fromMaybe, import Data.Maybe (catMaybes, fromJust, isJust)
isJust, isNothing) import qualified Data.Set as S
import qualified Data.Set as S
import Pipes.IRC.Message.Types import Pipes.IRC.Message.Types
import Pipes.IRC.Server.Channel
import Pipes.IRC.Server.Types import Pipes.IRC.Server.Types
import Pipes.IRC.Server.User
-- * IrcUser management
-- | IrcUser management
mkUser :: IrcMonad IrcUser mkUser :: IrcMonad IrcUser
mkUser = do mkUser = do
@ -23,195 +24,7 @@ mkUser = do
srvname <- view ircHostName srvname <- view ircHostName
return $ newUser srvname conn return $ newUser srvname conn
newUser :: ByteString -> Int -> IrcUser -- * Pretty Printing
newUser srvname cid =
IrcUser { _userServerName = srvname
, _userModes = S.empty
, _userChannels = S.empty
, _userConn = cid
, _userInvites = S.empty
}
userAddChan, userDelChan :: ByteString -> IrcUser -> IrcUser
userAddChan cn = userChannels %~ S.insert cn
userDelChan cn = userChannels %~ S.delete cn
userAddMode, userDelMode :: IrcUserMode -> IrcUser -> IrcUser
userAddMode um = userModes %~ S.insert um
userDelMode um = userModes %~ S.delete um
userAddInvite, userDelInvite :: ByteString -> IrcUser -> IrcUser
userAddInvite cn = userInvites %~ S.insert cn
userDelInvite cn = userInvites %~ S.delete cn
userHasMode :: IrcUserMode -> IrcUser -> Bool
userHasMode um usr = S.member um $ usr ^. userModes
userInChan :: ByteString -> IrcUser -> Bool
userInChan cn usr = S.member cn $ usr ^. userChannels
-- | IrcChannel management
newChannel :: ByteString -> IrcChannel
newChannel creator = IrcChannel { _chanTopic = Nothing
, _chanKey = Nothing
, _chanModeFlags = S.empty
, _chanUsers = S.fromList [creator]
, _chanOpers = S.fromList [creator]
, _chanVoices = S.empty
, _chanInvites = S.empty
}
chanAddModeFlag, chanDelModeFlag :: IrcChanModeFlags -> IrcChannel -> IrcChannel
chanAddModeFlag cm = chanModeFlags %~ S.insert cm
chanDelModeFlag cm = chanModeFlags %~ S.delete cm
chanAddUser, chanDelUser :: ByteString -> IrcChannel -> IrcChannel
chanAddUser un = chanUsers %~ S.insert un
chanDelUser un = (chanUsers %~ S.delete un)
. (chanOpers %~ S.delete un)
. (chanVoices %~ S.delete un)
chanHasUser :: ByteString -> IrcChannel -> Bool
chanHasUser un ch = S.member un $ ch ^. chanUsers
chanSetTopic :: ByteString -> IrcChannel -> IrcChannel
chanSetTopic top = chanTopic .~ Just top
chanHasTopic :: IrcChannel -> Bool
chanHasTopic ch = isJust $ ch ^. chanTopic
chanHasModeFlag :: IrcChanModeFlags -> IrcChannel -> Bool
chanHasModeFlag cm ch = S.member cm $ ch ^. chanModeFlags
chanSigil :: IrcChannel -> ByteString
chanSigil ch | chanHasModeFlag Secret ch = "@"
| chanHasModeFlag Private ch = "*"
| otherwise = "="
chanUserSigil :: ByteString -> IrcChannel -> ByteString
chanUserSigil un ch | S.member un $ ch ^. chanOpers = "@"
| S.member un $ ch ^. chanVoices = "+"
| otherwise = ""
chanSetKey :: ByteString -> IrcChannel -> IrcChannel
chanSetKey key = chanKey .~ Just key
chanHasKey :: IrcChannel -> Bool
chanHasKey ch = isJust $ ch ^. chanKey
chanCheckKey :: ByteString -> IrcChannel -> Bool
chanCheckKey key ch = case ch ^. chanKey of
Just chKey -> key == chKey
Nothing -> True
chanAddOper, chanDelOper :: ByteString -> IrcChannel -> IrcChannel
chanAddOper un = chanOpers %~ S.insert un
chanDelOper un = chanOpers %~ S.delete un
chanAddVoice, chanDelVoice :: ByteString -> IrcChannel -> IrcChannel
chanAddVoice un = chanVoices %~ S.insert un
chanDelVoice un = chanVoices %~ S.delete un
chanAddInvite, chanDelInvite :: ByteString -> IrcChannel -> IrcChannel
chanAddInvite un = chanInvites %~ S.insert un
chanDelInvite un = chanInvites %~ S.delete un
chanUserIsOper :: ByteString -> IrcChannel -> Bool
chanUserIsOper un ch = S.member un $ ch ^. chanOpers
chanUserHasVoice :: ByteString -> IrcChannel -> Bool
chanUserHasVoice un ch = S.member un $ ch ^. chanVoices
chanUserIsInvited :: ByteString -> IrcChannel -> Bool
chanUserIsInvited un ch = S.member un $ ch ^. chanInvites
chanUserMaySpeak :: ByteString -> IrcChannel -> Bool
chanUserMaySpeak un ch
| (chanHasUser un ch || not (chanHasModeFlag NoOutsideMsgs ch))
&& not (chanHasModeFlag Moderated ch) = True
| chanUserIsOper un ch = True
| chanUserHasVoice un ch = True
| otherwise = False
chanUserMayJoin :: ByteString -> IrcChannel -> Bool
chanUserMayJoin un ch | not $ chanHasModeFlag InviteOnly ch = True
| chanUserIsInvited un ch = True
| otherwise = False
chanUserMaySetTopic :: ByteString -> IrcChannel -> Bool
chanUserMaySetTopic un ch
| not (chanHasModeFlag TopicOperOnly ch) &&
chanHasUser un ch = True
| chanUserIsOper un ch = True
| otherwise = False
-- | IrcServer management
ircAddUser :: ByteString -> IrcUser -> IrcServer -> IrcServer
ircAddUser nn usr = ircUsers %~ M.insert nn usr
ircDelUser :: ByteString -> IrcServer -> IrcServer
ircDelUser nn srv =
srv & ircNicks %~ S.delete nn
& if ircHasUser nn srv then let
Just usr = M.lookup nn $ srv ^. ircUsers
uchans = S.elems (usr ^. userChannels)
ichans = S.elems (usr ^. userInvites)
in
(ircUsers %~ M.delete nn) .
(ircChannels %~ \cs -> foldr (M.alter $ ircPartChan nn) cs uchans) .
(ircChannels %~ \cs -> foldr (M.adjust $ chanDelInvite nn) cs ichans)
else id
ircHasUser :: ByteString -> IrcServer -> Bool
ircHasUser nn srv = isJust $ M.lookup nn (srv ^. ircUsers)
ircHasChan :: ByteString -> IrcServer -> Bool
ircHasChan cn srv = isJust $ M.lookup cn (srv ^. ircChannels)
ircJoin :: ByteString -> ByteString -> IrcServer -> IrcServer
ircJoin un cn = (ircChannels %~ M.alter alterChan cn)
. (ircUsers %~ M.adjust (userAddChan cn) un)
where
alterChan mChan = Just $ chanAddUser un (fromMaybe (newChannel un) mChan)
ircPartChan :: ByteString -> Maybe IrcChannel -> Maybe IrcChannel
ircPartChan un chan = case chanDelUser un (fromJust chan) of
IrcChannel{ _chanUsers = us }
| us == S.empty -> Nothing
chan' -> Just chan'
ircPart :: ByteString -> ByteString -> IrcServer -> IrcServer
ircPart un cn srv =
srv & (ircChannels %~ (M.alter $ ircPartChan un) cn)
& (ircUsers %~ \us -> foldr (M.adjust $ userDelInvite cn) us iusers)
where
chan = fromJust $ M.lookup cn (srv ^. ircChannels)
iusers = S.elems $ chan ^. chanInvites
ircInvite :: ByteString -> ByteString -> IrcServer -> IrcServer
ircInvite un cn = (ircChannels %~ M.adjust (chanAddInvite un) cn)
. (ircUsers %~ M.adjust (userAddInvite cn) un)
ircInviteCheck :: ByteString -> IrcChannel -> Bool
ircInviteCheck n chan =
chanHasModeFlag InviteOnly chan && not (chanUserIsInvited n chan)
ircKeyCheck :: Maybe ByteString -> IrcChannel -> Bool
ircKeyCheck k chan =
chanHasKey chan && (isNothing k || not (chanCheckKey (fromJust k) chan))
-- | Misc
parseParamList :: ByteString -> [ByteString]
parseParamList ps = filter (not . BS.null) $ BS.split ',' ps
zipParams :: [ByteString] -> [ByteString]
-> [(ByteString, Maybe ByteString)]
zipParams chans chkeys = zip chans (map Just chkeys ++ repeat Nothing)
-- | Pretty Printing
ppServiceName :: ServiceName -> ByteString ppServiceName :: ServiceName -> ByteString
ppServiceName = BS.pack ppServiceName = BS.pack
@ -223,12 +36,12 @@ ppHostPreference hp = case hp of
HostIPv6 -> "*6" HostIPv6 -> "*6"
Host hn -> BS.pack hn Host hn -> BS.pack hn
-- | Monadic utilities -- * Monadic utilities
useChan :: ByteString -> IrcMonad (Maybe IrcChannel) useChan :: ChanKey -> IrcMonad (Maybe IrcChannel)
useChan cname = fmap (M.lookup cname) $ use (clientServer . ircChannels) useChan cname = fmap (M.lookup cname) $ use (clientServer . ircChannels)
useNick :: IrcMonad (Maybe ByteString) useNick :: IrcMonad (Maybe NickKey)
useNick = do useNick = do
regState <- use clientReg regState <- use clientReg
return $ case regState of return $ case regState of
@ -236,7 +49,7 @@ useNick = do
RegUser (NickName nn _ _) -> Just nn RegUser (NickName nn _ _) -> Just nn
_ -> Nothing _ -> Nothing
validateNick :: ByteString -> IrcMonad Bool validateNick :: NickKey -> IrcMonad Bool
validateNick nickname = do validateNick nickname = do
nickSet <- use $ clientServer . ircNicks nickSet <- use $ clientServer . ircNicks
if S.member nickname nickSet if S.member nickname nickSet
@ -245,7 +58,7 @@ validateNick nickname = do
return False return False
else return True else return True
channelTargets :: ByteString -> IrcMonad [Int] channelTargets :: ChanKey -> IrcMonad [Int]
channelTargets chname = do channelTargets chname = do
srv <- use clientServer srv <- use clientServer
Just mynick <- useNick Just mynick <- useNick
@ -256,7 +69,7 @@ channelTargets chname = do
_ -> do tellNumeric err_nosuchnick [chname, ":No such nick/channel"] _ -> do tellNumeric err_nosuchnick [chname, ":No such nick/channel"]
return [] return []
userTarget :: ByteString -> IrcMonad (Maybe Int) userTarget :: NickKey -> IrcMonad (Maybe Int)
userTarget uname = do userTarget uname = do
srv <- use clientServer srv <- use clientServer
let umap = srv ^. ircUsers let umap = srv ^. ircUsers
@ -299,7 +112,7 @@ addUserPrefix msg = do
Just nickname -> msg{ prefix = Just . Right $ nickname } Just nickname -> msg{ prefix = Just . Right $ nickname }
_ -> msg{ prefix = Nothing } _ -> msg{ prefix = Nothing }
chanEcho :: [ByteString] -> IrcMessage -> IrcMonad () chanEcho :: [ChanKey] -> IrcMessage -> IrcMonad ()
chanEcho chans iMsg = do chanEcho chans iMsg = do
msg <- addUserPrefix iMsg msg <- addUserPrefix iMsg
findReceivers chans >>= fwdMsg msg findReceivers chans >>= fwdMsg msg
@ -315,9 +128,9 @@ allChanEcho iMsg = do
let chans = S.elems $ usr ^. userChannels let chans = S.elems $ usr ^. userChannels
chanEcho chans iMsg chanEcho chans iMsg
-- | Adding responses to the Writer portion of the monad -- * Adding responses to the Writer portion of the monad
tellYOURHOST :: ByteString -> IrcMonad () tellYOURHOST :: NickKey -> IrcMonad ()
tellYOURHOST nickname = do tellYOURHOST nickname = do
srvname <- view ircHostName srvname <- view ircHostName
srvhost <- view ircHost srvhost <- view ircHost
@ -332,7 +145,7 @@ tellYOURHOST nickname = do
, "running version ", version ] , "running version ", version ]
] ]
tellMOTD :: ByteString -> IrcMonad () tellMOTD :: NickKey -> IrcMonad ()
tellMOTD nickname = do tellMOTD nickname = do
motd <- view ircMotd motd <- view ircMotd
tellNumeric rpl_motdstart [nickname, ":- Message of the Day -"] tellNumeric rpl_motdstart [nickname, ":- Message of the Day -"]
@ -340,13 +153,13 @@ tellMOTD nickname = do
tellNumeric rpl_motd [nickname, ":- " `BS.append` line] tellNumeric rpl_motd [nickname, ":- " `BS.append` line]
tellNumeric rpl_endofmotd [nickname, ":End of MOTD"] tellNumeric rpl_endofmotd [nickname, ":End of MOTD"]
tellWELCOME :: ByteString -> IrcMonad () tellWELCOME :: NickKey -> IrcMonad ()
tellWELCOME nickname = do tellWELCOME nickname = do
srvname <- view ircHostName srvname <- view ircHostName
tellNumeric rpl_welcome [ nickname tellNumeric rpl_welcome [ nickname
, BS.append ":Welcome to IRC on " srvname ] , BS.append ":Welcome to IRC on " srvname ]
tellTOPIC :: ByteString -> IrcMonad () tellTOPIC :: ChanKey -> IrcMonad ()
tellTOPIC cname = do tellTOPIC cname = do
chan <- use $ clientServer . ircChannels . at cname chan <- use $ clientServer . ircChannels . at cname
case chan of case chan of
@ -354,7 +167,7 @@ tellTOPIC cname = do
tellNumeric rpl_topic [cname, fromJust $ ch ^. chanTopic] tellNumeric rpl_topic [cname, fromJust $ ch ^. chanTopic]
Nothing -> return () Nothing -> return ()
tellNAMES :: [ByteString] -> IrcMonad () tellNAMES :: [ChanKey] -> IrcMonad ()
tellNAMES cnames = do tellNAMES cnames = do
Just nn <- useNick Just nn <- useNick
forM_ cnames $ \cname -> do forM_ cnames $ \cname -> do

View File

@ -12,10 +12,14 @@ import qualified Data.ByteString.Char8 as BS
import Data.Map as M import Data.Map as M
import Data.Maybe import Data.Maybe
import Data.Set as S import Data.Set as S
import Pipes.IRC.Message.Types
import Pipes.IRC.Server.IrcMonad
import Pipes.IRC.Server.Types
import Pipes.IRC.Message.Types
import Pipes.IRC.Server.Channel
import Pipes.IRC.Server.IrcMonad
import Pipes.IRC.Server.Server
import Pipes.IRC.Server.Types
import Pipes.IRC.Server.User
import Pipes.IRC.Server.Util
ircMessageHandler :: IrcMessage -> IrcMonad () ircMessageHandler :: IrcMessage -> IrcMonad ()
ircMessageHandler msg = ircMessageHandler msg =
@ -145,7 +149,7 @@ doJoin msg chans = forM_ chans $ \(c, k) -> runEitherT $ do
when (ircInviteCheck nick chan) $ when (ircInviteCheck nick chan) $
tellErr err_inviteonlychan [c, ":Cannot join channel (+i)"] tellErr err_inviteonlychan [c, ":Cannot join channel (+i)"]
when (ircKeyCheck k chan) $ when (ircPassCheck k chan) $
tellErr err_badchannelkey [c, ":Cannot join channel (+k)"] tellErr err_badchannelkey [c, ":Cannot join channel (+k)"]
lift $ do lift $ do

View File

@ -0,0 +1,142 @@
-- | This module contains the pure functions for transforming the state of
-- the IRC server record ('IrcServer') as commands are processed. It depends
-- on the "Server.User" and "Server.Channel" modules for some transformations
-- of user and channel ('IrcUser' and 'IrcChannel') structures.
module Pipes.IRC.Server.Server
( ircAddUser, ircDelUser, ircHasUser, ircHasChan, ircJoin
, ircPart, ircInvite, ircInviteCheck, ircPassCheck
, ircChangeNick )
where
import Control.Lens
import Data.ByteString.Char8 (ByteString)
import Data.Map ((!))
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Set as S
import Pipes.IRC.Server.Channel
import Pipes.IRC.Server.Types
import Pipes.IRC.Server.User
import Pipes.IRC.Server.Util
-- | Add the user to the server with the given nickname. The
-- nickname should already be in the server's nick list; this
-- function will not check for that condition.
ircAddUser :: NickKey -- ^ nickname of the user
-> IrcUser -- ^ user to add to the server
-> IrcServer -- ^ server to add the user to
-> IrcServer -- ^ new server with user added
ircAddUser nn usr = ircUsers %~ M.insert nn usr
-- | Delete the user known by the given nickname from the
-- server. This removes the nickname from the nick list and also
-- from any channels the user is in or invited to.
ircDelUser :: NickKey -- ^ nickname of the user to delete
-> IrcServer -- ^ server to delete the user from
-> IrcServer -- ^ new server with user deleted
ircDelUser nn srv =
srv & ircNicks %~ S.delete nn
& if M.notMember nn (srv ^. ircUsers) then id else
let
usr = (srv ^. ircUsers) ! nn
uchans = S.elems (usr ^. userChannels)
ichans = S.elems (usr ^. userInvites)
in
(ircUsers %~ M.delete nn) .
(ircChannels %~ alterAtKeys (ircPartChan nn) uchans) .
(ircChannels %~ adjustAtKeys (chanDelInvite nn) ichans)
-- | Check whether a user with the given nickname is known by the
-- server. This only checks for fully-registered users; the nick
-- may still be reserved even if this returns 'False'.
ircHasUser :: NickKey -- ^ nickname of the user to check for
-> IrcServer -- ^ server to check
-> Bool -- ^ is the user registered?
ircHasUser nn srv = M.member nn (srv ^. ircUsers)
-- | Check whether a channel with the given name is known by the
-- server.
ircHasChan :: ChanKey -- ^ name of the channel to check for
-> IrcServer -- ^ server to check
-> Bool -- ^ does the channel exist?
ircHasChan cn srv = M.member cn (srv ^. ircChannels)
-- | Add the user with the given nickname to the named channel. If the
-- channel does not exist yet, it will be created. This does not do any
-- permissions checks, it just adds the user.
ircJoin :: NickKey -- ^ nickname of the joining user
-> ChanKey -- ^ name of the channel to join
-> IrcServer -- ^ server to perform the join on
-> IrcServer -- ^ new server with join completed
ircJoin un cn = (ircChannels %~ M.alter alterChan cn)
. (ircUsers %~ M.adjust (userAddChan cn) un)
where
alterChan mChan = Just $ chanAddUser un (fromMaybe (newChannel un) mChan)
-- Helper 'alter' function for ircPart, not exported
ircPartChan :: NickKey -> Maybe IrcChannel -> Maybe IrcChannel
ircPartChan un (Just chan) = case chanDelUser un chan of
IrcChannel{ _chanUsers = us }
| us == S.empty -> Nothing
chan' -> Just chan'
ircPartChan _ Nothing = Nothing
-- | Remove the user with the given nickname from the named
-- channel. If this causes the channel to become empty, it will be
-- removed from the server.
ircPart :: NickKey -- ^ nickname of parting user
-> ChanKey -- ^ name of the channel to part from
-> IrcServer -- ^ server to perform the part on
-> IrcServer -- ^ new server with part completed
ircPart un cn srv =
srv & (ircChannels %~ (M.alter $ ircPartChan un) cn)
& (ircUsers %~ adjustAtKeys (userDelInvite cn) iusers)
where
chan = (srv ^. ircChannels) ! cn
iusers = S.elems $ chan ^. chanInvites
-- | Add the user with the given nickname to the invited list for the
-- channel, and add the channel to the user's invited list.
ircInvite :: NickKey -- ^ nickname of user to invite
-> ChanKey -- ^ name of channel user is invited to
-> IrcServer -- ^ server to perform the invitation on
-> IrcServer -- ^ new server with invite completed
ircInvite un cn = (ircChannels %~ M.adjust (chanAddInvite un) cn)
. (ircUsers %~ M.adjust (userAddInvite cn) un)
-- | Determine whether the user with the given nickname is disallowed
-- from joining the channel due to the 'InviteOnly' flag and lack of
-- invitation. A 'True' value indicates that the user may join.
ircInviteCheck :: NickKey -- ^ nickname of possibly invited user
-> IrcChannel -- ^ name of channel to check
-> Bool -- ^ may the user join?
ircInviteCheck n chan =
chanHasModeFlag InviteOnly chan && not (chanUserIsInvited n chan)
-- | Determine whether the given channel will disallow joining due to
-- a missing or incorrect password. A 'True' value indicates that
-- conditions for joining are met.
ircPassCheck :: Maybe ByteString -- ^ password supplied by user
-> IrcChannel -- ^ channel to check
-> Bool -- ^ may the user join?
ircPassCheck k chan =
chanHasPass chan && (isNothing k || not (chanCheckPass (fromJust k) chan))
-- | Change the nickname of a user from 'old' to 'new', updating the
-- necessary 'IrcServer' structures. No nick collision check is
-- performed; that should be done before calling this.
ircChangeNick :: NickKey -- ^ old nickname of the user
-> NickKey -- ^ new nickname of the user
-> IrcServer -- ^ server to change the nickname on
-> IrcServer -- ^ new server with change performed
ircChangeNick old new srv =
if M.notMember old (srv ^. ircUsers) then
srv
else let
usr = (srv ^. ircUsers) ! old
chs = S.elems (usr ^. userChannels)
in
srv & (ircNicks %~ S.delete old . S.insert new)
& (ircUsers %~ M.delete old . M.insert new usr)
& (ircChannels %~ adjustAtKeys (chanChangeNick old new) chs)

View File

@ -20,6 +20,9 @@ import Pipes.IRC.Message.Types (IrcMessage, NickName)
import Pipes.Network.TCP (HostPreference (..), ServiceName, import Pipes.Network.TCP (HostPreference (..), ServiceName,
SockAddr, Socket) SockAddr, Socket)
type NickKey = ByteString
type ChanKey = ByteString
type IrcEvents = [IrcEvent] type IrcEvents = [IrcEvent]
data IrcEvent = Msg { _outMsg :: !IrcMessage data IrcEvent = Msg { _outMsg :: !IrcMessage
@ -37,9 +40,9 @@ data IrcUserMode = Away | Invisible | WallOps | Restricted
data IrcUser = data IrcUser =
IrcUser { _userServerName :: !ByteString IrcUser { _userServerName :: !ByteString
, _userModes :: !(Set IrcUserMode) , _userModes :: !(Set IrcUserMode)
, _userChannels :: !(Set ByteString) , _userChannels :: !(Set ChanKey)
, _userConn :: !Int , _userConn :: !Int
, _userInvites :: !(Set ByteString) , _userInvites :: !(Set ChanKey)
} deriving (Show, Eq) } deriving (Show, Eq)
makeLenses ''IrcUser makeLenses ''IrcUser
@ -49,19 +52,19 @@ data IrcChanModeFlags = Anonymous | InviteOnly | Moderated | NoOutsideMsgs
data IrcChannel = data IrcChannel =
IrcChannel { _chanTopic :: !(Maybe ByteString) IrcChannel { _chanTopic :: !(Maybe ByteString)
, _chanKey :: !(Maybe ByteString) , _chanPass :: !(Maybe ByteString)
, _chanModeFlags :: !(Set IrcChanModeFlags) , _chanModeFlags :: !(Set IrcChanModeFlags)
, _chanUsers :: !(Set ByteString) , _chanUsers :: !(Set NickKey)
, _chanOpers :: !(Set ByteString) , _chanOpers :: !(Set NickKey)
, _chanVoices :: !(Set ByteString) , _chanVoices :: !(Set NickKey)
, _chanInvites :: !(Set ByteString) , _chanInvites :: !(Set NickKey)
} deriving (Show, Eq) } deriving (Show, Eq)
makeLenses ''IrcChannel makeLenses ''IrcChannel
data IrcServer = data IrcServer =
IrcServer { _ircNicks :: !(Set ByteString) IrcServer { _ircNicks :: !(Set NickKey)
, _ircUsers :: !(Map ByteString IrcUser) , _ircUsers :: !(Map NickKey IrcUser)
, _ircChannels :: !(Map ByteString IrcChannel) , _ircChannels :: !(Map ChanKey IrcChannel)
, _ircVersion :: !ByteString , _ircVersion :: !ByteString
} deriving (Show) } deriving (Show)
makeLenses ''IrcServer makeLenses ''IrcServer
@ -76,7 +79,7 @@ data IrcConfig =
makeLenses ''IrcConfig makeLenses ''IrcConfig
data RegState = Unreg { _rcvdPass :: !(Maybe ByteString) data RegState = Unreg { _rcvdPass :: !(Maybe ByteString)
, _rcvdNick :: !(Maybe ByteString) , _rcvdNick :: !(Maybe NickKey)
, _rcvdName :: !(Maybe ByteString) } , _rcvdName :: !(Maybe ByteString) }
| RegUser { _regdNick :: !NickName } | RegUser { _regdNick :: !NickName }
deriving (Show) deriving (Show)

View File

@ -0,0 +1,85 @@
-- | This module contains pure functions for transforming the state of
-- 'IrcUser' structures while processing IRC commands.
module Pipes.IRC.Server.User
( newUser
, userAddChan
, userDelChan
, userAddMode
, userDelMode
, userAddInvite
, userDelInvite
, userHasMode
, userInChan
)
where
import Control.Lens
import Data.ByteString.Char8 (ByteString)
import Data.Set (delete, empty, insert, member)
import Pipes.IRC.Server.Types
-- | Create a new 'IrcUser' record with the given server name and connection
-- id.
newUser :: ByteString -- ^ a 'ByteString' containing the user's server name
-> Int -- ^ the user's connection id
-> IrcUser -- ^ the resulting new 'IrcUser' structure
newUser srvname cid =
IrcUser { _userServerName = srvname
, _userModes = empty
, _userChannels = empty
, _userConn = cid
, _userInvites = empty
}
userAddChan, userDelChan :: ChanKey -- ^ channel to add or remove from user
-> IrcUser -- ^ user of which to modify channel list
-> IrcUser -- ^ new user with channel list changed
-- | Add a channel to the user's set of channels. This does not change the
-- set of users in the channel or perform any checking; this just performs
-- the low-level change to the user.
userAddChan cn = userChannels %~ insert cn
-- | Delete a channel from the user's set of channels. This does not change
-- the set of users in the channel or perform any checking; this just performs
-- the low-level change to the user.
userDelChan cn = userChannels %~ delete cn
userAddMode, userDelMode :: IrcUserMode -- ^ mode to add or remove from user
-> IrcUser -- ^ user to perform the mode change on
-> IrcUser -- ^ new user after mode change
-- | Add the mode to the user's set of modes.
userAddMode um = userModes %~ insert um
-- | Remove the mode from the user's set of modes.
userDelMode um = userModes %~ delete um
userAddInvite, userDelInvite :: ChanKey -- ^ channel name of invitation
-> IrcUser -- ^ user of which to modify invites
-> IrcUser -- ^ new user after invite changes
-- | Add a channel invite to the user's set of active invites. This does not do
-- any change to the channel; it just performs the low-level change to the user.
userAddInvite cn = userInvites %~ insert cn
-- | Delete a channel invite from the user's set of active invites. This does
-- not do any change to the channel; it just performs the low-level change to
-- the user.
userDelInvite cn = userInvites %~ delete cn
-- | Check whether the user has the indicated mode.
userHasMode :: IrcUserMode -- ^ mode to check for presence of
-> IrcUser -- ^ user to check the mode of
-> Bool -- ^ does user have the mode?
userHasMode um usr = member um (usr ^. userModes)
-- | Check whether the user has the named channel in its set of channels. Note
-- that this does not check anything other than the user's set of channels.
userInChan :: ChanKey -- ^ name of the channel to check for
-> IrcUser -- ^ user to check the channel set of
-> Bool -- ^ is the user on the channel?
userInChan cn usr = member cn (usr ^. userChannels)

View File

@ -0,0 +1,51 @@
-- | This module contains various utility functions that don't fit elsewhere.
module Pipes.IRC.Server.Util
( parseParamList
, zipParams
, adjustAtKeys
, alterAtKeys
)
where
import Data.ByteString.Char8 (ByteString, null, split)
import Data.Map (Map, adjust, alter)
import Prelude hiding (null)
-- | Split a 'ByteString' containing an IRC command parameter list into a
-- list of 'ByteString' parameters.
parseParamList :: ByteString -- ^ comma-delimited string of parameters
-> [ByteString] -- ^ list of parameter strings
parseParamList ps = filter (not . null) $ split ',' ps
-- | Pair a list of parameters (such as channel names) with a corresponding
-- second list of parameters (such as channel passwords) they should be
-- paired with. The second list may be empty or up to the same length as the
-- first list; differences will be made up with 'Nothing' values.
zipParams :: [ByteString] -- ^ first list of parameter strings
-> [ByteString] -- ^ list of parameters to pair them with
-> [(ByteString, Maybe ByteString)]
-- ^ list of parameter pairs
zipParams chans chkeys = zip chans (map Just chkeys ++ repeat Nothing)
-- | A "Data.Map" helper that applies an 'adjust' function at each of the
-- given keys. For example:
--
-- >>> adjustAtKeys (++ "x") [1,3] (fromList [(1, "a"), (2, "b"), (3, "c")])
-- fromList [(1, "ax"), (2, "b"), (3, "cx")]
--
adjustAtKeys :: Ord k
=> (v -> v) -- ^ function to apply with 'adjust'
-> [k] -- ^ keys to apply the adjustment at
-> Map k v -- ^ map to apply the adjustments to
-> Map k v -- ^ new map resulting from the adjustments
adjustAtKeys f keys dmap = foldr (adjust f) dmap keys
-- | A "Data.Map" helper that applies an 'alter' function at each of the
-- given keys. This is similar to 'adjustAtKeys' except it may create or
-- remove key/value pairs instead of just changing existing ones.
alterAtKeys :: Ord k
=> (Maybe v -> Maybe v) -- ^ function to apply with 'alter'
-> [k] -- ^ keys to apply the alteration at
-> Map k v -- ^ map to apply the alterations to
-> Map k v -- ^ new map resulting from the alterations
alterAtKeys f keys dmap = foldr (alter f) dmap keys