106 lines
4.6 KiB
Haskell
106 lines
4.6 KiB
Haskell
|
module Network.IRC.Protocol
|
||
|
( MessagePart (..)
|
||
|
, parseLine
|
||
|
, lineFromCommand) where
|
||
|
|
||
|
import ClassyPrelude
|
||
|
import Data.List ((!!))
|
||
|
import Data.Text (strip)
|
||
|
|
||
|
import Network.IRC.Types
|
||
|
|
||
|
data MessageParseType = Names
|
||
|
deriving (Show, Eq)
|
||
|
|
||
|
data MessagePart = MessagePart { msgParserType :: MessageParseType
|
||
|
, msgPartTarget :: Text
|
||
|
, msgPartTime :: UTCTime
|
||
|
, msgPartLine :: Text }
|
||
|
deriving (Show, Eq)
|
||
|
|
||
|
data MessageParseResult = Done Message [MessagePart]
|
||
|
| Partial [MessagePart]
|
||
|
| Reject
|
||
|
deriving (Show, Eq)
|
||
|
|
||
|
type MessageParser = BotConfig -> UTCTime -> Text -> [MessagePart] -> MessageParseResult
|
||
|
|
||
|
parseLine :: BotConfig -> UTCTime -> Text -> [MessagePart] -> (Maybe Message, [MessagePart])
|
||
|
parseLine botConfig time line msgParts =
|
||
|
fromMaybe (Nothing, msgParts) . flip (`foldl'` Nothing) parsers $ \parseResult parser ->
|
||
|
case parseResult of
|
||
|
Just _ -> parseResult
|
||
|
Nothing -> case parser botConfig time line msgParts of
|
||
|
Reject -> Nothing
|
||
|
Partial msgParts' -> Just (Nothing, msgParts')
|
||
|
Done message' msgParts' -> Just (Just message', msgParts')
|
||
|
where
|
||
|
parsers = [pingParser, namesParser, lineParser]
|
||
|
|
||
|
pingParser :: MessageParser
|
||
|
pingParser _ time line msgParts
|
||
|
| "PING :" `isPrefixOf` line = Done (Message time line . PingMsg . drop 6 $ line) msgParts
|
||
|
| otherwise = Reject
|
||
|
|
||
|
lineParser :: MessageParser
|
||
|
lineParser BotConfig { .. } time line msgParts = flip Done msgParts . Message time line $
|
||
|
case command of
|
||
|
"PONG" -> PongMsg message
|
||
|
"JOIN" -> JoinMsg user
|
||
|
"QUIT" -> QuitMsg user quitMessage
|
||
|
"PART" -> PartMsg user message
|
||
|
"KICK" -> KickMsg user kicked kickReason
|
||
|
"MODE" -> if source == botNick
|
||
|
then ModeMsg Self target message []
|
||
|
else ModeMsg user target mode modeArgs
|
||
|
"NICK" -> NickMsg user (drop 1 target)
|
||
|
"433" -> NickInUseMsg
|
||
|
"PRIVMSG" | target /= channel -> PrivMsg user message
|
||
|
| isActionMsg -> ActionMsg user (initDef . drop 8 $ message)
|
||
|
| otherwise -> ChannelMsg user message
|
||
|
_ -> OtherMsg source command target message
|
||
|
where
|
||
|
splits = words line
|
||
|
command = splits !! 1
|
||
|
source = drop 1 $ splits !! 0
|
||
|
target = splits !! 2
|
||
|
message = strip . drop 1 . unwords . drop 3 $ splits
|
||
|
quitMessage = strip . drop 1 . unwords . drop 2 $ splits
|
||
|
user = uncurry User . second (drop 1) . break (== '!') $ source
|
||
|
mode = splits !! 3
|
||
|
modeArgs = drop 4 splits
|
||
|
kicked = splits !! 3
|
||
|
kickReason = drop 1 . unwords . drop 4 $ splits
|
||
|
isActionMsg = "\SOH" `isPrefixOf` message && "ACTION" `isPrefixOf` drop 1 message
|
||
|
|
||
|
partitionMsgParts :: MessageParseType -> Text -> [MessagePart] -> ([MessagePart], [MessagePart])
|
||
|
partitionMsgParts parserType target =
|
||
|
partition (\MessagePart { .. } -> msgParserType == parserType && msgPartTarget == target)
|
||
|
|
||
|
namesParser :: MessageParser
|
||
|
namesParser BotConfig { .. } time line msgParts = case command of
|
||
|
"353" -> Partial $ MessagePart Names target time line : msgParts
|
||
|
"366" -> let
|
||
|
(myMsgParts, otherMsgParts) = partitionMsgParts Names target msgParts
|
||
|
(nicks, allLines) = concat *** intercalate "\r\n" . (++ [line])
|
||
|
$ unzip $ map (\MessagePart { .. } -> (namesNicks msgPartLine, msgPartLine)) myMsgParts
|
||
|
in Done (Message time allLines $ NamesMsg nicks) otherMsgParts
|
||
|
_ -> Reject
|
||
|
where
|
||
|
(_ : command : target : _) = words line
|
||
|
stripNickPrefix = pack . dropWhile (`elem` ("~&@%+" :: String)) . unpack
|
||
|
namesNicks line' = map stripNickPrefix . words . drop 1 . unwords . drop 5 . words $ line'
|
||
|
|
||
|
lineFromCommand :: BotConfig -> Command -> Maybe Text
|
||
|
lineFromCommand BotConfig { .. } command = case command of
|
||
|
PongCmd { .. } -> Just $ "PONG :" ++ rmsg
|
||
|
PingCmd { .. } -> Just $ "PING :" ++ rmsg
|
||
|
NickCmd -> Just $ "NICK " ++ botNick
|
||
|
UserCmd -> Just $ "USER " ++ botNick ++ " 0 * :" ++ botNick
|
||
|
JoinCmd -> Just $ "JOIN " ++ channel
|
||
|
QuitCmd -> Just "QUIT"
|
||
|
ChannelMsgReply { .. } -> Just $ "PRIVMSG " ++ channel ++ " :" ++ rmsg
|
||
|
PrivMsgReply (User { .. }) rmsg -> Just $ "PRIVMSG " ++ userNick ++ " :" ++ rmsg
|
||
|
NamesCmd -> Just $ "NAMES " ++ channel
|
||
|
_ -> Nothing
|