summaryrefslogtreecommitdiff
path: root/dht/ToxToXMPP.hs
diff options
context:
space:
mode:
authorJames Crayne <jim.crayne@gmail.com>2019-09-28 13:43:29 -0400
committerJoe Crayne <joe@jerkface.net>2020-01-01 19:27:53 -0500
commit11987749fc6e6d3e53ea737d46d5ab13a16faeb8 (patch)
tree5716463275c2d3e902889db619908ded2a73971c /dht/ToxToXMPP.hs
parentadd2c76bced51fde5e9917e7449ef52be70faf87 (diff)
Factor out some new libraries
word64-map: Data.Word64Map network-addr: Network.Address tox-crypto: Crypto.Tox lifted-concurrent: Control.Concurrent.Lifted.Instrument Control.Concurrent.Async.Lifted.Instrument psq-wrap: Data.Wrapper.PSQInt Data.Wrapper.PSQ minmax-psq: Data.MinMaxPSQ tasks: Control.Concurrent.Tasks kad: Network.Kademlia Network.Kademlia.Bootstrap Network.Kademlia.Routing Network.Kademlia.CommonAPI Network.Kademlia.Persistence Network.Kademlia.Search
Diffstat (limited to 'dht/ToxToXMPP.hs')
-rw-r--r--dht/ToxToXMPP.hs226
1 files changed, 226 insertions, 0 deletions
diff --git a/dht/ToxToXMPP.hs b/dht/ToxToXMPP.hs
new file mode 100644
index 00000000..1420c642
--- /dev/null
+++ b/dht/ToxToXMPP.hs
@@ -0,0 +1,226 @@
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE GADTs #-}
3{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE NamedFieldPuns #-}
5{-# LANGUAGE NondecreasingIndentation #-}
6{-# LANGUAGE ViewPatterns #-}
7module ToxToXMPP where
8
9import Control.Monad
10import Crypto.Tox
11import Conduit as C
12import qualified Data.Conduit.List as CL
13import Data.Dependent.Sum
14import Data.Function
15import Data.Functor.Identity
16import Data.Maybe
17import Data.Monoid
18import qualified Data.Text as T
19 ;import Data.Text (Text)
20import Data.Text.Encoding as T
21import Data.Tox.Msg as Tox
22import Data.Word
23import Data.XML.Types as XML
24import EventUtil
25import Network.Address
26import Network.Tox.Crypto.Transport as Tox hiding (UserStatus (..))
27import Network.Tox.NodeId
28import Util (unsplitJID)
29import XMPPServer as XMPP
30
31available :: StanzaType
32available = PresenceStatus { presenceShow = Available
33 , presencePriority = Nothing
34 , presenceStatus = []
35 , presenceWhiteList = []
36 }
37
38xmppHostname :: PublicKey -> Text
39xmppHostname k = T.pack $ show (key2id k) ++ ".tox"
40
41toxUserStatus :: UserStatus -> JabberShow
42toxUserStatus Online = Available
43toxUserStatus Tox.Away = XMPP.Away
44toxUserStatus Busy = DoNotDisturb
45toxUserStatus _ = XMPP.Away -- Default, shouldn't occur.
46
47-- Currently unused, see note in 'toxJID'.
48toJabberResource :: Int -> Maybe Text
49toJabberResource addr = T.pack . show <$> Just (positive addr)
50 where
51 positive addr | addr < 0 = 2 * negate addr + 1
52 | otherwise = 2 * addr
53
54toxJID :: Text -> Int -> Text
55toxJID theirhost addr =
56 -- unsplitJID (Nothing, theirhost, toJabberResource addr)
57 --
58 -- Not encoding the Tox session ID because Pidgin apparently doesn't
59 -- cope well with resource IDs occuring on bare hostname JIDs.
60 unsplitJID (Nothing, theirhost, Nothing)
61
62toxToXmpp :: Monad m =>
63 (Int -> Maybe Text -> Invite -> m ())
64 -> SockAddr
65 -> PublicKey
66 -> Text
67 -> ConduitM (Int,Tox.CryptoMessage) XML.Event m ()
68toxToXmpp store_invite _ me theirhost = do
69 CL.sourceList $ XMPP.greet' "jabber:server" theirhost
70 let me_u = Nothing
71 me_h = xmppHostname me
72 im_to = (Just $ unsplitJID
73 ( me_u
74 -- /to/ should match local address of this node.
75 , me_h
76 , Nothing))
77 let
78 statelessMessages addr im_from = \case
79
80 Pkt MESSAGE :=> Identity bs ->
81 xmppInstantMessage "jabber:server" im_from im_to [] bs
82
83 Pkt TYPING :=> Identity st -> xmppTyping "jabber:server" im_from im_to st
84
85 Pkt NICKNAME :=> Identity bs ->
86 xmppInstantMessage "jabber:server" im_from im_to
87 [ attr "style" "font-weight:bold; color:red" ]
88 ("NICKNAME(todo) " <> bs)
89
90 toxmsg | msgID toxmsg == M PacketRequest -> return ()
91
92 Pkt INVITE_GROUPCHAT :=> Identity ginv -> do
93 xmppInstantMessage "jabber:server" im_from im_to
94 [ attr "style" "font-weight:bold; color:red" ]
95 ("INVITE(todo)" <> (T.pack $ show ginv))
96 case invite ginv of
97 GroupInvite {} -> do C.lift $ store_invite addr im_from ginv
98 xmppInvite "jabber:server" me_h (fromJust im_from) (fromJust im_to) ginv
99 _ -> return ()
100
101 toxmsg -> do
102 xmppInstantMessage "jabber:server" im_from im_to
103 [ attr "style" "font-weight:bold; color:red" ]
104 (T.pack $ "Unhandled message: " ++ show (msgID toxmsg))
105
106 flip fix available $ \loop status -> do
107 m <- await
108 forM_ m $ \(addr,x) -> do
109 let im_from = (Just $ toxJID theirhost addr)
110 case x of
111 Pkt USERSTATUS :=> Identity st -> do
112 let status' = status { presenceShow = toxUserStatus st }
113 xmppPresence "jabber:server" im_from status'
114 loop status'
115
116 Pkt STATUSMESSAGE :=> Identity bs -> do
117 let status' = status { presenceStatus = [("",bs)] }
118 xmppPresence "jabber:server" im_from status'
119 loop status'
120
121 Pkt ONLINE :=> _ -> do
122 xmppPresence "jabber:server" im_from status
123 loop status
124
125 x -> do
126 statelessMessages addr im_from x
127 loop status
128
129xmppPresence :: Monad m => Text -> Maybe Text -> StanzaType -> ConduitM i XML.Event m ()
130xmppPresence namespace mjid p = do
131 let ns n = n { nameNamespace = Just namespace }
132 setFrom = maybe id
133 (\jid -> (attr "from" jid :) )
134 mjid
135 typ Offline = [attr "type" "unavailable"]
136 typ _ = []
137 shw ExtendedAway = ["xa"]
138 shw Chatty = ["chat"]
139 shw XMPP.Away = ["away"]
140 shw DoNotDisturb = ["dnd"]
141 shw _ = []
142 jabberShow stat =
143 [ EventBeginElement "{jabber:client}show" []
144 , EventContent (ContentText stat)
145 , EventEndElement "{jabber:client}show" ]
146 C.yield $ EventBeginElement (ns "presence") (setFrom $ typ $ presenceShow p)
147 mapM_ C.yield $ shw (presenceShow p) >>= jabberShow
148 forM_ (presencePriority p) $ \prio -> do
149 C.yield $ EventBeginElement (ns "priority") []
150 C.yield $ EventContent $ ContentText (T.pack $ show prio)
151 C.yield $ EventEndElement (ns "priority")
152 forM_ (presenceStatus p) $ \(lang,txt) -> do
153 let atts | T.null lang = []
154 | otherwise = [ ("xml:lang", [ContentText lang]) ]
155 C.yield $ EventBeginElement (ns "status") atts
156 C.yield $ EventContent $ ContentText txt
157 C.yield $ EventEndElement (ns "status")
158 C.yield $ EventEndElement (ns "presence")
159
160chatRoomJID me cid = T.pack (show cid) <> "@ngc." <> me
161
162xmppInvite :: Monad m => Text -> Text -> Text -> Text -> Invite -> ConduitM i XML.Event m ()
163xmppInvite namespace me them to inv =
164 let ns n = n { nameNamespace = Just namespace }
165 in mapM_ C.yield
166 [ EventBeginElement (ns "message")
167 [ attr "from" (chatRoomJID me $ inviteChatID inv)
168 , attr "to" to
169 ]
170 , EventBeginElement "{http://jabber.org/protocol/muc#user}x" []
171 , EventBeginElement "{http://jabber.org/protocol/muc#user}invite"
172 [ attr "from" them ]
173 , EventBeginElement "{http://jabber.org/protocol/muc#user}reason" []
174 , EventContent (ContentText $ groupName $ invite inv)
175 , EventEndElement "{http://jabber.org/protocol/muc#user}reason"
176 , EventEndElement "{http://jabber.org/protocol/muc#user}invite"
177 , EventEndElement "{http://jabber.org/protocol/muc#user}x"
178 , EventEndElement (ns "message")
179 ]
180
181xmppTyping :: Monad m => Text
182 -> Maybe Text
183 -> Maybe Text
184 -> Bool
185 -> ConduitM i XML.Event m ()
186xmppTyping namespace mfrom mto x =
187 let ns n = n { nameNamespace = Just namespace }
188 st = case x of
189 False -> "{http://jabber.org/protocol/chatstates}active"
190 True -> "{http://jabber.org/protocol/chatstates}composing"
191 -- tox-core supports only 0 and 1
192 -- _ -> "{http://jabber.org/protocol/chatstates}paused"
193 in mapM_ C.yield
194 [ EventBeginElement (ns "message")
195 ( maybe id (\t->(attr "from" t:)) mfrom
196 $ maybe id (\t->(attr "to" t:)) mto
197 $ [attr "type" "chat" ] )
198 , EventBeginElement st []
199 , EventEndElement st
200 , EventEndElement (ns "message")
201 ]
202
203xmppInstantMessage :: Monad m => Text
204 -> Maybe Text
205 -> Maybe Text
206 -> [(Name, [Content])]
207 -> Text
208 -> ConduitM i XML.Event m ()
209xmppInstantMessage namespace mfrom mto style text = do
210 let ns n = n { nameNamespace = Just namespace }
211 C.yield $ EventBeginElement (ns "message")
212 ( maybe id (\t->(attr "from" t:)) mfrom
213 $ maybe id (\t->(attr "to" t:)) mto
214 $ [attr "type" "normal" ] )
215 C.yield $ EventBeginElement (ns "body") []
216 C.yield $ EventContent $ ContentText text
217 C.yield $ EventEndElement (ns "body")
218 C.yield $ EventBeginElement "{http://jabber.org/protocol/xhtml-im}html" []
219 C.yield $ EventBeginElement "{http://www.w3.org/1999/xhtml}body" []
220 C.yield $ EventBeginElement "{http://www.w3.org/1999/xhtml}p" style
221 C.yield $ EventContent $ ContentText text
222 C.yield $ EventEndElement "{http://www.w3.org/1999/xhtml}p"
223 C.yield $ EventEndElement "{http://www.w3.org/1999/xhtml}body"
224 C.yield $ EventEndElement "{http://jabber.org/protocol/xhtml-im}html"
225 C.yield $ EventEndElement (ns "message")
226