packages feed

matsuri-0.0.3: Jabber.hs

-- | Jabber.hs
-- A module for interaction with XMPP.

module Jabber where

import Buffers
import Config
import Utils

import Network
import Network.XMPP
import Network.XMPP.MUC
import Control.Concurrent
import Control.Concurrent.MVar
import Data.Maybe
import Data.List
import qualified Data.Map as M


connect :: MVar MEvent -> Config -> Account -> IO Buffer
connect mev config acc =
    case connection acc of
      NoConnection -> do
        forkIO $ do
            c <- openStream (server acc)
            getStreamStart c
            runXMPP c $ do
              err <- startAuth (username acc)
                               (server acc)
                               (password acc)
                               (resource acc)
              if err == 0
                then do
                  tId <- liftIO $ myThreadId
                  put mev $ insConn (OK c tId)

                  items <- getRoster
                  mapM_ (put mev . insChat) items
                  -- create groups; if roster item doesn't have any
                  -- groups then it's element of default group from
                  -- config
                  mapM_ (put mev . insGroup items) (groups items)
                  put mev $ insDefaultGroup items
                  put mev $ insConferencesGroup

                  sendPresence Nothing (Just $ priority acc)
                  -- set handlers
                  handleVersion (getCF "client" config)
                                (getCF "version" config)
                                (getCF "OS" config)
                  addHandler isPresence (presenceCB mev k) True
                  addHandler isChat (chatCB mev k) True
                  addHandler isGroupchatPresence (roomPresenceCB mev k) True
                  addHandler isGroupchatMessage (roomCB mev k) True
                  ---
                else put mev $ insConn NoConnection
        return $ doBuf Trying
      _ -> return (BufAccount acc)
  where
    k = accName acc
    insConn = InsBuffer k . doBuf
    doBuf conn = BufAccount acc{connection=conn}

    insChat item = InsBuffer (chatName' item) (item2chat item)
    item2chat item = BufChat (Chat item offline (chatName' item) [])
    offline = Status StatusOffline []
    chatName' item = k++"|"++itemJid item

    insGroup items name = insGroup' (name `elem`) items name
    insDefaultGroup items = insGroup' null items (getCF "default_group" config)
    insConferencesGroup = InsBuffer conf_group
                          $ BufGroup $ Group conf_group False []
      where conf_group = k++"|"++getCF "conferences_group" config
    groups = nub . concat . map itemGroups
    insGroup' p items name = InsBuffer (k++"|"++name) (grp p name items)
    grp p name = BufGroup . Group (k++"|"++name) False . grpItems' p name
    grpItems' p name
      = map chatName' . filter (p . itemGroups)


-- | Close account connection.
disconnect :: Account -> IO Buffer
disconnect account =
    case connection account of
      OK c tId -> do
        killThread tId
        closeConnection c
        return (BufAccount account{connection=NoConnection})
      _ -> return (BufAccount account)


-- | Status changes.
presenceCB :: MVar MEvent -> String -> StanzaHandler
presenceCB mev acc stanza = do
    let (jid, res) = getJidRes stanza
        presence = doPresence stanza
    case presence of
      Available status' -> putStatus jid status'
      Unavailable status' -> putStatus jid status'
      _ -> return ()
  where
    putStatus jid = put mev . NewStatus (acc++"|"++jid)


-- | Send chat message.
sendChatMessage :: Account -> Chat -> String -> IO Buffer
sendChatMessage acc chat msg = case connection acc of
    OK c _ -> do
      runXMPP c $ sendMessage (itemJid $ item chat) msg
      time <- nowTime
      let msg' = MyMsg $ time++" => "++msg
      return $ BufChat chat{chatContents=msg':(chatContents chat)}


-- | Callback for chat messages.
chatCB :: MVar MEvent -> String -> StanzaHandler
chatCB mev acc stanza = do
    (jid, res, body, time, isHistory) <- liftIO $ readStanza stanza
    let msg = time++" <= "++body
        msg' = if isHistory then HistoryMsg msg else Msg msg

    put mev $ NewMsg (acc++"|"++jid) msg'


-- | Join room.
joinRoom :: Account -> String -> IO Buffer
joinRoom acc room = case connection acc of
    OK c _ -> do
      let name = accName acc++"|"++room
          nick = defaultNick acc
      runXMPP c $ joinGroupchat nick room Nothing
      return $ BufRoom $ Room name [] nick "" (M.empty)


-- | Send room message.
sendRoomMessage :: Account -> Room -> String -> IO ()
sendRoomMessage acc room msg = case connection acc of
    OK c _ -> runXMPP c $ sendGroupchatMessage jid msg
    where
      jid = name (roomName room)


-- | Callback for room presences.
roomPresenceCB :: MVar MEvent -> String -> StanzaHandler
roomPresenceCB mev acc stanza = do
    time <- liftIO $ nowTime
    case getAttr "type" stanza of
      Nothing ->
          let msg = InfoMsg (time++" ** Join: "++show occ)
          in put mev $ RoomPresence Join k occ msg
      Just "unavailable" ->
          let msg = InfoMsg (time++" ** Leave: "++show occ)
          in put mev $ RoomPresence Leave k occ msg
      _ -> return ()
  where
    occ = doOccupant stanza
    (conf_jid, _) = getJidRes stanza
    k = acc++"|"++conf_jid


-- | Callback for room messages.
roomCB :: MVar MEvent -> String -> StanzaHandler
roomCB mev acc stanza = do
    (room, nick, body, time, isHistory) <- liftIO $ readStanza stanza
    let msg = time++" <"++nick++"> "++body
        subj = getMessageSubject stanza
        header = time++" ** Topic: "++nick++"\n"
        msg_subj = header++fromMaybe "" subj
        msg' | isJust subj = (Nothing,   msg_subj, Just msg_subj)
             | isHistory   = (Nothing,   msg,      Nothing      )
             | otherwise   = (Just nick, msg,      Nothing      )

    put mev $ NewRoomMsg (acc++"|"++room) msg'


---
readStanza stanza = do
    time <- time'
    return (jid, res, body, time, isHistory)
  where
    (jid, res) = getJidRes stanza
    body = maybe "" id (getMessageBody stanza)
    stamp = getMessageStamp stanza
    isHistory = isJust stamp
    time' | isHistory = utcToZoned $ maybe "" id stamp
          | otherwise = nowTime

put m = liftIO . putMVar m