packages feed

kevin-0.4: Kevin/Damn/Protocol.hs

module Kevin.Damn.Protocol (
    initialize,
    cleanup,
    listen,
    errHandlers
) where

import Kevin.Base
import Kevin.Util.Logger
import Kevin.Util.Entity
import Kevin.Util.Tablump
import Kevin.Damn.Packet
import qualified Data.Text as T
import Data.Maybe (fromJust, fromMaybe)
import Data.List (nub, delete, sortBy)
import Data.Ord (comparing)
import Kevin.Damn.Protocol.Send
import qualified Kevin.IRC.Protocol.Send as I
import Control.Applicative ((<$>))
import Data.Time.Clock.POSIX (getPOSIXTime)

initialize :: KevinIO ()
initialize = sendHandshake

cleanup :: KevinIO ()
cleanup = klog Blue "cleanup server"

listen :: KevinIO ()
listen = fix (\f -> flip catches errHandlers $ do
    k <- get_
    pkt <- io $ parsePacket <$> readServer k
    respond pkt (command pkt)
    f)

-- main responder
respond :: Packet -> T.Text -> KevinIO ()
respond _ "dAmnServer" = do
    s <- use_ settings
    sendLogin (s^.name) (s^.authtoken)

respond pkt "login" = if okay pkt
    then do
        j <- kevin $ do
           loggedIn .= True
           use joining
        mapM_ sendJoin j
    else I.sendNotice $ "Login failed: " `T.append` getArg "e" pkt

respond pkt "join" = do
    roomname <- deformatRoom . fromJust . parameter $ pkt
    if okay pkt
        then do
            kevin $ joining %= (roomname:)
            uname <- use_ name
            I.sendJoin uname roomname
        else I.sendNotice $ T.concat ["Couldn't join ", roomname, ": ", getArg "e" pkt]

respond pkt "part" = do
    roomname <- deformatRoom . fromJust . parameter $ pkt
    if okay pkt
        then do
            uname <- use_ name
            removeRoom roomname
            I.sendPart uname roomname Nothing
        else I.sendNotice $ T.concat ["Couldn't part ", roomname, ": ", getArg "e" pkt]

respond pkt "property" = deformatRoom (fromJust $ parameter pkt) >>= \roomname ->
    case getArg "p" pkt of
    "privclasses" -> do
        let pcs = parsePrivclasses . fromJust . body $ pkt
        setPrivclasses roomname pcs

    "topic" -> do
        uname <- use_ name
        I.sendTopic uname roomname (getArg "by" pkt) (T.replace "\n" " - " . entityDecode . tablumpDecode . fromJust . body $ pkt) (getArg "ts" pkt)

    "title" -> setTitle roomname (T.replace "\n" " - " . entityDecode . tablumpDecode . fromJust . body $ pkt)

    "members" -> do
        k <- get_
        let members = map (mkUser roomname (k^.privclasses) . parsePacket) . init . splitOn "\n\n" . fromJust $ body pkt
            pc = privclass . head . filter (\x -> username x == k^.name) $ members
            n = nub members
        setUsers roomname members
        when (roomname `elem` k^.joining) $ do
            I.sendUserList (k^.name) n roomname
            pclevel <- getPrivclassLevel roomname pc
            I.sendSetUserMode (k^.name) roomname pclevel
            kevin $ joining %= delete roomname

    "info" -> do
        us <- use_ name
        curtime <- io $ floor <$> getPOSIXTime
        let fixedPacket = parsePacket . T.init . T.replace "\n\nusericon" "\nusericon" . readable $ pkt
            uname = T.drop 6 . fromJust . parameter $ pkt
            rn = getArg "realname" fixedPacket
            conns = map (\x -> (read (T.unpack $ getArg "online" x) :: Int, read (T.unpack $ getArg "idle" x) :: Int, map (T.drop 8) . filter (not . T.null) . T.splitOn "\n\n" . fromJust . body $ x)) . fromJust $ (map (parsePacket . T.append "conn") . tail . T.splitOn "conn") <$> body fixedPacket
            allRooms = nub $ conns >>= (\(_,_,c) -> c)
            (onlinespan,idle) = head . sortBy (comparing fst) . map (\(a,b,_) -> (a,b)) $ conns
            signon = curtime - onlinespan
        I.sendWhoisReply us uname (entityDecode rn) allRooms idle signon

    q -> klogError $ "Unrecognized property " ++ T.unpack q

respond spk "recv" = deformatRoom (fromJust $ parameter spk) >>= \roomname ->
    case command pkt of
    "join" -> do
        let usname = fromJust $ parameter pkt
        pcs <- gets_ $ view privclasses
        countUser <- numUsers roomname usname
        let us = mkUser roomname pcs modifiedPkt
        addUser roomname us
        if countUser == 0
            then do
                I.sendJoin usname roomname
                pclevel <- getPrivclassLevel roomname (getArg "pc" modifiedPkt)
                I.sendSetUserMode usname roomname pclevel
            else I.sendNoticeClone (username us) (succ countUser) roomname

    "part" -> do
        let uname = fromJust $ parameter pkt
        removeUser roomname uname
        countUser <- numUsers roomname uname
        if countUser < 1
            then I.sendPart uname roomname $ case getArg "r" pkt of { "" -> Nothing; x -> Just x }
            else I.sendNoticeUnclone uname countUser roomname

    "msg" -> do
        let uname = arg "from"
            msg   = fromJust (body pkt)
        un <- use_ name
        unless (un == uname) $ I.sendChanMsg uname roomname (entityDecode $ tablumpDecode msg)

    "action" -> do
        let uname = arg "from"
            msg   = fromJust (body pkt)
        un <- use_ name
        unless (un == uname) $ I.sendChanAction uname roomname (entityDecode $ tablumpDecode msg)

    "privchg" -> do
        let user = fromJust $ parameter pkt
            by = arg "by"
            newPc = arg "pc"
        oldPc <- getPrivclass roomname user
        oldPcLevel <- getPrivclassLevel roomname (fromMaybe "" oldPc)
        newPcLevel <- getPrivclassLevel roomname newPc
        setUserPrivclass roomname user newPc
        I.sendRoomNotice roomname $ T.concat [user, " has been moved", maybe "" (T.append " from ") oldPc, " to ", newPc, " by ", by]
        I.sendChangeUserMode user roomname oldPcLevel newPcLevel

    "kicked" -> do
        let uname = fromJust $ parameter pkt
        removeUserAll roomname uname
        I.sendKick uname (arg "by") roomname $ case body pkt of {Just "" -> Nothing; x -> x}

    "admin" -> case fromJust $ parameter pkt of
        "create" -> I.sendRoomNotice roomname $ T.concat ["Privclass ", arg "name", " created by ", arg "by", " with: ", arg "privs"]
        "update" -> I.sendRoomNotice roomname $ T.concat ["Privclass ", arg "name", " updated by ", arg "by", " with: ", arg "privs"]
        "rename" -> I.sendRoomNotice roomname $ T.concat ["Privclass ", arg "prev", " renamed to ", arg "name", " by ", arg "by"]
        "move"   -> I.sendRoomNotice roomname $ T.concat [arg "n", " users in privclass ", arg "prev", " moved to ", arg "name", " by ", arg "by"]
        "remove" -> I.sendRoomNotice roomname $ T.concat ["Privclass", arg "name", " removed by ", arg "by"]
        "show"   -> mapM_ (I.sendRoomNotice roomname) . T.splitOn "\n" . fromJust . body $ pkt
        "privclass" -> I.sendRoomNotice roomname $ "Admin error: " `T.append` arg "e"
        q -> klogError $ "Unknown admin packet type " ++ show q

    x -> klogError $ "Unknown packet type " ++ show x

    where
        pkt = fromJust $ subPacket spk
        modifiedPkt = parsePacket (T.replace "\n\npc" "\npc" (fromJust $ body spk))
        arg = flip getArg pkt

respond pkt "kicked" = do
    roomname <- deformatRoom (fromJust $ parameter pkt)
    uname <- use_ name
    removeRoom roomname
    I.sendKick uname (getArg "by" pkt) roomname $ case body pkt of {Just "" -> Nothing; x -> x}

respond pkt "send" = I.sendNotice $ T.concat ["Send error: ", getArg "e" pkt]

respond _ "ping" = get_ >>= \k -> io . writeServer k $ ("pong\n\0" :: T.Text)

respond _ str = klog Yellow $ "Got the packet called " ++ T.unpack str


mkUser :: Chatroom -> PrivclassStore -> Packet -> User
mkUser room st p = User (fromJust $ parameter p)
                       (g "pc")
                       (fromMaybe 0 $ st^.at room >>= (^.at (g "pc")))
                       (g "symbol")
                       (entityDecode $ g "realname")
                       (g "typename")
                       (g "gpc")
    where
        g = flip getArg p

errHandlers :: [Handler KevinIO ()]
errHandlers = [Handler (\(_ :: KevinException) -> klogError "Malformed communication from server"),
               Handler (\(e :: IOException) -> klogError $ "server: " ++ show e)]