packages feed

kevin-0.7: Kevin/Damn/Protocol.hs

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

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

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 (view 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: " <> pkt ^. args.ix "e"

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

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

respond pkt "property" = do
    roomname <- deformatRoom (pkt^.parameter._Just)
    case pkt^.args.ix "p" of
        "privclasses" -> setPrivclasses roomname . parsePrivclasses $ pkt^.body._Just

        "topic" -> do
            uname <- use_ name
            I.sendTopic uname roomname
                        (fromMaybe uname $ pkt^.args.at "by")
                        (T.replace "\n" " - " . entityDecode
                            . tablumpDecode $ pkt^.body._Just)
                        (pkt^.args.ix "ts")

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

        "members" -> do
            k <- get_
            let members = map (mkUser roomname (k^.privclasses) . parsePacket)
                        . init . T.splitOn "\n\n"
                        $ pkt^.body._Just
                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 $ pkt^.parameter._Just
                rn = fixedPacket^.args.ix "realname"
                conns = map (\pk ->
                    let x = parsePacket . T.append "conn" $ pk
                    in ( read (T.unpack $ x^.args.ix "online") :: Int
                       , read (T.unpack $ x^.args.ix "idle") :: Int
                       , map (T.drop 8) . filter (not . T.null)
                       . T.splitOn "\n\n" $ x^.body._Just
                    )
                  ) . tail . T.splitOn "conn"
                    $ fixedPacket^.body._Just
                allRooms = nub $ conns >>= (\(_,_,c) -> c)
                (onlinespan,idle,_) = minimumBy (comparing (view _1)) conns
                signon = curtime - onlinespan
            I.sendWhoisReply us uname (entityDecode rn) allRooms idle signon

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

respond spk "recv" = deformatRoom (spk^.parameter._Just) >>= \roomname ->
    case pkt^.command of
    "join" -> do
        let usname = pkt^.parameter._Just
        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 (modifiedPkt^.args.ix "pc")
                I.sendSetUserMode usname roomname pclevel
            else I.sendNoticeClone (username us) (succ countUser) roomname

    "part" -> do
        let uname = pkt^.parameter._Just
        removeUser roomname uname
        countUser <- numUsers roomname uname
        if countUser < 1
            then I.sendPart uname roomname $ pkt^.args.at "r"
            else I.sendNoticeUnclone uname countUser roomname

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

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

    "privchg" -> do
        let user = pkt^.parameter._Just
            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 = pkt^.parameter._Just
        removeUserAll roomname uname
        I.sendKick uname (arg "by") roomname $ pkt^.body.traverse ^? notNull_

    "admin" -> case pkt^.parameter._Just 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" $ pkt^.body._Just
        "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" $ pkt^.body._Just)
        arg s = pkt^.args.ix s

respond pkt "kicked" = do
    roomname <- deformatRoom $ pkt^.parameter._Just
    uname <- use_ name
    removeRoom roomname
    I.sendKick uname (pkt^.args.ix "by") roomname $ pkt^.body.traverse ^? notNull_

respond pkt "send" = I.sendNotice $ "Send error: " <> pkt^.args.ix "e"

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 (p^.parameter._Just)
                       (g "pc")
                       (fromMaybe 0 $ st^.at room >>= (^.at (g "pc")))
                       (g "symbol")
                       (entityDecode $ g "realname")
                       (g "typename")
                       (g "gpc")
    where
        g s = p^.args.ix s

errHandlers :: [Handler KevinIO ()]
errHandlers = [ handler_ _KevinException $ klogError "Malformed communication from server"
              , handler _IOException (\e -> klogError $ "server: " ++ show e) ]