diff options
author | James Crayne <jim.crayne@gmail.com> | 2019-09-28 13:43:29 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2020-01-01 19:27:53 -0500 |
commit | 11987749fc6e6d3e53ea737d46d5ab13a16faeb8 (patch) | |
tree | 5716463275c2d3e902889db619908ded2a73971c /dht/ToxToXMPP.hs | |
parent | add2c76bced51fde5e9917e7449ef52be70faf87 (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.hs | 226 |
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 #-} | ||
7 | module ToxToXMPP where | ||
8 | |||
9 | import Control.Monad | ||
10 | import Crypto.Tox | ||
11 | import Conduit as C | ||
12 | import qualified Data.Conduit.List as CL | ||
13 | import Data.Dependent.Sum | ||
14 | import Data.Function | ||
15 | import Data.Functor.Identity | ||
16 | import Data.Maybe | ||
17 | import Data.Monoid | ||
18 | import qualified Data.Text as T | ||
19 | ;import Data.Text (Text) | ||
20 | import Data.Text.Encoding as T | ||
21 | import Data.Tox.Msg as Tox | ||
22 | import Data.Word | ||
23 | import Data.XML.Types as XML | ||
24 | import EventUtil | ||
25 | import Network.Address | ||
26 | import Network.Tox.Crypto.Transport as Tox hiding (UserStatus (..)) | ||
27 | import Network.Tox.NodeId | ||
28 | import Util (unsplitJID) | ||
29 | import XMPPServer as XMPP | ||
30 | |||
31 | available :: StanzaType | ||
32 | available = PresenceStatus { presenceShow = Available | ||
33 | , presencePriority = Nothing | ||
34 | , presenceStatus = [] | ||
35 | , presenceWhiteList = [] | ||
36 | } | ||
37 | |||
38 | xmppHostname :: PublicKey -> Text | ||
39 | xmppHostname k = T.pack $ show (key2id k) ++ ".tox" | ||
40 | |||
41 | toxUserStatus :: UserStatus -> JabberShow | ||
42 | toxUserStatus Online = Available | ||
43 | toxUserStatus Tox.Away = XMPP.Away | ||
44 | toxUserStatus Busy = DoNotDisturb | ||
45 | toxUserStatus _ = XMPP.Away -- Default, shouldn't occur. | ||
46 | |||
47 | -- Currently unused, see note in 'toxJID'. | ||
48 | toJabberResource :: Int -> Maybe Text | ||
49 | toJabberResource addr = T.pack . show <$> Just (positive addr) | ||
50 | where | ||
51 | positive addr | addr < 0 = 2 * negate addr + 1 | ||
52 | | otherwise = 2 * addr | ||
53 | |||
54 | toxJID :: Text -> Int -> Text | ||
55 | toxJID 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 | |||
62 | toxToXmpp :: Monad m => | ||
63 | (Int -> Maybe Text -> Invite -> m ()) | ||
64 | -> SockAddr | ||
65 | -> PublicKey | ||
66 | -> Text | ||
67 | -> ConduitM (Int,Tox.CryptoMessage) XML.Event m () | ||
68 | toxToXmpp 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 | |||
129 | xmppPresence :: Monad m => Text -> Maybe Text -> StanzaType -> ConduitM i XML.Event m () | ||
130 | xmppPresence 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 | |||
160 | chatRoomJID me cid = T.pack (show cid) <> "@ngc." <> me | ||
161 | |||
162 | xmppInvite :: Monad m => Text -> Text -> Text -> Text -> Invite -> ConduitM i XML.Event m () | ||
163 | xmppInvite 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 | |||
181 | xmppTyping :: Monad m => Text | ||
182 | -> Maybe Text | ||
183 | -> Maybe Text | ||
184 | -> Bool | ||
185 | -> ConduitM i XML.Event m () | ||
186 | xmppTyping 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 | |||
203 | xmppInstantMessage :: Monad m => Text | ||
204 | -> Maybe Text | ||
205 | -> Maybe Text | ||
206 | -> [(Name, [Content])] | ||
207 | -> Text | ||
208 | -> ConduitM i XML.Event m () | ||
209 | xmppInstantMessage 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 | |||