packages feed

haskell-xmpp-2.0.0: src/Network/XMPP/Stanza.hs

{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE QuasiQuotes           #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE OverloadedStrings     #-}
--These can disappear once we remove Content Posn versions
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}



-----------------------------------------------------------------------------
-- |
-- Module      :  Network.XMPP.Stanza
-- Copyright   :  (c) pierre, 2007
-- License     :  BSD-style (see the file libraries/base/LICENSE)
-- Copyright   :  (c) riskbook, 2020
-- SPDX-License-Identifier:  BSD3
--
-- Maintainer  :  k.pierre.k@gmail.com
-- Stability   :  experimental
-- Portability :  portable
--
-- XMPP stanzas parsing
--
-----------------------------------------------------------------------------

module Network.XMPP.Stanza
  ( StanzaEncoder(..)
  , StanzaDecoder(..)
  ) where

import           Control.Applicative         (Alternative, empty)
import           Data.Maybe                  (mapMaybe, listToMaybe)
import qualified Data.Text                   as T
import           Text.Hamlet.XML             (xml)
import           Text.XML                    (Node)
import           Text.XML.HaXml              (Content)
import           Text.XML.HaXml.Posn         (Posn)
import           Text.XML.HaXml.Xtract.Parse (xtract)
import           Network.XMPP.Types
import           Network.XMPP.XML

--------------------------------------------------------------------------------

class StanzaEncoder t p e a where
  encodeStanza :: Stanza t p e -> a

class StanzaDecoder t p e a where
  decodeStanza :: a -> Maybe (Stanza t p e)

--------------------------------------------------------------------------------

condToAlt :: Alternative m => (x -> Bool) -> x -> m x
condToAlt f x = if f x then pure x else empty

toAttrList :: [(String, Maybe a)] -> [(String, a)]
toAttrList = mapMaybe sequence

instance {-# OVERLAPPING #-} StanzaEncoder 'Message 'Outgoing e Node where
  encodeStanza MkMessage{..} = head [xml|
    <message *{messageAttrs} xml:lang=en>
      <body *{bodyAttrs}>
        #{mBody}
  |]
    where
      messageAttrs = toAttrList
        [ ("from", show <$> mFrom)
        , ("to", show <$> mTo)
        , ("id", Just $ T.unpack mId)
        , ("type", Just $ show mType)
        ]
      bodyAttrs = toAttrList
        [ ("subject", T.unpack <$> condToAlt (not . T.null) mSubject)
        , ("thread", T.unpack <$> condToAlt (not . T.null) mThread)
        ]

instance {-# OVERLAPPING #-} StanzaEncoder 'Presence 'Outgoing e Node where
  encodeStanza MkPresence{ pPurpose = SOutgoing, ..} = head [xml|
    <presence *{attrs} xml:lang="en">
      ^{pExt}
    |]
    where
      attrs = toAttrList
        [ ("from", show <$> pFrom)
        , ("to", show <$> pTo)
        , ("id", T.unpack <$> condToAlt (not . T.null) pId)
        , ("type", show <$> condToAlt (/= Default) pType)
        , ("show", show <$> condToAlt (/= Available) pShowType)
        , ("status", T.unpack <$> condToAlt (not . T.null) pStatus)
        , ("priority", show <$> pPriority)
        ]

instance {-# OVERLAPPING #-} StanzaEncoder 'IQ 'Outgoing e Node where
  encodeStanza MkIQ{ iqPurpose = SOutgoing, ..} = head [xml|
    <iq *{attrs} xml:lang="en">
      ^{iqBody}
  |]
    where
      attrs = toAttrList
        [ ("from", show <$> iqFrom)
        , ("to", show <$> iqTo)
        , ("id", Just $ T.unpack iqId)
        , ("type", Just $ show iqType)
        ]

instance StanzaEncoder t 'Outgoing e Node where
  encodeStanza s@MkPresence{} = encodeStanza s
  encodeStanza s@MkMessage{}  = encodeStanza s
  encodeStanza s@MkIQ{}       = encodeStanza s

instance FromXML e => StanzaDecoder 'Message 'Incoming e (Content Posn) where
  decodeStanza m =
    let content = xtract id "/message/*" m
    in
      Just $ MkMessage
        { mFrom    = mread $ txtpat "/message/@from" m
        , mTo      = mread $ txtpat "/message/@to" m
        , mId      = getText_ $ xtract id "/message/@id" m
        , mType    = read $ T.unpack $ getText_ $ xtract id "/message/@type" m
        , mSubject = getText_ $ xtract id "/message/subject/-" m
        , mBody    = getText_ $ xtract id "/message/body/-" m
        , mThread  = getText_ $ xtract id "/message/thread/-" m
        , mExt     = maybe (Left content) Right $ listToMaybe $ mapMaybe decodeXml
                                                                         content
        , mPurpose = SIncoming
        }

instance FromXML e => StanzaDecoder 'Presence 'Incoming e (Content Posn) where
  decodeStanza m =
    let content = xtract id "/presence/*" m
    in
      Just $ MkPresence
        { pFrom     = mread $ txtpat "/presence/@from" m
        , pTo       = mread $ txtpat "/presence/@to" m
        , pId       = txtpat "/presence/@id" m
        , pType     = read $ T.unpack $ txtpat "/presence/@type" m
        , pShowType = read $ T.unpack $ txtpat "/presence/show/-" m
        , pStatus   = txtpat "/presence/status/-" m
        , pPriority = mread $ txtpat "/presence/priority/-" m
        , pPurpose  = SIncoming
        , pExt = maybe (Left content) Right $ listToMaybe $ mapMaybe decodeXml content
        }

instance FromXML e => StanzaDecoder 'IQ 'Incoming e (Content Posn) where
  decodeStanza m =
    let content = xtract id "/iq/*" m
    in
      Just MkIQ
        { iqFrom    = mread $ txtpat "/iq/@from" m
        , iqTo      = mread $ txtpat "/iq/@to" m
        , iqId      = txtpat "/iq/@id" m
        , iqType    = read $ T.unpack $ txtpat "/iq/@type" m
        , iqBody = maybe (Left content) Right $ listToMaybe $ mapMaybe decodeXml
                                                                      content
        , iqPurpose = SIncoming
        }