{- This file is part of irc-fun-messages. - - Written in 2015 by fr33domlover . - - ♡ Copying is an act of love. Please copy, reuse and share. - - The author(s) have dedicated all copyright and related and neighboring - rights to this software to the public domain worldwide. This software is - distributed without any warranty. - - You should have received a copy of the CC0 Public Domain Dedication along - with this software. If not, see - . -} -- | This module handles user modes and channel modes. module Network.Irc.Messages.Modes ( -- * User Modes umodeToLetter , umodeFromLetter , parseUserMode -- * Channel Modes , cmodeToLetter , cmodeFromLetter , parseChannelModeParams ) where import Data.Either (isRight) import Data.Maybe (fromMaybe) import Data.List (nub, partition) import Data.Text (Text) import Data.Tuple (swap) import Network.Irc.Messages.Internal.Types import Network.Irc.Types import Text.Regex.Applicative import qualified Data.Text as T userModeMap :: [(UserMode, Char)] userModeMap = [ (UModeInvisible, 'i') , (UModeCallerID, 'g') , (UModeSeeWallops, 'w') , (UModeDeaf, 'D') , (UModeNoForwarding, 'Q') , (UModeBlockUnidentified, 'R') , (UModeConnectedViaSSL, 'Z') , (UModeAway, 'a') , (UModeRestricted, 'r') , (UModeOperator, 'o') , (UModeLocalOperator, 'O') , (UModeSeeNotices, 's') ] umodeToLetter :: UserMode -> Char umodeToLetter (UModeOther c) = c umodeToLetter um = fromMaybe (error "Implementation error: User mode not assigned a letter") (lookup um userModeMap) umodeFromLetter :: Char -> UserMode umodeFromLetter c = fromMaybe (UModeOther c) (lookup c $ map swap userModeMap) isAsciiLetter :: Char -> Bool isAsciiLetter c = 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' -- 1 to remove, 2 to add umode :: Regex ([UserMode], [UserMode]) umode = mix <$> many section where section = (,) <$> (True <$ sym '+' <|> False <$ sym '-') <*> (nub <$> many (psym isAsciiLetter)) mix l = let (add, remove) = partition fst l add' = nub $ concatMap snd add remove' = nub $ concatMap snd remove in (map umodeFromLetter remove', map umodeFromLetter add') -- the one from the MODE irc message parseUserMode :: Text -> Maybe ([UserMode], [UserMode]) parseUserMode = match umode . T.unpack --TODO add modes from https://freenode.net/using_the_network.shtml -- freenode it the main target anyway... -- also maybe add a system for server-specific non-standard flags channelModeMap :: [(ChannelMode, Char, ChannelModeType)] channelModeMap = --eIbq,k,flj,CFLMPQScgimnprstz -- o and v type B syntax-wise [ (CModeCreator, 'O', ModeTypeMaybeSetting) , (CModeOperator, 'o', ModeTypeSetting) , (CModeVoice, 'v', ModeTypeSetting) , (CModeAnonymous, 'a', ModeTypeFlag) , (CModeInviteOnly, 'i', ModeTypeFlag) , (CModeModerated, 'm', ModeTypeFlag) , (CModeNoMessagesFromOutside, 'n', ModeTypeFlag) , (CModeQuiet, 'q', ModeTypeFlag) , (CModePrivate, 'p', ModeTypeFlag) , (CModeSecret, 's', ModeTypeFlag) , (CModeServerReop, 'r', ModeTypeFlag) , (CModeTopicSettableByChannelOpOnly, 't', ModeTypeFlag) , (CModeKey Nothing, 'k', ModeTypeFlag) , (CModeUserLimit Nothing, 'l', ModeTypeMaybeSetting) , (CModeBanMask Nothing, 'b', ModeTypeList) , (CModeExceptionMask Nothing, 'e', ModeTypeList) , (CModeInvitationMask Nothing, 'I', ModeTypeList) ] freenodeOverride :: [(ChannelMode, Char, ChannelModeType)] freenodeOverride = [ (CModeFreenodeQuiet Nothing, 'q', ModeTypeSetting) ] lookupCLetter :: ChannelMode -> [(ChannelMode, Char, ChannelModeType)] -> Maybe Char lookupCLetter _ [] = Nothing lookupCLetter q ((m, l, _t):rest) = if q == m then Just l else lookupCLetter q rest --TODO make the bool=True mean we use the freenode override cmodeToLetter :: ChannelMode -> Bool -> Char cmodeToLetter (CModeOther c _) _ = c cmodeToLetter cm freenode = let m = if freenode then freenodeOverride ++ channelModeMap else channelModeMap in fromMaybe (error "Implementation error: Channel mode not assigned a letter") (lookupCLetter cm m) lookupCMode :: Char -> [(ChannelMode, Char, ChannelModeType)] -> Maybe ChannelMode lookupCMode _ [] = Nothing lookupCMode c ((m, l, _):rest) = if c == l then Just m else lookupCMode c rest --TODO make the bool=True mean we use the freenode override cmodeFromLetter :: Char -> Bool -> ChannelMode cmodeFromLetter c freenode = let m = if freenode then freenodeOverride ++ channelModeMap else channelModeMap in fromMaybe (CModeOther c []) (lookupCMode c m) -- the one from the MODE irc message -- http://www.irc.org/tech_docs/005.html {- some rules: A B C suppose we are setting 'l', then: l : does nothing +l : does nothing -l : unsets the mode l 1000 : sets mode, same as +l 1000 +l 1000 : sets mode -l 1000 : unsets mode, the 1000 is ignored D -} parseChannelModeParams :: [Text] -> Bool -- whether to use freenode override -> Maybe ( [ChannelMode] -- list additions and boolean sets , [ChannelMode] -- list removals and boolean unsets , [ChannelMode] -- setting changes (sets and unsets) ) parseChannelModeParams l _freenode = let _l' = map f l -- Tag params, each is either a mode (e.g. +abc) or an arg (e.g. *!*@*) f t = case match section $ T.unpack t of Just sect -> Right sect Nothing -> Left t section = (,) <$> optional (True <$ sym '+' <|> False <$ sym '-') <*> (nub <$> many (psym isAsciiLetter)) -- Collect (sign, modes, [arg]) tuples into a list _parts [] = Just [] _parts (Left _ : _) = Nothing _parts (Right (sign, letters) : ss) = let (args, next) = break isRight ss in case _parts next of Nothing -> Nothing Just pn -> Just $ (sign, letters, args) : pn -- Attach args to their modes by mode type --attach ( in Nothing