{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NoMonomorphismRestriction #-} module XMPPToTox ( module XMPPToTox , CryptoMessage(..) , MessageID(..) ) where import Control.Applicative import Control.Monad import Control.Monad.Catch import Data.Conduit import Data.Function import Data.Monoid import Data.Text (Text) import Data.Text.Encoding as T import Data.Word import Data.XML.Types as XML import Network.Tox.Crypto.Transport (CryptoMessage (..), MessageID (..)) import Text.XML.Stream.Parse as XML import XMPPServer (JabberShow(..)) -- Debugging. Not real Tox message. funnyMessage :: MonadThrow m => Text -> ConduitM i CryptoMessage m () funnyMessage txt = yield $ UpToN Padding (T.encodeUtf8 txt) sendMsg :: MonadThrow m => Text -> ConduitM i CryptoMessage m () sendMsg txt = do yield $ UpToN MESSAGE (T.encodeUtf8 txt) yield $ TwoByte TYPING 0 -- Message send implies not typing. eom :: MonadThrow m => ConduitM Event o m () eom = many_ ignoreAnyTreeContent msgToTox :: MonadThrow m => ConduitM Event CryptoMessage m (Maybe ()) msgToTox = tag' "{jabber:server}message" (requireAttr "type" >>= \case "chat" -> ignoreAttrs _ -> empty) $ \_ -> many_ $ choose [ tagIgnoreAttrs "{jabber:server}body" $ do content >>= sendMsg eom , tagIgnoreAttrs "{http://jabber.org/protocol/chatstates}composing" $ do yield $ TwoByte TYPING 1 eom , tagIgnoreAttrs "{http://jabber.org/protocol/chatstates}active" $ do yield $ TwoByte TYPING 0 eom , tagIgnoreAttrs "{http://jabber.org/protocol/chatstates}paused" $ do yield $ TwoByte TYPING 0 eom , ignoreAnyTreeContent ] readJabberShow :: Text -> JabberShow readJabberShow "xa" = ExtendedAway readJabberShow "chat" = Chatty readJabberShow "away" = Away readJabberShow "dnd" = DoNotDisturb readJabberShow _ = Available -- | Convert XMPP "show" field to Tox USERSTATUS. jabberUserStatus :: JabberShow -> Word8 jabberUserStatus Available = 0 jabberUserStatus Chatty = 0 jabberUserStatus Away = 1 jabberUserStatus ExtendedAway = 1 jabberUserStatus Offline = 1 jabberUserStatus DoNotDisturb = 2 requireMissing :: XML.Name -> AttrParser () requireMissing nm = force ("Unexpected "++show (XML.nameLocalName nm)++" attribute.") $ do m <- attr nm case m of Nothing -> return $ Just () Just _ -> return Nothing presenceToTox :: MonadThrow m => ConduitM Event CryptoMessage m (Maybe ()) presenceToTox = tag' "{jabber:server}presence" (requireMissing "type" >> ignoreAttrs) $ \_ -> do xs <- XML.many $ choose [ tagIgnoreAttrs "{jabber:server}show" $ do shw <- readJabberShow <$> content yield $ TwoByte USERSTATUS $ jabberUserStatus shw eom return USERSTATUS , tagIgnoreAttrs "{jabber:server}status" -- TODO: The tag may occur multiple times for different "xml:lang" values. $ do txt <- content yield $ UpToN STATUSMESSAGE $ T.encodeUtf8 txt eom return STATUSMESSAGE , fmap (const Padding) <$> ignoreAnyTreeContent -- Ignore the priority tag and anything else. ] when (not $ USERSTATUS `elem` xs) $ do -- Missing element means Available. yield $ TwoByte USERSTATUS $ jabberUserStatus Available unknownToTox :: MonadThrow m => ConduitM Event CryptoMessage m (Maybe ()) unknownToTox = tag anyName (\n -> ignoreAttrs >> return n) $ \n -> do funnyMessage $ nameLocalName n eom xmppToTox :: MonadThrow m => ConduitM XML.Event CryptoMessage m () xmppToTox = do eventBeginDocument <- await streamTag <- await fix $ \loop -> do got <- choose [ msgToTox , presenceToTox , unknownToTox ] forM_ got $ \_ -> loop