summaryrefslogtreecommitdiff
path: root/ToxToXMPP.hs
blob: 7208a1d1c8e8c72dbedcb8a3c68137139dd8601a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
{-# LANGUAGE CPP                      #-}
{-# LANGUAGE LambdaCase               #-}
{-# LANGUAGE NamedFieldPuns           #-}
{-# LANGUAGE NondecreasingIndentation #-}
{-# LANGUAGE ViewPatterns             #-}
module ToxToXMPP where

import Control.Monad
import Crypto.Tox
import Data.Conduit                 as C
import qualified Data.Conduit.List  as CL
import Data.Function
import Data.Monoid
import qualified Data.Text          as T
         ;import Data.Text          (Text)
import Data.Text.Encoding           as T
import Data.Word
import Data.XML.Types               as XML
import EventUtil
import Network.Address
import Network.Tox.Crypto.Transport as Tox hiding (UserStatus (..))
import Network.Tox.NodeId
import Util                         (unsplitJID)
import XMPPServer                   as XMPP

available :: StanzaType
available = PresenceStatus { presenceShow      = Available
                           , presencePriority  = Nothing
                           , presenceStatus    = []
                           , presenceWhiteList = []
                           }

xmppHostname :: PublicKey -> Text
xmppHostname k = T.pack $ show (key2id k) ++ ".tox"

toxUserStatus :: Word8 -> JabberShow
toxUserStatus 0 = Available
toxUserStatus 1 = Away
toxUserStatus 2 = DoNotDisturb
toxUserStatus _ = Away -- Default, shouldn't occur.

toxToXmpp :: Monad m => SockAddr -> PublicKey -> Text -> ConduitM Tox.CryptoMessage XML.Event m ()
toxToXmpp laddr me theirhost = do
    CL.sourceList $ XMPP.greet' "jabber:server" theirhost
    let me_u    = Nothing
        me_h    = xmppHostname me
        im_from = (Just $ unsplitJID (Nothing, theirhost, Nothing)) -- /from/
        im_to   = (Just $ unsplitJID
                            ( me_u
                            -- /to/ should match local address of this node.
                            , me_h
                            , Nothing))
    let
      statelessMessages = \case

        UpToN MESSAGE bs ->
            xmppInstantMessage "jabber:server" im_from im_to [] (T.decodeUtf8 bs)

        TwoByte TYPING st -> xmppTyping "jabber:server" im_from im_to st

        UpToN NICKNAME bs ->
            xmppInstantMessage "jabber:server" im_from im_to
                [ attr "style" "font-weight:bold; color:red" ]
                ("NICKNAME(todo) " <> T.decodeUtf8 bs)

        toxmsg | msgID toxmsg == PacketRequest -> return ()

        toxmsg -> do
            xmppInstantMessage "jabber:server" im_from im_to
                [ attr "style" "font-weight:bold; color:red" ]
                (T.pack $ "Unhandled message: " ++ show (msgID toxmsg))

    flip fix available $ \loop status -> do
        let go (TwoByte USERSTATUS st) = do
                let status' = status { presenceShow = toxUserStatus st }
                xmppPresence "jabber:server" im_from status'
                loop status'

            go (UpToN STATUSMESSAGE bs) = do
                let status' = status { presenceStatus = [("",T.decodeUtf8 bs)] }
                xmppPresence "jabber:server" im_from status'
                loop status'

            go (OneByte ONLINE) = do
                xmppPresence "jabber:server" im_from status
                loop status

            go x = do
                statelessMessages x
                loop status
        await >>= mapM_ go

xmppPresence :: Monad m => Text -> Maybe Text -> StanzaType -> ConduitM i XML.Event m ()
xmppPresence namespace mjid p = do
    let ns n = n { nameNamespace = Just namespace }
        setFrom = maybe id
                        (\jid -> (attr "from" jid :) )
                        mjid
        typ Offline = [attr "type" "unavailable"]
        typ _       = []
        shw ExtendedAway = ["xa"]
        shw Chatty       = ["chat"]
        shw Away         = ["away"]
        shw DoNotDisturb = ["dnd"]
        shw _            = []
        jabberShow stat =
          [ EventBeginElement "{jabber:client}show" []
          , EventContent (ContentText stat)
          , EventEndElement "{jabber:client}show" ]
    C.yield       $ EventBeginElement (ns "presence") (setFrom $ typ $ presenceShow p)
    mapM_ C.yield $ shw (presenceShow p) >>= jabberShow
    forM_ (presencePriority p) $ \prio -> do
        C.yield   $ EventBeginElement (ns "priority") []
        C.yield   $   EventContent $ ContentText (T.pack $ show prio)
        C.yield   $ EventEndElement (ns "priority")
    forM_ (presenceStatus p) $ \(lang,txt) -> do
        let atts | T.null lang = []
                 | otherwise   = [ ("xml:lang", [ContentText lang]) ]
        C.yield   $ EventBeginElement (ns "status") atts
        C.yield   $   EventContent $ ContentText txt
        C.yield   $ EventEndElement (ns "status")
    C.yield       $ EventEndElement (ns "presence")

xmppTyping :: Monad m => Text
                              -> Maybe Text
                              -> Maybe Text
                              -> Word8
                              -> ConduitM i XML.Event m ()
xmppTyping namespace mfrom mto x =
    let ns n = n { nameNamespace = Just namespace }
        st = case x of
            0 -> "{http://jabber.org/protocol/chatstates}active"
            1 -> "{http://jabber.org/protocol/chatstates}composing"
            -- tox-core supports only 0 and 1
            _ -> "{http://jabber.org/protocol/chatstates}paused"
    in mapM_ C.yield
        [ EventBeginElement (ns "message")
               (  maybe id (\t->(attr "from" t:)) mfrom
                $ maybe id (\t->(attr "to" t:)) mto
                $ [attr "type" "chat" ] )
        , EventBeginElement st []
        , EventEndElement st
        , EventEndElement (ns "message")
        ]

xmppInstantMessage :: Monad m => Text
                              -> Maybe Text
                              -> Maybe Text
                              -> [(Name, [Content])]
                              -> Text
                              -> ConduitM i XML.Event m ()
xmppInstantMessage namespace mfrom mto style text = do
    let ns n = n { nameNamespace = Just namespace }
    C.yield $ EventBeginElement (ns "message")
               (  maybe id (\t->(attr "from" t:)) mfrom
                $ maybe id (\t->(attr "to" t:)) mto
                $ [attr "type" "normal" ] )
    C.yield $   EventBeginElement (ns "body") []
    C.yield $     EventContent $ ContentText text
    C.yield $   EventEndElement (ns "body")
    C.yield $   EventBeginElement "{http://jabber.org/protocol/xhtml-im}html" []
    C.yield $     EventBeginElement "{http://www.w3.org/1999/xhtml}body" []
    C.yield $        EventBeginElement "{http://www.w3.org/1999/xhtml}p" style
    C.yield $          EventContent $ ContentText text
    C.yield $        EventEndElement "{http://www.w3.org/1999/xhtml}p"
    C.yield $     EventEndElement "{http://www.w3.org/1999/xhtml}body"
    C.yield $   EventEndElement "{http://jabber.org/protocol/xhtml-im}html"
    C.yield $ EventEndElement (ns "message")